","center"===(n=n[0].replace("align=",""))?s='':"left"===n?s='
':"right"===n&&(s='
')),s+' "+l},e.emoji=function(e){var t=(e=e.replace(C.regexs.emojiDatetime,function(e){return e.replace(/:/g,":")})).match(r);if(t&&a.emoji)for(var i=0,o=t.length;i'}else{var s=e.match(w),l=e.match(g);if(s)for(var c=0,h=s.length;c'}else{if(!l)return' ';for(var u=0,f=l.length;u '}}}});return e},e.atLink=function(e){return n.test(e)&&(a.atLink&&(e=(e=e.replace(i,function(e,t,i,o){return e.replace(/@/g,"_#_@_#_")})).replace(n,function(e,t){return''+e+" "}).replace(/_#_@_#_/g,"@")),a.emailLink)?e.replace(o,function(e,t,i,o,n){return!t&&v.inArray(n,"jpg|jpeg|png|gif|webp|ico|icon|pdf".split("|"))<0?''+e+" ":e}):e},e.link=function(e,t,i){if(this.options.sanitize){try{var o=decodeURIComponent(unescape(e)).replace(/[^\w:]/g,"").toLowerCase()}catch(e){return""}if(0===o.indexOf("javascript:"))return""}return 0===e.indexOf("@")?' ':(o=''+i.replace(/@/g,"@")+" "):(t&&(o+=' title="'+t+'"'),o+">"+i+""))},e.heading=function(e,t,i){var o=e,n=/\s*\]*)\>(.*)\<\/a\>\s*/;if(n.test(e)){for(var r=[],a=0,s=(e=e.split(/\ ]+)\>([^\>]*)\<\/a\>/)).length;a');return(l+='')+(n?this.atLink(this.emoji(o)):this.atLink(this.emoji(e)))+(" ")},e.pageBreak=function(e){return e=s.test(e)&&a.pageBreak?' ':e},e.paragraph=function(e){var t=/\$\$(.+?)\$\$/g.test(e),i=/^(?!\$\$.+?(?:\$\$).+?\$\$)(\$\$(.+?)\$\$)$/.test(e),o=i?' class="'+C.classNames.tex+'"':"",n=(a.tocm?/^(\[TOC\]|\[TOCM\])$/:/^\[TOC\]$/).test(e),r=/^\[TOCM\]$/.test(e),t=''+(e=!i&&t?e.replaceAll(/(\$\$(.+?)\$\$)/g,function(e,t,i){return''+i.replaceAll(/\ /g,"")+" "}):i?e.replace(/^(?!\$\$.+?(?:\$\$).+?\$\$)(\$\$(.+?)\$\$)$/,(e,t,i)=>i):e)+"
";return n?r?' ":t:s.test(e)?this.pageBreak(e):""+this.atLink(this.emoji(e))+"
\n"},e.code=function(e,t,i){if("seq"===t||"sequence"===t)return''+e+"
";if("mermaid"===t){for(var o=(a="ABCDEFGHJKLMNOPQRSTUVWXYZabcdefhijlkmnopqrstuvwxyz012345678").length,n="",r=0;r<4;r++)n+=a.charAt(Math.floor(Math.random()*o));return''+e+"
"}if("flow"===t)return''+e+"
";if("math"===t||"latex"===t||"katex"===t)return''+e+"
";if(/^mindmap/i.test(t)){for(var a,o=(a="ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678").length,s="",l=t.split(">")[1],l=null!=l?l:150,r=0;r<9;r++)s+=a.charAt(Math.floor(Math.random()*o));return""+e+" "}return"drawio"===t?''+decodeURIComponent(escape(window.atob(e)))+"
":c.Renderer.prototype.code.apply(this,arguments)},e.tablecell=function(e,t){var i=t.header?"th":"td";return(t.align?"<"+i+' style="text-align:'+t.align+'">':"<"+i+">")+this.atLink(this.emoji(e))+""+i+">\n"},e.listitem=function(e){return a.taskList&&/^\s*\[[x\s]\]\s*/.test(e)?(e=e.replace(/^\s*\[\s\]\s*/,' ').replace(/^\s*\[x\]\s*/,' '),''+this.atLink(this.emoji(e))+" "):""+this.atLink(this.emoji(e))+" "},e.checkbox=function(e){return e?' ':' '},e},C.markdownToCRenderer=function(e,t,i,o){var n="",r=0,a=this.classPrefix;o=o||1;for(var s=0,l=e.length;s"):"")+''+c+" ",r=h)}var u,f=t.find(".markdown-toc");return f.length<1&&"false"===t.attr("previewContainer")&&(u='
',t.html(u=i?'":u),f=t.find(".markdown-toc")),i&&f.wrap(' '),f.html('').children(".markdown-toc-list").html(n.replace(/\r?\n?\\<\/ul\>/g,"")),f},C.tocDropdownMenu=function(e,a){a=a||"Table of Contents";var s=400,e=e.find("."+this.classPrefix+"toc-menu");return e.each(function(){var e=v(this),t=e.children(".markdown-toc"),o=' ',i='",n=t.children("ul"),r=n.find("li");t.append(i),r.first().before(""+a+" "+o+" "),e.mouseover(function(){n.show(),r.each(function(){var e,t=v(this),i=t.children("ul");""===i.html()&&i.remove(),0]*)>([^>]*)","igm"),"")}void 0!==a&&(t=/\<(\w+)\s*([^\>]*)\>([^\>]*)\<\/(\w+)\>/gi,e="*"===a?e.replace(t,function(e,t,i,o,n){return"<"+t+">"+o+""+n+">"}):"on*"===a?e.replace(t,function(e,t,i,o,n){var t=v("<"+t+">"+o+""+n+">"),o=v(e)[0].attributes,r={},n=(v.each(o,function(e,t){'"'!==t.nodeName&&(r[t.nodeName]=t.nodeValue)}),v.each(r,function(e){0===e.indexOf("on")&&delete r[e]}),t.attr(r),void 0!==t[1]?v(t[1]).text():"");return t[0].outerHTML+n}):e.replace(t,function(e,t,i,o){var n=a.split(","),r=v(e);return r.html(o),v.each(n,function(e){r.attr(n[e],null)}),r[0].outerHTML}))}return e},C.markdownToHTML=function(e,t){C.$marked=marked;var i,o=v("#"+e),e=o.settings=v.extend(!0,{gfm:!0,toc:!0,tocm:!1,tocStartLevel:1,tocTitle:"目录",tocDropdown:!1,tocContainer:"",markdown:"",markdownSourceCode:!1,htmlDecode:!1,autoLoadKaTeX:!0,pageBreak:!0,atLink:!0,emailLink:!0,tex:!1,taskList:!1,emoji:!1,flowChart:!1,sequenceDiagram:!1,previewCodeHighlight:!0,mermaid:!0,mindMap:!0},t||{}),n=o.find("textarea"),t=(n.length<1&&(o.append(""),n=o.find("textarea")),void 0===e.markdown?n.val():e.markdown),r=[],a={toc:e.toc,tocm:e.tocm,tocStartLevel:e.tocStartLevel,taskList:e.taskList,emoji:e.emoji,tex:e.tex,pageBreak:e.pageBreak,atLink:e.atLink,emailLink:e.emailLink,flowChart:e.flowChart,sequenceDiagram:e.sequenceDiagram,mermaid:e.mermaid,mindMap:e.mindMap,previewCodeHighlight:e.previewCodeHighlight},a={renderer:C.markedRenderer(r,a),gfm:e.gfm,tables:!0,breaks:!0,pedantic:!1,sanitize:!e.htmlDecode,smartLists:!0,smartypants:!0},t=new String(t),a=marked.parse(t,a),a=C.$filterXSS(a),t=(e.markdownSourceCode?n.text(t):n.remove(),o.addClass("markdown-body "+this.classPrefix+"html-preview").append(a),""!==e.tocContainer?v(e.tocContainer):o);return""!==e.tocContainer&&t.attr("previewContainer",!1),e.toc&&(o.tocContainer=this.markdownToCRenderer(r,t,e.tocDropdown,e.tocStartLevel),(e.tocDropdown||0"),e[0]),e.find(".katex").css("font-size","1.6em")})},!e.autoLoadKaTeX||C.$katex||C.kaTeXLoaded?i():this.loadKaTeX(function(){C.$katex=katex,C.kaTeXLoaded=!0,i()})),e.mindMap&&o.find(".mindmap").each(function(){var e=v(this),e=window.markmap.transform(e.text().trim());window.markmap.markmap("svg#"+this.id,e)}),o.getMarkdown=function(){return n.val()},o},C.themes=["default","dark"],C.previewThemes=["default","dark"],C.editorThemes=["default","3024-day","3024-night","ambiance","ambiance-mobile","base16-dark","base16-light","blackboard","cobalt","eclipse","elegant","erlang-dark","lesser-dark","mbo","mdn-like","midnight","monokai","neat","neo","night","paraiso-dark","paraiso-light","pastel-on-dark","rubyblue","solarized","the-matrix","tomorrow-night-eighties","twilight","vibrant-ink","xq-dark","xq-light"],C.loadPlugins={},C.loadFiles={js:[],css:[],plugin:[]},C.loadPlugin=function(e,t,i,o){t=t||function(){},this.loadScript(e,function(){C.loadFiles.plugin.push(e),t()},i,o)},C.loadCSS=function(e,t,i,o=C.version){i=i||"head",t=t||function(){},o=o||C.version;var n=document.createElement("link");n.type="text/css",n.rel="stylesheet",n.onload=n.onreadystatechange=function(){C.loadFiles.css.push(e),t()},n.href=e+".css?editormd_version="+o,("head"===i?document.getElementsByTagName("head")[0]:document.body).appendChild(n)},C.isIE="Microsoft Internet Explorer"==navigator.appName,C.isIE8=C.isIE&&"8."==navigator.appVersion.match(/8./i),C.loadScript=function(e,t,i,o){i=i||"head",o=o||C.version,t=t||function(){};var n=null;(n=document.createElement("script")).id=e.replace(/[\./]+/g,"-"),n.type="text/javascript",n.src=e+".js?editormd_version="+o,C.isIE8?n.onreadystatechange=function(){!n.readyState||"loaded"!==n.readyState&&"complete"!==n.readyState||(n.onreadystatechange=null,C.loadFiles.js.push(e),t())}:n.onload=function(){C.loadFiles.js.push(e),t()},("head"===i?document.getElementsByTagName("head")[0]:document.body).appendChild(n)},C.katexURL={css:"https://cdn.bootcdn.net/ajax/libs/KaTeX/0.16.8/katex.min",jsmain:"https://cdn.bootcdn.net/ajax/libs/KaTeX/0.16.8/katex.min",jsauto:"https://cdn.bootcdn.net/ajax/libs/KaTeX/0.16.8/contrib/auto-render.min"},C.kaTeXLoaded=!1,C.loadKaTeX=function(e){C.loadCSS(C.katexURL.css,function(){C.loadScript(C.katexURL.jsmain,function(){C.loadScript(C.katexURL.jsauto,e||function(){})})})},C.lockScreen=function(e){v("html,body").css("overflow",e?"hidden":"")},C.createDialog=function(t){function e(){f.css({top:(v(window).height()-f.height())/2+"px",left:(v(window).width()-f.width())/2+"px"})}t=v.extend(!0,{name:"",width:420,height:"auto",title:"",drag:!0,closed:!0,content:"",mask:!0,maskStyle:{backgroundColor:"#fff",opacity:.1},removeDialogOnClose:!1,resizeableX:!1,resizeableY:!1,minWidth:0,minHeight:0,maxWidth:"none",maxHeight:"none",lockScreen:!0,footer:!0,buttons:!1},t);var r,a,i,s,o,n=this,l=this.editor,c=C.classPrefix,h=(new Date).getTime(),h=""===t.name?c+"dialog-"+h:t.name,d=C.mouseOrTouch,u='',f=(""!==t.title&&(u=(u+='"),u+='
'+t.content,!t.footer&&"string"!=typeof t.footer||(u+='"),l.append(u=(u=u+"
"+('
'))+('
')+"
"),l.find("."+h));f.lockScreen=function(e){return t.lockScreen&&(v("html,body").css("overflow",e?"hidden":""),n.resize()),f},f.showMask=function(){return t.mask&&l.find("."+c+"mask").css(t.maskStyle).css("z-index",C.dialogZindex-1).show(),f},f.hideMask=function(){return t.mask&&l.find("."+c+"mask").hide(),f},f.loading=function(e){return f.find("."+c+"dialog-mask")[e?"show":"hide"](),f},f.lockScreen(!0).showMask(),f.show().css({zIndex:C.dialogZindex,border:C.isIE8?"1px solid #ddd":"",width:"number"==typeof t.width?t.width+"px":t.width,height:"number"==typeof t.height?t.height+"px":t.height,"min-width":"number"==typeof t.minWidth?t.minWidth+"px":t.minWidth,"min-height":"number"==typeof t.minHeight?t.minHeight+"px":t.minHeight,"max-width":"number"==typeof t.maxWidth?t.maxWidth+"px":t.maxWidth,"max-height":"number"==typeof t.maxHeight?t.maxHeight+"px":t.maxHeight,...t.resizeableX||t.resizeableY?{overflow:"hidden",resize:t.resizeableX&&t.resizeableY?"both":t.resizeableX?"horizontal":"vertical"}:{}});if(e(),v(window).resize(e),f.find("."+c+"dialog-close").bind(d("click","touchend"),function(){f.hide().lockScreen(!1).hideMask(),t.removeDialogOnClose&&f.remove()}),"object"==typeof t.buttons){var p,g=f.footer=f.find("."+c+"dialog-footer");for(p in t.buttons){var m=t.buttons[p],w=c+p+"-btn";g.append(''+m[0]+" "),m[1]=v.proxy(m[1],f),g.children("."+w).bind(d("click","touchend"),m[1])}}return""!==t.title&&t.drag&&(u=f.children("."+c+"dialog-header"),t.mask||u.bind(d("click","touchend"),function(){C.dialogZindex+=2,f.css("z-index",C.dialogZindex)}),u.mousedown(function(e){e=e||window.event,r=e.clientX-parseInt(f[0].style.left),a=e.clientY-parseInt(f[0].style.top),document.onmousemove=o}),i=function(e){e.removeClass(c+"user-unselect").off("selectstart")},s=function(e){e.addClass(c+"user-unselect").on("selectstart",function(e){return!1})},o=function(e){e=e||window.event;var t,i,o=parseInt(f[0].style.left),n=parseInt(f[0].style.top);0<=o?o+f.width()<=v(window).width()?t=e.clientX-r:(t=v(window).width()-f.width(),document.onmousemove=null):(t=0,document.onmousemove=null),0<=n?i=e.clientY-a:(i=0,document.onmousemove=null),document.onselectstart=function(){return!1},s(v("body")),s(f),f[0].style.left=t+"px",f[0].style.top=i+"px"},document.onmouseup=function(){i(v("body")),i(f),document.onselectstart=null,document.onmousemove=null},u.touchDraggable=function(){var i=null;this.bind("touchstart",function(e){var e=e.originalEvent,t=v(this).parent().position();i={x:e.changedTouches[0].pageX-t.left,y:e.changedTouches[0].pageY-t.top}}).bind("touchmove",function(e){e.preventDefault();e=e.originalEvent;v(this).parent().css({top:e.changedTouches[0].pageY-i.y,left:e.changedTouches[0].pageX-i.x})})},u.touchDraggable()),C.dialogZindex+=2,f},C.mouseOrTouch=function(e,t){t=t||"touchend";e=e||"click";try{document.createEvent("TouchEvent"),e=t}catch(e){}return e},C.dateFormat=function(e){e=e||"";function t(e){return e<10?"0"+e:e}var i=new Date,o=i.getFullYear(),n=o.toString().slice(2,4),r=t(i.getMonth()+1),a=t(i.getDate()),s=i.getDay(),l=t(i.getHours()),c=t(i.getMinutes()),h=t(i.getSeconds()),d=t(i.getMilliseconds()),u="",f=n+"-"+r+"-"+a,p=o+"-"+r+"-"+a,g=l+":"+c+":"+h;switch(e){case"UNIX Time":u=i.getTime();break;case"UTC":u=i.toUTCString();break;case"yy":u=n;break;case"year":case"yyyy":u=o;break;case"month":case"mm":u=r;break;case"cn-week-day":case"cn-wd":u="星期"+["日","一","二","三","四","五","六"][s];break;case"week-day":case"wd":u=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"][s];break;case"day":case"dd":u=a;break;case"hour":case"hh":u=l;break;case"min":case"ii":u=c;break;case"second":case"ss":u=h;break;case"ms":u=d;break;case"yy-mm-dd":u=f;break;case"yyyy-mm-dd":u=p;break;case"yyyy-mm-dd h:i:s ms":case"full + ms":u=p+" "+g+" "+d;break;default:u=p+" "+g}return u},C.getLine=function(e){return this.cm.getLine(e)},C});
\ No newline at end of file
diff --git a/static/editor.md/languages/en.js b/static/editor.md/languages/en.js
index 91fab27ff..f50281832 100644
--- a/static/editor.md/languages/en.js
+++ b/static/editor.md/languages/en.js
@@ -5,8 +5,8 @@
description : "Open source online Markdown editor.",
tocTitle : "Table of Contents",
toolbar : {
- undo : "Undo(Ctrl+Z)",
- redo : "Redo(Ctrl+Y)",
+ undo : "Undo",
+ redo : "Redo",
bold : "Bold",
del : "Strikethrough",
italic : "Italic",
@@ -22,13 +22,13 @@
h6 : "Heading 6",
"list-ul" : "Unordered list",
"list-ol" : "Ordered list",
- hr : "Horizontal rule",
+ hr : "Horizontal line",
link : "Link",
"reference-link" : "Reference link",
image : "Image",
- code : "Code inline",
+ code : "Inline code",
"preformatted-text" : "Preformatted text / Code block (Tab indent)",
- "code-block" : "Code block (Multi-languages)",
+ "code-block" : "Code block",
table : "Tables",
datetime : "Datetime",
emoji : "Emoji",
@@ -76,16 +76,18 @@
formatNotAllowed : "Error: only allows to upload pictures file, upload allowed image file format:"
},
preformattedText : {
- title : "Preformatted text / Codes",
- emptyAlert : "Error: Please fill in the Preformatted text or content of the codes."
+ title : "Preformatted text / Codes",
+ emptyAlert : "Error: Please fill in the Preformatted text or content of the codes.",
+ placeholder : "coding now...."
},
codeBlock : {
- title : "Code block",
+ title : "Code block",
selectLabel : "Languages: ",
selectDefaultText : "select a code language...",
otherLanguage : "Other languages",
unselectedLanguageAlert : "Error: Please select the code language.",
- codeEmptyAlert : "Error: Please fill in the code content."
+ codeEmptyAlert : "Error: Please fill in the code content.",
+ placeholder : "coding now...."
},
htmlEntities : {
title : "HTML Entities"
@@ -95,13 +97,13 @@
}
}
};
-
+
exports.defaults.lang = lang;
};
-
+
// CommonJS/Node.js
if (typeof require === "function" && typeof exports === "object" && typeof module === "object")
- {
+ {
module.exports = factory;
}
else if (typeof define === "function") // AMD/CMD/Sea.js
@@ -118,10 +120,10 @@
factory(editormd);
});
}
- }
+ }
else
{
factory(window.editormd);
}
-
-})();
\ No newline at end of file
+
+})();
diff --git a/static/editor.md/languages/zh-tw.js b/static/editor.md/languages/zh-tw.js
index a7c0496ac..4bb83aed7 100644
--- a/static/editor.md/languages/zh-tw.js
+++ b/static/editor.md/languages/zh-tw.js
@@ -77,7 +77,8 @@
},
preformattedText : {
title : "添加預格式文本或代碼塊",
- emptyAlert : "錯誤:請填寫預格式文本或代碼的內容。"
+ emptyAlert : "錯誤:請填寫預格式文本或代碼的內容。",
+ placeholder : "coding now...."
},
codeBlock : {
title : "添加代碼塊",
@@ -85,7 +86,8 @@
selectDefaultText : "請語言代碼語言",
otherLanguage : "其他語言",
unselectedLanguageAlert : "錯誤:請選擇代碼所屬的語言類型。",
- codeEmptyAlert : "錯誤:請填寫代碼內容。"
+ codeEmptyAlert : "錯誤:請填寫代碼內容。",
+ placeholder : "coding now...."
},
htmlEntities : {
title : "HTML實體字符"
@@ -124,4 +126,4 @@
factory(window.editormd);
}
-})();
\ No newline at end of file
+})();
diff --git a/static/editor.md/lib/codemirror/AUTHORS b/static/editor.md/lib/codemirror/AUTHORS
index 9d62d48e6..bacc2b842 100644
--- a/static/editor.md/lib/codemirror/AUTHORS
+++ b/static/editor.md/lib/codemirror/AUTHORS
@@ -1,30 +1,51 @@
List of CodeMirror contributors. Updated before every release.
+4oo4
4r2r
Aaron Brooks
Abdelouahab
+Abdussalam Abdurrahman
Abe Fettig
+Abhishek Gahlot
Adam Ahmed
Adam King
+Adam Particka
+Adam Wight
adanlobato
Adán Lobato
+Aditya Toshniwal
Adrian Aichner
+Adrian Heine
+Adrian Kunz
+Adrien Bertrand
aeroson
Ahmad Amireh
Ahmad M. Zawawi
+AHOHNMYC
ahoward
+Ajin Abraham
Akeksandr Motsjonov
+Alasdair Smith
+AlbertHilb
Alberto González Palomo
Alberto Pose
Albert Xing
+Alexander Marks
Alexander Pavlov
Alexander Schepanovski
Alexander Shvets
Alexander Solovyov
Alexandre Bique
+Alex Churchill
alexey-k
Alex Piggott
+Alf Eaton
Aliaksei Chapyzhenka
+Allen Sarkisyan
+Ami Fischman
+Amin Shali
+Amin Ullah Khan
+amshali@google.com
Amsul
amuntean
Amy
@@ -33,62 +54,142 @@ anaran
AndersMad
Anders Nawroth
Anderson Mesquita
+Anders Wåglund
Andrea G
Andreas Reischuck
+Andres Taylor
Andre von Houck
+Andrew Cheng
+Andrew Dassonville
Andrey Fedorov
Andrey Klyuchnikov
Andrey Lushnikov
+Andrey Shchekin
Andy Joslin
Andy Kimball
Andy Li
+Angelo
angelozerr
angelo.zerr@gmail.com
Ankit
Ankit Ahuja
Ansel Santosa
+Anthony Dugois
+anthonygego
+Anthony Gégo
Anthony Grimes
+Anthony Stewart
Anton Kovalyov
+antosarho
+aoki ken
+Apollo Zhu
+AQNOUCH Mohammed
+Aram Shatakhtsyan
areos
+Arnab Bose
+Arnoud Buzing
+Arsène von Wyss
+Arthur Müller
+Arun Narasani
as3boyan
+asolove
+atelierbram
AtomicPages LLC
Atul Bhouraskar
Aurelian Oancea
+Axel Lewenhaupt
+Baptiste Augrain
+Barret Rennie
+Bartosz Dziewoński
+Basarat Ali Syed
Bastian Müller
+belhaj
Bem Jones-Bey
benbro
+Benedikt Meurer
+benhormann
+Ben Hormann
Beni Cherniavsky-Paskin
Benjamin DeCoste
+Benjamin Young
Ben Keen
+Ben Miller
+Ben Mosher
Bernhard Sirlinger
Bert Chang
+Bharad
+BigBlueHat
+Billiam
Billy Moon
+Bin Ni
binny
+Bjorn Hansen
B Krishna Chaitanya
Blaine G
blukat29
+Bo
boomyjee
+Bo Peng
borawjm
+Boris K
+Brad Metcalf
Brandon Frohs
Brandon Wamboldt
+Bret Little
Brett Zamir
Brian Grinstead
+BrianHung
Brian Sletten
+brrd
Bruce Mitchener
+Bruno Logerfo
+Bryan Gin-ge Chen
+Bryan Massoth
+Caitlin Potter
+Calin Barbat
+callodacity
+Camilo Roca
+Casey Klebba
+cBiscuit87
+César González Íñiguez
+Chad Jolly
Chandra Sekhar Pydi
Charles Skelton
Cheah Chu Yeow
+Chhekur
+Chris Colborne
Chris Coyier
+Chris Ford
Chris Granger
Chris Houseknecht
+Chris Lohfink
Chris Morgan
+Chris Reeves
+Chris Smith
+Christian Gruen
Christian Oyarzun
Christian Petrov
+Christian Sonne
+christopherblaser
Christopher Brown
+Christopher Kramer
+Christopher Mitchell
+Christopher Pfohl
+Christopher Wallis
+Chunliang Lyu
ciaranj
+clone-it
+clso
CodeAnimal
+CodeBitt
+coderaiser
+Cole R Lawrence
ComFreek
+Cornelius Weig
+Cristian Prieto
+Curran Kelleher
Curtis Gagliardi
+d8888
dagsta
daines
Dale Jung
@@ -97,38 +198,74 @@ Dan Heberden
Daniel, Dao Quang Minh
Daniele Di Sarli
Daniel Faust
+Daniel Hanggi
Daniel Huigens
+Daniel Kesler
Daniel KJ
Daniel Neel
Daniel Parnell
+Daniel Thwaites
+Danila Malyutin
Danny Yoo
darealshinji
Darius Roberts
+databricks-david-lewis
+Dave Brondsema
+Dave MacLachlan
Dave Myers
+David Barnett
+David H. Bronke
David Mignot
David Pathakjee
+David R. Myers
+David Rodrigues
+David Santana
David Vázquez
+David Whittington
deebugger
Deep Thought
+Denis Ovsienko
+Devin Abbott
Devon Carew
+Dick Choi
+Diego Fernandez
dignifiedquire
Dimage Sapelkin
+Dimitri Mitropoulos
+Dinindu D. Wanniarachchi
+dmaclach
Dmitry Kiselyov
domagoj412
Dominator008
Domizio Demichelis
+Doug Blank
Doug Wikle
Drew Bratcher
Drew Hintz
Drew Khoury
+Drini Cami
Dror BG
+Duncan Lilley
duralog
+dwelle
+Ealton
eborden
+edoroshenko
edsharp
ekhaled
+Elisée
+Elmar Peise
+elpnt
+Emmanuel Schanzer
Enam Mijbah Noor
Eric Allam
+Eric Bogard
+Erik Demaine
+Erik Welander
+erosman
eustas
+Evan Minsk
+Fabien Dubosson
Fabien O'Carroll
Fabio Zendhi Nagao
Faiza Alsaied
@@ -136,130 +273,281 @@ Fauntleroy
fbuchinger
feizhang365
Felipe Lalanne
+Felipe S. S. Schneider
Felix Raab
+ficristo
Filip Noetzel
+Filip Stollár
+Filype Pereira
+finalfantasia
flack
-ForbesLindesay
+Florian Felten
+Fons van der Plas
Forbes Lindesay
+ForbesLindesay
Ford_Lawnmower
Forrest Oliphant
+Franco Catena
+Frank Seifferth
Frank Wiegand
+fraxx001
+Fredrik Borg
+FUJI Goro (gfx)
Gabriel Gheorghian
Gabriel Horner
Gabriel Nahmias
galambalazs
+Gary Sheng
Gautam Mehta
+Gavin Douglas
gekkoe
+Geordie Hall
+George Stephanis
+geowarin
Gerard Braad
Gergely Hegykozi
+Germain Chazot
Giovanni Calò
+Glebov Boris
Glenn Jorde
Glenn Ruehle
+goldsmcb
Golevka
+Google LLC
Gordon Smith
Grant Skinner
greengiant
Gregory Koberger
+Grzegorz Mazur
+Guang Li
+Guan Gui
Guillaume Massé
Guillaume Massé
+guraga
Gustavo Rodrigues
Hakan Tunc
+Hanno Fellmann
Hans Engel
+Hanzhao Deng
+Haoran Yu
+Harald Schilly
Hardest
+Harshvardhan Gupta
+Hasan Delibaş
Hasan Karahan
+Heanes
+Hector Oswaldo Caballero
+Hein Htat
+Hélio
+Hendrik Erz
+Hendrik Wallbaum
+Henrik Haugbølle
Herculano Campos
+hidaiy
Hiroyuki Makino
hitsthings
Hocdoc
+Howard
+Howard Jing
+Hugues Malphettes
Ian Beck
+Ian Davies
Ian Dickinson
+Ian Henderson
+ianhi
+Ian Rose
Ian Wehrman
Ian Wetherbee
Ice White
ICHIKAWA, Yuji
+idleberg
+Igor Petruk
ilvalle
+Ilya Kharlamov
+Ilya Zverev
Ingo Richter
+Intervue
Irakli Gozalishvili
+iteriani
Ivan Kurnosov
+Ivoah
+Jack Douglas
Jacob Lee
+Jaimin
+Jake Peyser
+Jake Zimmerman
+Jakob Kummerow
Jakob Miland
+Jakub T. Jankiewicz
Jakub Vrana
Jakub Vrána
James Campos
+James Cockshull
+James Howard
James Thorne
Jamie Hill
+Jamie Morris
+Janice Leung
Jan Jongboom
jankeromnes
Jan Keromnes
Jan Odvarko
+Jan Schär
Jan T. Sott
+Jared Dean
Jared Forsyth
+Jared Jacobs
Jason
Jason Barnabe
Jason Grout
+Jason Heeris
Jason Johnston
Jason San Jose
Jason Siefken
+Jayaprabhakar
+Jay Contonio
Jaydeep Solanki
Jean Boussier
+Jeff Blaisdell
+Jeff Hanke
+Jeff Jenkins
jeffkenton
Jeff Pickhardt
jem (graphite)
Jeremy Parmenter
+Jim
+Jim Avery
+jkaplon
+JobJob
+jochenberger
Jochen Berger
+Joel Einbinder
+joelpinheiro
+Joe Predham
+joewalsh
Johan Ask
+Johannes
+John Chen
John Connor
+John-David Dalton
+John Engler
John Lees-Miller
+John Ryan
John Snelson
+johnspiegel
John Van Der Loo
+Jon Ander Peñalba
+Jonas Döbertin
+Jonas Helfer
+Jonathan Dierksen
+Jonathan Hart
Jonathan Malmaud
+Jonathan Rascher
+Jon Gacnik
jongalloway
Jon Malmaud
Jon Sangster
+Joo
Joost-Wim Boekesteijn
+José dBruxelles
Joseph Pecoraro
+Josh Barnes
+Josh Cohen
+Josh Soref
Joshua Newman
Josh Watzman
jots
+Joy Zhong
jsoojeon
+ju1ius
Juan Benavides Romero
Jucovschi Constantin
Juho Vuori
+Julien CROUZET
+Julien Rebetez
+Justin Andresen
Justin Hileman
jwallers@gmail.com
kaniga
+karevn
+Karol
+Kaushik Kulkarni
+Kayur Patel
+Kazuhisa Ishizaka
+Kazuhito Hokamura
+kcwiakala
+Kees de Kooter
+Keldan Chapman
+Kenan Christian Dimas
Ken Newman
+ken restivo
Ken Rockot
+Kevin Earls
+Kevin Kwok
+Kevin Muret
Kevin Sawicki
Kevin Ushey
+Kier Darby
+Kim-Anh Tran
Klaus Silveira
Koh Zi Han, Cliff
komakino
+Konstantin Chernenko
Konstantin Lopuhin
koops
+Kris Ciccarello
ks-ifware
kubelsmieci
+kvncp
KwanEsq
+Kyle Kelley
+KyleMcNutt
+LaKing
Lanfei
Lanny
+laobubu
Laszlo Vidacs
+leaf
leaf corcoran
+Lemmon
+Leo Baschy
Leonid Khachaturov
Leon Sorokin
Leonya Khachaturov
+lexer2086
Liam Newman
+Libo Cannici
+Lior Goldberg
+Lior Shub
+lishid
+LloydMilligan
LM
lochel
+Lonnie Abelbeck
+Lorenzo Simionato
Lorenzo Stoakes
+Louis Mauchet
+Luca Fabbri
+Lucas Buchala
Luciano Longo
+Luciano Santana
+Lu Fangjian
+Łukasz Wielgus
+Luke Browning
+Luke Granger-Brown
Luke Stagner
lynschinzer
+M1cha
+Madhura Jayaratne
Maksim Lin
Maksym Taran
Malay Majithia
+Manideep
Manuel Rego Casasnovas
Marat Dreizin
Marcel Gerber
+Marcelo Camargo
+Marc Espín
Marco Aurélio
Marco Munizaga
Marcus Bointon
@@ -267,18 +555,38 @@ Marek Rudnicki
Marijn Haverbeke
Mário Gonçalves
Mario Pietsch
+Mark Anderson
+Mark Boyes
+Mark Dalgleish
+Mark Hamstra
Mark Lentczner
Marko Bonaci
+Mark Peace
+Markus Bordihn
+Markus Olsson
Martin Balek
Martín Gaitán
Martin Hasoň
+Martin Hunt
+Martin Laine
+Martin Zagora
+Masahiro MATAYOSHI
Mason Malone
Mateusz Paprocki
Mathias Bynens
mats cronqvist
+Matt Diehl
+Matt Gaide
+Matthew Bauer
Matthew Beale
+Matthew Casperson
+matthewhayes
+Matthew Rathbone
+Matthew Suozzo
Matthias Bussonnier
Matthias BUSSONNIER
+Mattia Astorino
+Matt MacPherson
Matt McDonald
Matt Pass
Matt Sacks
@@ -286,151 +594,362 @@ mauricio
Maximilian Hils
Maxim Kraev
Max Kirsch
+Max Schaefer
+Max Wu
Max Xiantu
mbarkhau
+McBrainy
+mce2
+Mélanie Chauvel
+melpon
+meshuamam
Metatheos
Micah Dubinko
+Michael
+Michael Chirico
+Michael Goderbauer
+Michael Grey
+Michael Kaminsky
Michael Lehenbauer
+Michael Wadman
+Michael Walker
Michael Zhou
+Michal Čihař
+Michal Dorner
+Michal Kapiczynski
Mighty Guava
Miguel Castillo
mihailik
+Mika Andrianarijaona
Mike
+Mike Bostock
Mike Brevoort
Mike Diaz
Mike Ivanov
Mike Kadin
+Mike Kobit
+Milan Szekely
+MinJune Kim
MinRK
Miraculix87
misfo
+mkaminsky11
mloginov
+mlsad3
+Moritz Schubotz (physikerwelt)
Moritz Schwörer
+Moshe Wajnberg
mps
+ms
mtaran-google
+Mu-An ✌️ Chiou
+Mu-An Chiou
+Mykola Martynovets
+mzabuawala
Narciso Jaramillo
+nathanlesage
Nathan Williams
ndr
+Neil Anderson
+neon-dev
nerbert
+NetworkNode
nextrevision
ngn
nguillaumin
Ng Zhi An
Nicholas Bollweg
Nicholas Bollweg (Nick)
+NickKolok
+Nick Kreeger
Nick Small
+Nicolas Chevobbe
+Nicolas Kick
+Nicolò Ribaudo
Niels van Groningen
nightwing
Nikita Beloglazov
Nikita Vasilyev
+Nikolaj Kappler
Nikolay Kostov
nilp0inter
+Nils Knappmeier
+Nina Pypchenko
Nisarg Jhaveri
nlwillia
+noragrossman
Norman Rzepka
+Nouzbe
+Oleksandr Yakovenko
+Olivia Ytterbrink
+Ondřej Mirtes
+Opender Singh
+opl-
+Oreoluwa Onatemowo
+orionlee
+oscar.lofwenhamn
+Oskar Segersvärd
+ossdev
+overdodactyl
pablo
+pabloferz
+Pablo Zubieta
+paddya
Page
+paladox
Panupong Pasupat
paris
+Paris
+Paris Kasidiaris
Patil Arpith
+Patrick Kettner
Patrick Stoica
Patrick Strawderman
Paul Garvin
Paul Ivanov
+Paul Masson
+Paul Schmidt
+Pavel
Pavel Feldman
+Pavel Petržela
Pavel Strashkin
Paweł Bartkiewicz
peteguhl
+peter
Peter Flynn
peterkroon
Peter Kroon
+Peter László
+Phil DeJarnett
+Philipp A
+Philipp Markovics
+Philip Stadermann
+Pi Delport
+Pierre Gerold
+Pieter Ouwerkerk
+Piyush
+Pontus Melke
prasanthj
Prasanth J
+Prayag Verma
+prendota
+Prendota
+ps173
+Qiang Li
+quiddity-wp
Radek Piórkowski
Rahul
+Rahul Anand
+ramwin1
Randall Mason
Randy Burden
Randy Edmunds
+Randy Luecke
+Raphael Amorim
Rasmus Erik Voel Jensen
+Rasmus Schultz
+raymondf
+Raymond Hill
+ray ratchup
Ray Ratchup
+Remi Nyborg
+Renaud Durlin
+Reynold Xin
+Richard Denton
Richard van der Meer
Richard Z.H. Wang
+Rishi Goomar
+Robert Brignull
Robert Crossfield
+Robert Martin
Roberto Abdelkader Martínez Pérez
robertop23
+Roberto Vidal
Robert Plummer
+Roman Frolov
+Roman Janusz
+Rongjian Zhang
+Rrandom
+Rrrandom
+Ruslan Bekenev
Ruslan Osmanov
+rvalavicius
+Ryan Pangrle
+Ryan Petrello
Ryan Prior
+ryu-sato
sabaca
+Sachin Gupta
+Sam Lee
+Sam Rawlins
Samuel Ainsworth
+Sam Wilson
sandeepshetty
Sander AKA Redsandro
+Sander Verweij
santec
+Sarah McAlear and Wenlin Zhang
Sascha Peilicke
+Sasha Varlamov
+satamas
satchmorun
sathyamoorthi
+Saul Costa
+S. Chris Colbert
SCLINIC\jdecker
Scott Aikin
+Scott Feeney
Scott Goodhew
+Seb35
+Sebastian Ślepowroński
+Sebastian Wilzbach
Sebastian Zaha
+Seren D
+Sergey Goder
+Sergey Tselovalnikov
+Se-Won Kim
+Shane Liesegang
shaund
shaun gilchrist
Shawn A
+Shea Bunge
sheopory
+Shil S
Shiv Deepak
Shmuel Englard
Shubham Jain
+Siamak Mokhtari
+Siddhartha Gunti
silverwind
+Simon Edwards
+Simon Huber
+sinkuu
+Slava Rozhnev
snasa
soliton4
sonson
+Sorab Bisht
spastorelli
srajanpaliwal
Stanislav Oaserele
+stan-z
Stas Kobzar
+stasoid
Stefan Borsje
Steffen Beyer
+Steffen Bruchmann
+Steffen Kowalski
+Stephane Moore
+Stephen Lavelle
+Steve Champagne
+Steve Hoover
+Steven Yung
Steve O'Hara
+stockiNail
stoskov
+Stryder Crown
+Stu Kennedy
+Sungho Kim
+sverweij
Taha Jahangir
+takamori
+Tako Schotanus
Takuji Shimokawa
+Takuya Matsuyama
Tarmil
+T. Brandon Ashley
+TDaglis
+Teja
tel
+Tentone
tfjgeorge
Thaddee Tyl
+thanasis
TheHowl
+themrmax
+Thiemo Kreuz
think
+Thomas Brouard
Thomas Dvornik
+Thomas Kluyver
+thomasmaclean
Thomas Schmid
Tim Alby
Tim Baumann
+Tim Gates
+Tim Nguyen
Timothy Farrell
+Timothy Gu
Timothy Hatcher
+Tim van der Lippe
+Tobias Bertelsen
TobiasBg
+Todd Berman
+Todd Kennedy
+tokafew420
Tomas-A
Tomas Varaneckas
Tom Erik Støwer
+Tom Klancer
Tom MacWright
+Tom McLaughlin
Tony Jian
+tophf
+Torben Bundt
+Torgeir Thoresen
+totalamd
Travis Heppe
Triangle717
+Tristan Tarrant
+TSUYUSATO Kitsune
+Tugrul Elmas
twifkak
+Tyler Long
+Tyler Makaro
+Vadim Dyachenko
+Vadzim Ramanenka
+Vaibhav Sagar
+vamshi.revu
+VapidWorx
Vestimir Markov
vf
+Victor Bocharsky
Vincent Woo
Volker Mische
+vtripolitakis
+wdouglashall
+Weiyan Shao
wenli
+Wes Cossick
Wesley Wiser
+Weston Ruter
Will Binns-Smith
+Will Cassella
+Will Dean
+Will Hernandez
+William Desportes
William Jamieson
William Stein
Willy
Wojtek Ptak
+wonderboyjon
+Wu Cheng-Han
Xavier Mendez
+Yang Guo
+Yash Singh
+Yash-Singh1
Yassin N. Hassan
YNH Webdev
+yoongu
Yunchi Luo
Yuvi Panda
+Yvonnick Esnault
+Zac Anger
Zachary Dremann
+ZeeshanNoor
+Zeno Rocha
Zhang Hao
+Ziv
+zoobestik
zziuni
魏鹏刚
diff --git a/static/editor.md/lib/codemirror/LICENSE b/static/editor.md/lib/codemirror/LICENSE
index d21bbea5a..ff7db4b99 100644
--- a/static/editor.md/lib/codemirror/LICENSE
+++ b/static/editor.md/lib/codemirror/LICENSE
@@ -1,4 +1,6 @@
-Copyright (C) 2014 by Marijn Haverbeke and others
+MIT License
+
+Copyright (C) 2017 by Marijn Haverbeke and others
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/static/editor.md/lib/codemirror/README.md b/static/editor.md/lib/codemirror/README.md
index bc6e7f5c7..39c24686b 100644
--- a/static/editor.md/lib/codemirror/README.md
+++ b/static/editor.md/lib/codemirror/README.md
@@ -1,12 +1,47 @@
# CodeMirror
-[](https://travis-ci.org/codemirror/CodeMirror)
-[](https://www.npmjs.org/package/codemirror)
-[Funding status: ](https://marijnhaverbeke.nl/fund/)
-CodeMirror is a JavaScript component that provides a code editor in
-the browser. When a mode is available for the language you are coding
-in, it will color your code, and optionally help with indentation.
+[](https://github.com/codemirror/codemirror/actions)
+[](https://www.npmjs.org/package/codemirror)
-The project page is http://codemirror.net
-The manual is at http://codemirror.net/doc/manual.html
-The contributing guidelines are in [CONTRIBUTING.md](https://github.com/codemirror/CodeMirror/blob/master/CONTRIBUTING.md)
+CodeMirror is a versatile text editor implemented in JavaScript for
+the browser. It is specialized for editing code, and comes with over
+100 language modes and various addons that implement more advanced
+editing functionality. Every language comes with fully-featured code
+and syntax highlighting to help with reading and editing complex code.
+
+A rich programming API and a CSS theming system are available for
+customizing CodeMirror to fit your application, and extending it with
+new functionality.
+
+You can find more information (and the
+[manual](https://codemirror.net/doc/manual.html)) on the [project
+page](https://codemirror.net). For questions and discussion, use the
+[discussion forum](https://discuss.codemirror.net/).
+
+See
+[CONTRIBUTING.md](https://github.com/codemirror/CodeMirror/blob/master/CONTRIBUTING.md)
+for contributing guidelines.
+
+The CodeMirror community aims to be welcoming to everybody. We use the
+[Contributor Covenant
+(1.1)](http://contributor-covenant.org/version/1/1/0/) as our code of
+conduct.
+
+### Installation
+
+Either get the [zip file](https://codemirror.net/codemirror.zip) with
+the latest version, or make sure you have [Node](https://nodejs.org/)
+installed and run:
+
+ npm install codemirror
+
+**NOTE**: This is the source repository for the library, and not the
+distribution channel. Cloning it is not the recommended way to install
+the library, and will in fact not work unless you also run the build
+step.
+
+### Quickstart
+
+To build the project, make sure you have Node.js installed (at least version 6)
+and then `npm install`. To run, just open `index.html` in your
+browser (you don't need to run a webserver). Run the tests with `npm test`.
diff --git a/static/editor.md/lib/codemirror/addon/comment/comment.js b/static/editor.md/lib/codemirror/addon/comment/comment.js
index 2dd114d33..dac48d038 100644
--- a/static/editor.md/lib/codemirror/addon/comment/comment.js
+++ b/static/editor.md/lib/codemirror/addon/comment/comment.js
@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
@@ -13,7 +13,7 @@
var noOptions = {};
var nonWS = /[^\s\u00a0]/;
- var Pos = CodeMirror.Pos;
+ var Pos = CodeMirror.Pos, cmp = CodeMirror.cmpPos;
function firstNonWS(str) {
var found = str.search(nonWS);
@@ -21,26 +21,45 @@
}
CodeMirror.commands.toggleComment = function(cm) {
- var minLine = Infinity, ranges = cm.listSelections(), mode = null;
+ cm.toggleComment();
+ };
+
+ CodeMirror.defineExtension("toggleComment", function(options) {
+ if (!options) options = noOptions;
+ var cm = this;
+ var minLine = Infinity, ranges = this.listSelections(), mode = null;
for (var i = ranges.length - 1; i >= 0; i--) {
var from = ranges[i].from(), to = ranges[i].to();
if (from.line >= minLine) continue;
if (to.line >= minLine) to = Pos(minLine, 0);
minLine = from.line;
if (mode == null) {
- if (cm.uncomment(from, to)) mode = "un";
- else { cm.lineComment(from, to); mode = "line"; }
+ if (cm.uncomment(from, to, options)) mode = "un";
+ else { cm.lineComment(from, to, options); mode = "line"; }
} else if (mode == "un") {
- cm.uncomment(from, to);
+ cm.uncomment(from, to, options);
} else {
- cm.lineComment(from, to);
+ cm.lineComment(from, to, options);
}
}
- };
+ });
+
+ // Rough heuristic to try and detect lines that are part of multi-line string
+ function probablyInsideString(cm, pos, line) {
+ return /\bstring\b/.test(cm.getTokenTypeAt(Pos(pos.line, 0))) && !/^[\'\"\`]/.test(line)
+ }
+
+ function getMode(cm, pos) {
+ var mode = cm.getMode()
+ return mode.useInnerComments === false || !mode.innerMode ? mode : cm.getModeAt(pos)
+ }
CodeMirror.defineExtension("lineComment", function(from, to, options) {
if (!options) options = noOptions;
- var self = this, mode = self.getModeAt(from);
+ var self = this, mode = getMode(self, from);
+ var firstLine = self.getLine(from.line);
+ if (firstLine == null || probablyInsideString(self, from, firstLine)) return;
+
var commentString = options.lineComment || mode.lineComment;
if (!commentString) {
if (options.blockCommentStart || mode.blockCommentStart) {
@@ -49,15 +68,21 @@
}
return;
}
- var firstLine = self.getLine(from.line);
- if (firstLine == null) return;
+
var end = Math.min(to.ch != 0 || to.line == from.line ? to.line + 1 : to.line, self.lastLine() + 1);
var pad = options.padding == null ? " " : options.padding;
var blankLines = options.commentBlankLines || from.line == to.line;
self.operation(function() {
if (options.indent) {
- var baseString = firstLine.slice(0, firstNonWS(firstLine));
+ var baseString = null;
+ for (var i = from.line; i < end; ++i) {
+ var line = self.getLine(i);
+ var whitespace = line.slice(0, firstNonWS(line));
+ if (baseString == null || baseString.length > whitespace.length) {
+ baseString = whitespace;
+ }
+ }
for (var i = from.line; i < end; ++i) {
var line = self.getLine(i), cut = baseString.length;
if (!blankLines && !nonWS.test(line)) continue;
@@ -75,7 +100,7 @@
CodeMirror.defineExtension("blockComment", function(from, to, options) {
if (!options) options = noOptions;
- var self = this, mode = self.getModeAt(from);
+ var self = this, mode = getMode(self, from);
var startString = options.blockCommentStart || mode.blockCommentStart;
var endString = options.blockCommentEnd || mode.blockCommentEnd;
if (!startString || !endString) {
@@ -83,6 +108,7 @@
self.lineComment(from, to, options);
return;
}
+ if (/\bcomment\b/.test(self.getTokenTypeAt(Pos(from.line, 0)))) return
var end = Math.min(to.line, self.lastLine());
if (end != from.line && to.ch == 0 && nonWS.test(self.getLine(end))) --end;
@@ -100,7 +126,9 @@
if (i != end || lastLineHasText)
self.replaceRange(lead + pad, Pos(i, 0));
} else {
+ var atCursor = cmp(self.getCursor("to"), to) == 0, empty = !self.somethingSelected()
self.replaceRange(endString, to);
+ if (atCursor) self.setSelection(empty ? to : self.getCursor("from"), to)
self.replaceRange(startString, from);
}
});
@@ -108,7 +136,7 @@
CodeMirror.defineExtension("uncomment", function(from, to, options) {
if (!options) options = noOptions;
- var self = this, mode = self.getModeAt(from);
+ var self = this, mode = getMode(self, from);
var end = Math.min(to.ch != 0 || to.line == from.line ? to.line : to.line - 1, self.lastLine()), start = Math.min(from.line, end);
// Try finding line comments
@@ -120,7 +148,7 @@
var line = self.getLine(i);
var found = line.indexOf(lineString);
if (found > -1 && !/comment/.test(self.getTokenTypeAt(Pos(i, found + 1)))) found = -1;
- if (found == -1 && (i != end || i == start) && nonWS.test(line)) break lineComment;
+ if (found == -1 && nonWS.test(line)) break lineComment;
if (found > -1 && nonWS.test(line.slice(0, found))) break lineComment;
lines.push(line);
}
@@ -142,15 +170,15 @@
var endString = options.blockCommentEnd || mode.blockCommentEnd;
if (!startString || !endString) return false;
var lead = options.blockCommentLead || mode.blockCommentLead;
- var startLine = self.getLine(start), endLine = end == start ? startLine : self.getLine(end);
- var open = startLine.indexOf(startString), close = endLine.lastIndexOf(endString);
- if (close == -1 && start != end) {
- endLine = self.getLine(--end);
- close = endLine.lastIndexOf(endString);
- }
- if (open == -1 || close == -1 ||
- !/comment/.test(self.getTokenTypeAt(Pos(start, open + 1))) ||
- !/comment/.test(self.getTokenTypeAt(Pos(end, close + 1))))
+ var startLine = self.getLine(start), open = startLine.indexOf(startString)
+ if (open == -1) return false
+ var endLine = end == start ? startLine : self.getLine(end)
+ var close = endLine.indexOf(endString, end == start ? open + startString.length : 0);
+ var insideStart = Pos(start, open + 1), insideEnd = Pos(end, close + 1)
+ if (close == -1 ||
+ !/comment/.test(self.getTokenTypeAt(insideStart)) ||
+ !/comment/.test(self.getTokenTypeAt(insideEnd)) ||
+ self.getRange(insideStart, insideEnd, "\n").indexOf(endString) > -1)
return false;
// Avoid killing block comments completely outside the selection.
diff --git a/static/editor.md/lib/codemirror/addon/comment/continuecomment.js b/static/editor.md/lib/codemirror/addon/comment/continuecomment.js
index b11d51e6c..7ca1b4a9c 100644
--- a/static/editor.md/lib/codemirror/addon/comment/continuecomment.js
+++ b/static/editor.md/lib/codemirror/addon/comment/continuecomment.js
@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
@@ -9,46 +9,69 @@
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
- var modes = ["clike", "css", "javascript"];
-
- for (var i = 0; i < modes.length; ++i)
- CodeMirror.extendMode(modes[i], {blockCommentContinue: " * "});
-
+ var nonspace = /\S/g;
+ var repeat = String.prototype.repeat || function (n) { return Array(n + 1).join(this); };
function continueComment(cm) {
if (cm.getOption("disableInput")) return CodeMirror.Pass;
var ranges = cm.listSelections(), mode, inserts = [];
for (var i = 0; i < ranges.length; i++) {
- var pos = ranges[i].head, token = cm.getTokenAt(pos);
- if (token.type != "comment") return CodeMirror.Pass;
- var modeHere = CodeMirror.innerMode(cm.getMode(), token.state).mode;
+ var pos = ranges[i].head
+ if (!/\bcomment\b/.test(cm.getTokenTypeAt(pos))) return CodeMirror.Pass;
+ var modeHere = cm.getModeAt(pos)
if (!mode) mode = modeHere;
else if (mode != modeHere) return CodeMirror.Pass;
- var insert = null;
- if (mode.blockCommentStart && mode.blockCommentContinue) {
- var end = token.string.indexOf(mode.blockCommentEnd);
- var full = cm.getRange(CodeMirror.Pos(pos.line, 0), CodeMirror.Pos(pos.line, token.end)), found;
- if (end != -1 && end == token.string.length - mode.blockCommentEnd.length && pos.ch >= end) {
- // Comment ended, don't continue it
- } else if (token.string.indexOf(mode.blockCommentStart) == 0) {
- insert = full.slice(0, token.start);
- if (!/^\s*$/.test(insert)) {
- insert = "";
- for (var j = 0; j < token.start; ++j) insert += " ";
+ var insert = null, line, found;
+ var blockStart = mode.blockCommentStart, lineCmt = mode.lineComment;
+ if (blockStart && mode.blockCommentContinue) {
+ line = cm.getLine(pos.line);
+ var end = line.lastIndexOf(mode.blockCommentEnd, pos.ch - mode.blockCommentEnd.length);
+ // 1. if this block comment ended
+ // 2. if this is actually inside a line comment
+ if (end != -1 && end == pos.ch - mode.blockCommentEnd.length ||
+ lineCmt && (found = line.lastIndexOf(lineCmt, pos.ch - 1)) > -1 &&
+ /\bcomment\b/.test(cm.getTokenTypeAt({line: pos.line, ch: found + 1}))) {
+ // ...then don't continue it
+ } else if (pos.ch >= blockStart.length &&
+ (found = line.lastIndexOf(blockStart, pos.ch - blockStart.length)) > -1 &&
+ found > end) {
+ // reuse the existing leading spaces/tabs/mixed
+ // or build the correct indent using CM's tab/indent options
+ if (nonspaceAfter(0, line) >= found) {
+ insert = line.slice(0, found);
+ } else {
+ var tabSize = cm.options.tabSize, numTabs;
+ found = CodeMirror.countColumn(line, found, tabSize);
+ insert = !cm.options.indentWithTabs ? repeat.call(" ", found) :
+ repeat.call("\t", (numTabs = Math.floor(found / tabSize))) +
+ repeat.call(" ", found - tabSize * numTabs);
}
- } else if ((found = full.indexOf(mode.blockCommentContinue)) != -1 &&
- found + mode.blockCommentContinue.length > token.start &&
- /^\s*$/.test(full.slice(0, found))) {
- insert = full.slice(0, found);
+ } else if ((found = line.indexOf(mode.blockCommentContinue)) > -1 &&
+ found <= pos.ch &&
+ found <= nonspaceAfter(0, line)) {
+ insert = line.slice(0, found);
}
- if (insert != null) insert += mode.blockCommentContinue;
+ if (insert != null) insert += mode.blockCommentContinue
}
- if (insert == null && mode.lineComment && continueLineCommentEnabled(cm)) {
- var line = cm.getLine(pos.line), found = line.indexOf(mode.lineComment);
- if (found > -1) {
- insert = line.slice(0, found);
- if (/\S/.test(insert)) insert = null;
- else insert += mode.lineComment + line.slice(found + mode.lineComment.length).match(/^\s*/)[0];
+ if (insert == null && lineCmt && continueLineCommentEnabled(cm)) {
+ if (line == null) line = cm.getLine(pos.line);
+ found = line.indexOf(lineCmt);
+ // cursor at pos 0, line comment also at pos 0 => shift it down, don't continue
+ if (!pos.ch && !found) insert = "";
+ // continue only if the line starts with an optional space + line comment
+ else if (found > -1 && nonspaceAfter(0, line) >= found) {
+ // don't continue if there's only space(s) after cursor or the end of the line
+ insert = nonspaceAfter(pos.ch, line) > -1;
+ // but always continue if the next line starts with a line comment too
+ if (!insert) {
+ var next = cm.getLine(pos.line + 1) || '',
+ nextFound = next.indexOf(lineCmt);
+ insert = nextFound > -1 && nonspaceAfter(0, next) >= nextFound || null;
+ }
+ if (insert) {
+ insert = line.slice(0, found) + lineCmt +
+ line.slice(found + lineCmt.length).match(/^\s*/)[0];
+ }
}
}
if (insert == null) return CodeMirror.Pass;
@@ -61,6 +84,12 @@
});
}
+ function nonspaceAfter(ch, str) {
+ nonspace.lastIndex = ch;
+ var m = nonspace.exec(str);
+ return m ? m.index : -1;
+ }
+
function continueLineCommentEnabled(cm) {
var opt = cm.getOption("continueComments");
if (opt && typeof opt == "object")
diff --git a/static/editor.md/lib/codemirror/addon/dialog/dialog.css b/static/editor.md/lib/codemirror/addon/dialog/dialog.css
index 2e7c0fc9b..677c07838 100644
--- a/static/editor.md/lib/codemirror/addon/dialog/dialog.css
+++ b/static/editor.md/lib/codemirror/addon/dialog/dialog.css
@@ -1,11 +1,11 @@
.CodeMirror-dialog {
position: absolute;
left: 0; right: 0;
- background: white;
+ background: inherit;
z-index: 15;
padding: .1em .8em;
overflow: hidden;
- color: #333;
+ color: inherit;
}
.CodeMirror-dialog-top {
diff --git a/static/editor.md/lib/codemirror/addon/dialog/dialog.js b/static/editor.md/lib/codemirror/addon/dialog/dialog.js
index e0e8ad4eb..5f1f4aa4c 100644
--- a/static/editor.md/lib/codemirror/addon/dialog/dialog.js
+++ b/static/editor.md/lib/codemirror/addon/dialog/dialog.js
@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
// Open simple dialogs on top of an editor. Relies on dialog.css.
@@ -25,6 +25,7 @@
} else { // Assuming it's a detached DOM element.
dialog.appendChild(template);
}
+ CodeMirror.addClass(wrap, 'dialog-opened');
return dialog;
}
@@ -47,6 +48,7 @@
} else {
if (closed) return;
closed = true;
+ CodeMirror.rmClass(dialog.parentNode, 'dialog-opened');
dialog.parentNode.removeChild(dialog);
me.focus();
@@ -56,9 +58,13 @@
var inp = dialog.getElementsByTagName("input")[0], button;
if (inp) {
+ inp.focus();
+
if (options.value) {
inp.value = options.value;
- inp.select();
+ if (options.selectValueOnOpen !== false) {
+ inp.select();
+ }
}
if (options.onInput)
@@ -76,9 +82,9 @@
if (e.keyCode == 13) callback(inp.value, e);
});
- if (options.closeOnBlur !== false) CodeMirror.on(inp, "blur", close);
-
- inp.focus();
+ if (options.closeOnBlur !== false) CodeMirror.on(dialog, "focusout", function (evt) {
+ if (evt.relatedTarget !== null) close();
+ });
} else if (button = dialog.getElementsByTagName("button")[0]) {
CodeMirror.on(button, "click", function() {
close();
@@ -100,6 +106,7 @@
function close() {
if (closed) return;
closed = true;
+ CodeMirror.rmClass(dialog.parentNode, 'dialog-opened');
dialog.parentNode.removeChild(dialog);
me.focus();
}
@@ -139,6 +146,7 @@
if (closed) return;
closed = true;
clearTimeout(doneTimer);
+ CodeMirror.rmClass(dialog.parentNode, 'dialog-opened');
dialog.parentNode.removeChild(dialog);
}
diff --git a/static/editor.md/lib/codemirror/addon/display/autorefresh.js b/static/editor.md/lib/codemirror/addon/display/autorefresh.js
new file mode 100644
index 000000000..37014dc31
--- /dev/null
+++ b/static/editor.md/lib/codemirror/addon/display/autorefresh.js
@@ -0,0 +1,47 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: https://codemirror.net/LICENSE
+
+(function(mod) {
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
+ mod(require("../../lib/codemirror"))
+ else if (typeof define == "function" && define.amd) // AMD
+ define(["../../lib/codemirror"], mod)
+ else // Plain browser env
+ mod(CodeMirror)
+})(function(CodeMirror) {
+ "use strict"
+
+ CodeMirror.defineOption("autoRefresh", false, function(cm, val) {
+ if (cm.state.autoRefresh) {
+ stopListening(cm, cm.state.autoRefresh)
+ cm.state.autoRefresh = null
+ }
+ if (val && cm.display.wrapper.offsetHeight == 0)
+ startListening(cm, cm.state.autoRefresh = {delay: val.delay || 250})
+ })
+
+ function startListening(cm, state) {
+ function check() {
+ if (cm.display.wrapper.offsetHeight) {
+ stopListening(cm, state)
+ if (cm.display.lastWrapHeight != cm.display.wrapper.clientHeight)
+ cm.refresh()
+ } else {
+ state.timeout = setTimeout(check, state.delay)
+ }
+ }
+ state.timeout = setTimeout(check, state.delay)
+ state.hurry = function() {
+ clearTimeout(state.timeout)
+ state.timeout = setTimeout(check, 50)
+ }
+ CodeMirror.on(window, "mouseup", state.hurry)
+ CodeMirror.on(window, "keyup", state.hurry)
+ }
+
+ function stopListening(_cm, state) {
+ clearTimeout(state.timeout)
+ CodeMirror.off(window, "mouseup", state.hurry)
+ CodeMirror.off(window, "keyup", state.hurry)
+ }
+});
diff --git a/static/editor.md/lib/codemirror/addon/display/fullscreen.js b/static/editor.md/lib/codemirror/addon/display/fullscreen.js
index cd3673b96..eda7300f1 100644
--- a/static/editor.md/lib/codemirror/addon/display/fullscreen.js
+++ b/static/editor.md/lib/codemirror/addon/display/fullscreen.js
@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
diff --git a/static/editor.md/lib/codemirror/addon/display/panel.js b/static/editor.md/lib/codemirror/addon/display/panel.js
index 22c0453e8..29f7e0beb 100644
--- a/static/editor.md/lib/codemirror/addon/display/panel.js
+++ b/static/editor.md/lib/codemirror/addon/display/panel.js
@@ -1,26 +1,51 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
-(function(mod) {
+(function (mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
-})(function(CodeMirror) {
- CodeMirror.defineExtension("addPanel", function(node, options) {
+})(function (CodeMirror) {
+ CodeMirror.defineExtension("addPanel", function (node, options) {
+ options = options || {};
+
if (!this.state.panels) initPanels(this);
var info = this.state.panels;
- if (options && options.position == "bottom")
- info.wrapper.appendChild(node);
- else
- info.wrapper.insertBefore(node, info.wrapper.firstChild);
+ var wrapper = info.wrapper;
+ var cmWrapper = this.getWrapperElement();
+ var replace = options.replace instanceof Panel && !options.replace.cleared;
+
+ if (options.after instanceof Panel && !options.after.cleared) {
+ wrapper.insertBefore(node, options.before.node.nextSibling);
+ } else if (options.before instanceof Panel && !options.before.cleared) {
+ wrapper.insertBefore(node, options.before.node);
+ } else if (replace) {
+ wrapper.insertBefore(node, options.replace.node);
+ options.replace.clear(true);
+ } else if (options.position == "bottom") {
+ wrapper.appendChild(node);
+ } else if (options.position == "before-bottom") {
+ wrapper.insertBefore(node, cmWrapper.nextSibling);
+ } else if (options.position == "after-top") {
+ wrapper.insertBefore(node, cmWrapper);
+ } else {
+ wrapper.insertBefore(node, wrapper.firstChild);
+ }
+
var height = (options && options.height) || node.offsetHeight;
- this._setSize(null, info.heightLeft -= height);
- info.panels++;
- return new Panel(this, node, options, height);
+
+ var panel = new Panel(this, node, options, height);
+ info.panels.push(panel);
+
+ this.setSize();
+ if (options.stable && isAtTop(this, node))
+ this.scrollTo(null, this.getScrollInfo().top + height);
+
+ return panel;
});
function Panel(cm, node, options, height) {
@@ -31,40 +56,43 @@
this.cleared = false;
}
- Panel.prototype.clear = function() {
+ /* when skipRemove is true, clear() was called from addPanel().
+ * Thus removePanels() should not be called (issue 5518) */
+ Panel.prototype.clear = function (skipRemove) {
if (this.cleared) return;
this.cleared = true;
var info = this.cm.state.panels;
- this.cm._setSize(null, info.heightLeft += this.height);
+ info.panels.splice(info.panels.indexOf(this), 1);
+ this.cm.setSize();
+ if (this.options.stable && isAtTop(this.cm, this.node))
+ this.cm.scrollTo(null, this.cm.getScrollInfo().top - this.height)
info.wrapper.removeChild(this.node);
- if (--info.panels == 0) removePanels(this.cm);
+ if (info.panels.length == 0 && !skipRemove) removePanels(this.cm);
};
- Panel.prototype.changed = function(height) {
- var newHeight = height == null ? this.node.offsetHeight : height;
- var info = this.cm.state.panels;
- this.cm._setSize(null, info.height += (newHeight - this.height));
- this.height = newHeight;
+ Panel.prototype.changed = function () {
+ this.height = this.node.getBoundingClientRect().height;
+ this.cm.setSize();
};
function initPanels(cm) {
- var wrap = cm.getWrapperElement();
+ var wrap = cm.getWrapperElement()
var style = window.getComputedStyle ? window.getComputedStyle(wrap) : wrap.currentStyle;
var height = parseInt(style.height);
var info = cm.state.panels = {
setHeight: wrap.style.height,
- heightLeft: height,
- panels: 0,
+ panels: [],
wrapper: document.createElement("div")
};
+ var hasFocus = cm.hasFocus(), scrollPos = cm.getScrollInfo()
wrap.parentNode.insertBefore(info.wrapper, wrap);
- var hasFocus = cm.hasFocus();
info.wrapper.appendChild(wrap);
+ cm.scrollTo(scrollPos.left, scrollPos.top)
if (hasFocus) cm.focus();
cm._setSize = cm.setSize;
- if (height != null) cm.setSize = function(width, newHeight) {
- if (newHeight == null) return this._setSize(width, newHeight);
+ if (height != null) cm.setSize = function (width, newHeight) {
+ if (!newHeight) newHeight = info.wrapper.offsetHeight;
info.setHeight = newHeight;
if (typeof newHeight != "number") {
var px = /^(\d+\.?\d*)px$/.exec(newHeight);
@@ -73,10 +101,12 @@
} else {
info.wrapper.style.height = newHeight;
newHeight = info.wrapper.offsetHeight;
- info.wrapper.style.height = "";
}
}
- cm._setSize(width, info.heightLeft += (newHeight - height));
+ var editorheight = newHeight - info.panels
+ .map(function (p) { return p.node.getBoundingClientRect().height; })
+ .reduce(function (a, b) { return a + b; }, 0);
+ cm._setSize(width, editorheight);
height = newHeight;
};
}
@@ -85,10 +115,19 @@
var info = cm.state.panels;
cm.state.panels = null;
- var wrap = cm.getWrapperElement();
+ var wrap = cm.getWrapperElement()
+ var hasFocus = cm.hasFocus(), scrollPos = cm.getScrollInfo()
info.wrapper.parentNode.replaceChild(wrap, info.wrapper);
+ cm.scrollTo(scrollPos.left, scrollPos.top)
+ if (hasFocus) cm.focus();
wrap.style.height = info.setHeight;
cm.setSize = cm._setSize;
cm.setSize();
}
+
+ function isAtTop(cm, dom) {
+ for (var sibling = dom.nextSibling; sibling; sibling = sibling.nextSibling)
+ if (sibling == cm.getWrapperElement()) return true
+ return false
+ }
});
diff --git a/static/editor.md/lib/codemirror/addon/display/placeholder.js b/static/editor.md/lib/codemirror/addon/display/placeholder.js
index bb0c3931e..cfb8341db 100644
--- a/static/editor.md/lib/codemirror/addon/display/placeholder.js
+++ b/static/editor.md/lib/codemirror/addon/display/placeholder.js
@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
@@ -14,10 +14,14 @@
if (val && !prev) {
cm.on("blur", onBlur);
cm.on("change", onChange);
+ cm.on("swapDoc", onChange);
+ CodeMirror.on(cm.getInputField(), "compositionupdate", cm.state.placeholderCompose = function() { onComposition(cm) })
onChange(cm);
} else if (!val && prev) {
cm.off("blur", onBlur);
cm.off("change", onChange);
+ cm.off("swapDoc", onChange);
+ CodeMirror.off(cm.getInputField(), "compositionupdate", cm.state.placeholderCompose)
clearPlaceholder(cm);
var wrapper = cm.getWrapperElement();
wrapper.className = wrapper.className.replace(" CodeMirror-empty", "");
@@ -36,11 +40,27 @@
clearPlaceholder(cm);
var elt = cm.state.placeholder = document.createElement("pre");
elt.style.cssText = "height: 0; overflow: visible";
- elt.className = "CodeMirror-placeholder";
- elt.appendChild(document.createTextNode(cm.getOption("placeholder")));
+ elt.style.direction = cm.getOption("direction");
+ elt.className = "CodeMirror-placeholder CodeMirror-line-like";
+ var placeHolder = cm.getOption("placeholder")
+ if (typeof placeHolder == "string") placeHolder = document.createTextNode(placeHolder)
+ elt.appendChild(placeHolder)
cm.display.lineSpace.insertBefore(elt, cm.display.lineSpace.firstChild);
}
+ function onComposition(cm) {
+ setTimeout(function() {
+ var empty = false
+ if (cm.lineCount() == 1) {
+ var input = cm.getInputField()
+ empty = input.nodeName == "TEXTAREA" ? !cm.getLine(0).length
+ : !/[^\u200b]/.test(input.querySelector(".CodeMirror-line").textContent)
+ }
+ if (empty) setPlaceholder(cm)
+ else clearPlaceholder(cm)
+ }, 20)
+ }
+
function onBlur(cm) {
if (isEmpty(cm)) setPlaceholder(cm);
}
diff --git a/static/editor.md/lib/codemirror/addon/display/rulers.js b/static/editor.md/lib/codemirror/addon/display/rulers.js
index 13185d30b..0bb83bb02 100644
--- a/static/editor.md/lib/codemirror/addon/display/rulers.js
+++ b/static/editor.md/lib/codemirror/addon/display/rulers.js
@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
@@ -11,34 +11,30 @@
})(function(CodeMirror) {
"use strict";
- CodeMirror.defineOption("rulers", false, function(cm, val, old) {
- if (old && old != CodeMirror.Init) {
- clearRulers(cm);
- cm.off("refresh", refreshRulers);
+ CodeMirror.defineOption("rulers", false, function(cm, val) {
+ if (cm.state.rulerDiv) {
+ cm.state.rulerDiv.parentElement.removeChild(cm.state.rulerDiv)
+ cm.state.rulerDiv = null
+ cm.off("refresh", drawRulers)
}
if (val && val.length) {
- setRulers(cm);
- cm.on("refresh", refreshRulers);
+ cm.state.rulerDiv = cm.display.lineSpace.parentElement.insertBefore(document.createElement("div"), cm.display.lineSpace)
+ cm.state.rulerDiv.className = "CodeMirror-rulers"
+ drawRulers(cm)
+ cm.on("refresh", drawRulers)
}
});
- function clearRulers(cm) {
- for (var i = cm.display.lineSpace.childNodes.length - 1; i >= 0; i--) {
- var node = cm.display.lineSpace.childNodes[i];
- if (/(^|\s)CodeMirror-ruler($|\s)/.test(node.className))
- node.parentNode.removeChild(node);
- }
- }
-
- function setRulers(cm) {
+ function drawRulers(cm) {
+ cm.state.rulerDiv.textContent = ""
var val = cm.getOption("rulers");
var cw = cm.defaultCharWidth();
var left = cm.charCoords(CodeMirror.Pos(cm.firstLine(), 0), "div").left;
- var minH = cm.display.scroller.offsetHeight + 30;
+ cm.state.rulerDiv.style.minHeight = (cm.display.scroller.offsetHeight + 30) + "px";
for (var i = 0; i < val.length; i++) {
var elt = document.createElement("div");
elt.className = "CodeMirror-ruler";
- var col, cls = null, conf = val[i];
+ var col, conf = val[i];
if (typeof conf == "number") {
col = conf;
} else {
@@ -47,18 +43,9 @@
if (conf.color) elt.style.borderColor = conf.color;
if (conf.lineStyle) elt.style.borderLeftStyle = conf.lineStyle;
if (conf.width) elt.style.borderLeftWidth = conf.width;
- cls = val[i].className;
}
elt.style.left = (left + col * cw) + "px";
- elt.style.top = "-50px";
- elt.style.bottom = "-20px";
- elt.style.minHeight = minH + "px";
- cm.display.lineSpace.insertBefore(elt, cm.display.cursorDiv);
+ cm.state.rulerDiv.appendChild(elt)
}
}
-
- function refreshRulers(cm) {
- clearRulers(cm);
- setRulers(cm);
- }
});
diff --git a/static/editor.md/lib/codemirror/addon/edit/closebrackets.js b/static/editor.md/lib/codemirror/addon/edit/closebrackets.js
index ff4bb3f73..593d4439f 100644
--- a/static/editor.md/lib/codemirror/addon/edit/closebrackets.js
+++ b/static/editor.md/lib/codemirror/addon/edit/closebrackets.js
@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
@@ -9,153 +9,193 @@
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
- var DEFAULT_BRACKETS = "()[]{}''\"\"";
- var DEFAULT_TRIPLES = "'\"";
- var DEFAULT_EXPLODE_ON_ENTER = "[]{}";
- var SPACE_CHAR_REGEX = /\s/;
+ var defaults = {
+ pairs: "()[]{}''\"\"",
+ closeBefore: ")]}'\":;>",
+ triples: "",
+ explode: "[]{}"
+ };
var Pos = CodeMirror.Pos;
CodeMirror.defineOption("autoCloseBrackets", false, function(cm, val, old) {
- if (old != CodeMirror.Init && old)
- cm.removeKeyMap("autoCloseBrackets");
- if (!val) return;
- var pairs = DEFAULT_BRACKETS, triples = DEFAULT_TRIPLES, explode = DEFAULT_EXPLODE_ON_ENTER;
- if (typeof val == "string") pairs = val;
- else if (typeof val == "object") {
- if (val.pairs != null) pairs = val.pairs;
- if (val.triples != null) triples = val.triples;
- if (val.explode != null) explode = val.explode;
+ if (old && old != CodeMirror.Init) {
+ cm.removeKeyMap(keyMap);
+ cm.state.closeBrackets = null;
+ }
+ if (val) {
+ ensureBound(getOption(val, "pairs"))
+ cm.state.closeBrackets = val;
+ cm.addKeyMap(keyMap);
}
- var map = buildKeymap(pairs, triples);
- if (explode) map.Enter = buildExplodeHandler(explode);
- cm.addKeyMap(map);
});
- function charsAround(cm, pos) {
- var str = cm.getRange(Pos(pos.line, pos.ch - 1),
- Pos(pos.line, pos.ch + 1));
- return str.length == 2 ? str : null;
+ function getOption(conf, name) {
+ if (name == "pairs" && typeof conf == "string") return conf;
+ if (typeof conf == "object" && conf[name] != null) return conf[name];
+ return defaults[name];
}
- // Project the token type that will exists after the given char is
- // typed, and use it to determine whether it would cause the start
- // of a string token.
- function enteringString(cm, pos, ch) {
- var line = cm.getLine(pos.line);
- var token = cm.getTokenAt(pos);
- if (/\bstring2?\b/.test(token.type)) return false;
- var stream = new CodeMirror.StringStream(line.slice(0, pos.ch) + ch + line.slice(pos.ch), 4);
- stream.pos = stream.start = token.start;
- for (;;) {
- var type1 = cm.getMode().token(stream, token.state);
- if (stream.pos >= pos.ch + 1) return /\bstring2?\b/.test(type1);
- stream.start = stream.pos;
+ var keyMap = {Backspace: handleBackspace, Enter: handleEnter};
+ function ensureBound(chars) {
+ for (var i = 0; i < chars.length; i++) {
+ var ch = chars.charAt(i), key = "'" + ch + "'"
+ if (!keyMap[key]) keyMap[key] = handler(ch)
}
}
+ ensureBound(defaults.pairs + "`")
- function buildKeymap(pairs, triples) {
- var map = {
- name : "autoCloseBrackets",
- Backspace: function(cm) {
- if (cm.getOption("disableInput")) return CodeMirror.Pass;
- var ranges = cm.listSelections();
- for (var i = 0; i < ranges.length; i++) {
- if (!ranges[i].empty()) return CodeMirror.Pass;
- var around = charsAround(cm, ranges[i].head);
- if (!around || pairs.indexOf(around) % 2 != 0) return CodeMirror.Pass;
- }
- for (var i = ranges.length - 1; i >= 0; i--) {
- var cur = ranges[i].head;
- cm.replaceRange("", Pos(cur.line, cur.ch - 1), Pos(cur.line, cur.ch + 1));
- }
- }
- };
- var closingBrackets = "";
- for (var i = 0; i < pairs.length; i += 2) (function(left, right) {
- closingBrackets += right;
- map["'" + left + "'"] = function(cm) {
- if (cm.getOption("disableInput")) return CodeMirror.Pass;
- var ranges = cm.listSelections(), type, next;
- for (var i = 0; i < ranges.length; i++) {
- var range = ranges[i], cur = range.head, curType;
- var next = cm.getRange(cur, Pos(cur.line, cur.ch + 1));
- if (!range.empty()) {
- curType = "surround";
- } else if (left == right && next == right) {
- if (cm.getRange(cur, Pos(cur.line, cur.ch + 3)) == left + left + left)
- curType = "skipThree";
- else
- curType = "skip";
- } else if (left == right && cur.ch > 1 && triples.indexOf(left) >= 0 &&
- cm.getRange(Pos(cur.line, cur.ch - 2), cur) == left + left &&
- (cur.ch <= 2 || cm.getRange(Pos(cur.line, cur.ch - 3), Pos(cur.line, cur.ch - 2)) != left)) {
- curType = "addFour";
- } else if (left == '"' || left == "'") {
- if (!CodeMirror.isWordChar(next) && enteringString(cm, cur, left)) curType = "both";
- else return CodeMirror.Pass;
- } else if (cm.getLine(cur.line).length == cur.ch || closingBrackets.indexOf(next) >= 0 || SPACE_CHAR_REGEX.test(next)) {
- curType = "both";
- } else {
- return CodeMirror.Pass;
- }
- if (!type) type = curType;
- else if (type != curType) return CodeMirror.Pass;
- }
-
- cm.operation(function() {
- if (type == "skip") {
- cm.execCommand("goCharRight");
- } else if (type == "skipThree") {
- for (var i = 0; i < 3; i++)
- cm.execCommand("goCharRight");
- } else if (type == "surround") {
- var sels = cm.getSelections();
- for (var i = 0; i < sels.length; i++)
- sels[i] = left + sels[i] + right;
- cm.replaceSelections(sels, "around");
- } else if (type == "both") {
- cm.replaceSelection(left + right, null);
- cm.execCommand("goCharLeft");
- } else if (type == "addFour") {
- cm.replaceSelection(left + left + left + left, "before");
- cm.execCommand("goCharRight");
- }
- });
- };
- if (left != right) map["'" + right + "'"] = function(cm) {
- var ranges = cm.listSelections();
- for (var i = 0; i < ranges.length; i++) {
- var range = ranges[i];
- if (!range.empty() ||
- cm.getRange(range.head, Pos(range.head.line, range.head.ch + 1)) != right)
- return CodeMirror.Pass;
- }
- cm.execCommand("goCharRight");
- };
- })(pairs.charAt(i), pairs.charAt(i + 1));
- return map;
+ function handler(ch) {
+ return function(cm) { return handleChar(cm, ch); };
+ }
+
+ function getConfig(cm) {
+ var deflt = cm.state.closeBrackets;
+ if (!deflt || deflt.override) return deflt;
+ var mode = cm.getModeAt(cm.getCursor());
+ return mode.closeBrackets || deflt;
+ }
+
+ function handleBackspace(cm) {
+ var conf = getConfig(cm);
+ if (!conf || cm.getOption("disableInput")) return CodeMirror.Pass;
+
+ var pairs = getOption(conf, "pairs");
+ var ranges = cm.listSelections();
+ for (var i = 0; i < ranges.length; i++) {
+ if (!ranges[i].empty()) return CodeMirror.Pass;
+ var around = charsAround(cm, ranges[i].head);
+ if (!around || pairs.indexOf(around) % 2 != 0) return CodeMirror.Pass;
+ }
+ for (var i = ranges.length - 1; i >= 0; i--) {
+ var cur = ranges[i].head;
+ cm.replaceRange("", Pos(cur.line, cur.ch - 1), Pos(cur.line, cur.ch + 1), "+delete");
+ }
}
- function buildExplodeHandler(pairs) {
- return function(cm) {
- if (cm.getOption("disableInput")) return CodeMirror.Pass;
- var ranges = cm.listSelections();
+ function handleEnter(cm) {
+ var conf = getConfig(cm);
+ var explode = conf && getOption(conf, "explode");
+ if (!explode || cm.getOption("disableInput")) return CodeMirror.Pass;
+
+ var ranges = cm.listSelections();
+ for (var i = 0; i < ranges.length; i++) {
+ if (!ranges[i].empty()) return CodeMirror.Pass;
+ var around = charsAround(cm, ranges[i].head);
+ if (!around || explode.indexOf(around) % 2 != 0) return CodeMirror.Pass;
+ }
+ cm.operation(function() {
+ var linesep = cm.lineSeparator() || "\n";
+ cm.replaceSelection(linesep + linesep, null);
+ moveSel(cm, -1)
+ ranges = cm.listSelections();
for (var i = 0; i < ranges.length; i++) {
- if (!ranges[i].empty()) return CodeMirror.Pass;
- var around = charsAround(cm, ranges[i].head);
- if (!around || pairs.indexOf(around) % 2 != 0) return CodeMirror.Pass;
+ var line = ranges[i].head.line;
+ cm.indentLine(line, null, true);
+ cm.indentLine(line + 1, null, true);
}
- cm.operation(function() {
- cm.replaceSelection("\n\n", null);
- cm.execCommand("goCharLeft");
- ranges = cm.listSelections();
- for (var i = 0; i < ranges.length; i++) {
- var line = ranges[i].head.line;
- cm.indentLine(line, null, true);
- cm.indentLine(line + 1, null, true);
- }
- });
- };
+ });
+ }
+
+ function moveSel(cm, dir) {
+ var newRanges = [], ranges = cm.listSelections(), primary = 0
+ for (var i = 0; i < ranges.length; i++) {
+ var range = ranges[i]
+ if (range.head == cm.getCursor()) primary = i
+ var pos = range.head.ch || dir > 0 ? {line: range.head.line, ch: range.head.ch + dir} : {line: range.head.line - 1}
+ newRanges.push({anchor: pos, head: pos})
+ }
+ cm.setSelections(newRanges, primary)
+ }
+
+ function contractSelection(sel) {
+ var inverted = CodeMirror.cmpPos(sel.anchor, sel.head) > 0;
+ return {anchor: new Pos(sel.anchor.line, sel.anchor.ch + (inverted ? -1 : 1)),
+ head: new Pos(sel.head.line, sel.head.ch + (inverted ? 1 : -1))};
+ }
+
+ function handleChar(cm, ch) {
+ var conf = getConfig(cm);
+ if (!conf || cm.getOption("disableInput")) return CodeMirror.Pass;
+
+ var pairs = getOption(conf, "pairs");
+ var pos = pairs.indexOf(ch);
+ if (pos == -1) return CodeMirror.Pass;
+
+ var closeBefore = getOption(conf,"closeBefore");
+
+ var triples = getOption(conf, "triples");
+
+ var identical = pairs.charAt(pos + 1) == ch;
+ var ranges = cm.listSelections();
+ var opening = pos % 2 == 0;
+
+ var type;
+ for (var i = 0; i < ranges.length; i++) {
+ var range = ranges[i], cur = range.head, curType;
+ var next = cm.getRange(cur, Pos(cur.line, cur.ch + 1));
+ if (opening && !range.empty()) {
+ curType = "surround";
+ } else if ((identical || !opening) && next == ch) {
+ if (identical && stringStartsAfter(cm, cur))
+ curType = "both";
+ else if (triples.indexOf(ch) >= 0 && cm.getRange(cur, Pos(cur.line, cur.ch + 3)) == ch + ch + ch)
+ curType = "skipThree";
+ else
+ curType = "skip";
+ } else if (identical && cur.ch > 1 && triples.indexOf(ch) >= 0 &&
+ cm.getRange(Pos(cur.line, cur.ch - 2), cur) == ch + ch) {
+ if (cur.ch > 2 && /\bstring/.test(cm.getTokenTypeAt(Pos(cur.line, cur.ch - 2)))) return CodeMirror.Pass;
+ curType = "addFour";
+ } else if (identical) {
+ var prev = cur.ch == 0 ? " " : cm.getRange(Pos(cur.line, cur.ch - 1), cur)
+ if (!CodeMirror.isWordChar(next) && prev != ch && !CodeMirror.isWordChar(prev)) curType = "both";
+ else return CodeMirror.Pass;
+ } else if (opening && (next.length === 0 || /\s/.test(next) || closeBefore.indexOf(next) > -1)) {
+ curType = "both";
+ } else {
+ return CodeMirror.Pass;
+ }
+ if (!type) type = curType;
+ else if (type != curType) return CodeMirror.Pass;
+ }
+
+ var left = pos % 2 ? pairs.charAt(pos - 1) : ch;
+ var right = pos % 2 ? ch : pairs.charAt(pos + 1);
+ cm.operation(function() {
+ if (type == "skip") {
+ moveSel(cm, 1)
+ } else if (type == "skipThree") {
+ moveSel(cm, 3)
+ } else if (type == "surround") {
+ var sels = cm.getSelections();
+ for (var i = 0; i < sels.length; i++)
+ sels[i] = left + sels[i] + right;
+ cm.replaceSelections(sels, "around");
+ sels = cm.listSelections().slice();
+ for (var i = 0; i < sels.length; i++)
+ sels[i] = contractSelection(sels[i]);
+ cm.setSelections(sels);
+ } else if (type == "both") {
+ cm.replaceSelection(left + right, null);
+ cm.triggerElectric(left + right);
+ moveSel(cm, -1)
+ } else if (type == "addFour") {
+ cm.replaceSelection(left + left + left + left, "before");
+ moveSel(cm, 1)
+ }
+ });
+ }
+
+ function charsAround(cm, pos) {
+ var str = cm.getRange(Pos(pos.line, pos.ch - 1),
+ Pos(pos.line, pos.ch + 1));
+ return str.length == 2 ? str : null;
+ }
+
+ function stringStartsAfter(cm, pos) {
+ var token = cm.getTokenAt(Pos(pos.line, pos.ch + 1))
+ return /\bstring/.test(token.type) && token.start == pos.ch &&
+ (pos.ch == 0 || !/\bstring/.test(cm.getTokenTypeAt(pos)))
}
});
diff --git a/static/editor.md/lib/codemirror/addon/edit/closetag.js b/static/editor.md/lib/codemirror/addon/edit/closetag.js
index e68d52d9d..7c22a50ec 100644
--- a/static/editor.md/lib/codemirror/addon/edit/closetag.js
+++ b/static/editor.md/lib/codemirror/addon/edit/closetag.js
@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
/**
* Tag-closer extension for CodeMirror.
@@ -21,6 +21,8 @@
* An array of tag names that should, when opened, cause a
* blank line to be added inside the tag, and the blank line and
* closing line to be indented.
+ * `emptyTags` (default is none)
+ * An array of XML tag names that should be autoclosed with '/>'.
*
* See demos/closetag.html for a usage example.
*/
@@ -38,9 +40,9 @@
cm.removeKeyMap("autoCloseTags");
if (!val) return;
var map = {name: "autoCloseTags"};
- if (typeof val != "object" || val.whenClosing)
+ if (typeof val != "object" || val.whenClosing !== false)
map["'/'"] = function(cm) { return autoCloseSlash(cm); };
- if (typeof val != "object" || val.whenOpening)
+ if (typeof val != "object" || val.whenOpening !== false)
map["'>'"] = function(cm) { return autoCloseGT(cm); };
cm.addKeyMap(map);
});
@@ -53,41 +55,50 @@
function autoCloseGT(cm) {
if (cm.getOption("disableInput")) return CodeMirror.Pass;
var ranges = cm.listSelections(), replacements = [];
+ var opt = cm.getOption("autoCloseTags");
for (var i = 0; i < ranges.length; i++) {
if (!ranges[i].empty()) return CodeMirror.Pass;
var pos = ranges[i].head, tok = cm.getTokenAt(pos);
var inner = CodeMirror.innerMode(cm.getMode(), tok.state), state = inner.state;
- if (inner.mode.name != "xml" || !state.tagName) return CodeMirror.Pass;
+ var tagInfo = inner.mode.xmlCurrentTag && inner.mode.xmlCurrentTag(state)
+ var tagName = tagInfo && tagInfo.name
+ if (!tagName) return CodeMirror.Pass
- var opt = cm.getOption("autoCloseTags"), html = inner.mode.configuration == "html";
+ var html = inner.mode.configuration == "html";
var dontCloseTags = (typeof opt == "object" && opt.dontCloseTags) || (html && htmlDontClose);
var indentTags = (typeof opt == "object" && opt.indentTags) || (html && htmlIndent);
- var tagName = state.tagName;
if (tok.end > pos.ch) tagName = tagName.slice(0, tagName.length - tok.end + pos.ch);
var lowerTagName = tagName.toLowerCase();
// Don't process the '>' at the end of an end-tag or self-closing tag
if (!tagName ||
tok.type == "string" && (tok.end != pos.ch || !/[\"\']/.test(tok.string.charAt(tok.string.length - 1)) || tok.string.length == 1) ||
- tok.type == "tag" && state.type == "closeTag" ||
- tok.string.indexOf("/") == (tok.string.length - 1) || // match something like
+ tok.type == "tag" && tagInfo.close ||
+ tok.string.indexOf("/") == (pos.ch - tok.start - 1) || // match something like
dontCloseTags && indexOf(dontCloseTags, lowerTagName) > -1 ||
- closingTagExists(cm, tagName, pos, state, true))
+ closingTagExists(cm, inner.mode.xmlCurrentContext && inner.mode.xmlCurrentContext(state) || [], tagName, pos, true))
return CodeMirror.Pass;
+ var emptyTags = typeof opt == "object" && opt.emptyTags;
+ if (emptyTags && indexOf(emptyTags, tagName) > -1) {
+ replacements[i] = { text: "/>", newPos: CodeMirror.Pos(pos.line, pos.ch + 2) };
+ continue;
+ }
+
var indent = indentTags && indexOf(indentTags, lowerTagName) > -1;
replacements[i] = {indent: indent,
text: ">" + (indent ? "\n\n" : "") + "" + tagName + ">",
newPos: indent ? CodeMirror.Pos(pos.line + 1, 0) : CodeMirror.Pos(pos.line, pos.ch + 1)};
}
+ var dontIndentOnAutoClose = (typeof opt == "object" && opt.dontIndentOnAutoClose);
for (var i = ranges.length - 1; i >= 0; i--) {
var info = replacements[i];
cm.replaceRange(info.text, ranges[i].head, ranges[i].anchor, "+insert");
var sel = cm.listSelections().slice(0);
sel[i] = {head: info.newPos, anchor: info.newPos};
cm.setSelections(sel);
- if (info.indent) {
+ if (!dontIndentOnAutoClose && info.indent) {
cm.indentLine(info.newPos.line, null, true);
cm.indentLine(info.newPos.line + 1, null, true);
}
@@ -97,6 +108,8 @@
function autoCloseCurrent(cm, typingSlash) {
var ranges = cm.listSelections(), replacements = [];
var head = typingSlash ? "/" : "";
+ var opt = cm.getOption("autoCloseTags");
+ var dontIndentOnAutoClose = (typeof opt == "object" && opt.dontIndentOnSlash);
for (var i = 0; i < ranges.length; i++) {
if (!ranges[i].empty()) return CodeMirror.Pass;
var pos = ranges[i].head, tok = cm.getTokenAt(pos);
@@ -108,25 +121,28 @@
// when completing in JS/CSS snippet in htmlmixed mode. Does not
// work for other XML embedded languages (there is no general
// way to go from a mixed mode to its current XML state).
- if (inner.mode.name != "xml") {
- if (cm.getMode().name == "htmlmixed" && inner.mode.name == "javascript")
- replacements[i] = head + "script>";
- else if (cm.getMode().name == "htmlmixed" && inner.mode.name == "css")
- replacements[i] = head + "style>";
- else
- return CodeMirror.Pass;
+ var replacement, mixed = inner.mode.name != "xml" && cm.getMode().name == "htmlmixed"
+ if (mixed && inner.mode.name == "javascript") {
+ replacement = head + "script";
+ } else if (mixed && inner.mode.name == "css") {
+ replacement = head + "style";
} else {
- if (!state.context || !state.context.tagName ||
- closingTagExists(cm, state.context.tagName, pos, state))
+ var context = inner.mode.xmlCurrentContext && inner.mode.xmlCurrentContext(state)
+ var top = context.length ? context[context.length - 1] : ""
+ if (!context || (context.length && closingTagExists(cm, context, top, pos)))
return CodeMirror.Pass;
- replacements[i] = head + state.context.tagName + ">";
+ replacement = head + top
}
+ if (cm.getLine(pos.line).charAt(tok.end) != ">") replacement += ">";
+ replacements[i] = replacement;
}
cm.replaceSelections(replacements);
ranges = cm.listSelections();
- for (var i = 0; i < ranges.length; i++)
- if (i == ranges.length - 1 || ranges[i].head.line < ranges[i + 1].head.line)
- cm.indentLine(ranges[i].head.line);
+ if (!dontIndentOnAutoClose) {
+ for (var i = 0; i < ranges.length; i++)
+ if (i == ranges.length - 1 || ranges[i].head.line < ranges[i + 1].head.line)
+ cm.indentLine(ranges[i].head.line);
+ }
}
function autoCloseSlash(cm) {
@@ -145,16 +161,19 @@
// If xml-fold is loaded, we use its functionality to try and verify
// whether a given tag is actually unclosed.
- function closingTagExists(cm, tagName, pos, state, newTag) {
+ function closingTagExists(cm, context, tagName, pos, newTag) {
if (!CodeMirror.scanForClosingTag) return false;
var end = Math.min(cm.lastLine() + 1, pos.line + 500);
var nextClose = CodeMirror.scanForClosingTag(cm, pos, null, end);
if (!nextClose || nextClose.tag != tagName) return false;
- var cx = state.context;
// If the immediate wrapping context contains onCx instances of
// the same tag, a closing tag only exists if there are at least
// that many closing tags of that type following.
- for (var onCx = newTag ? 1 : 0; cx && cx.tagName == tagName; cx = cx.prev) ++onCx;
+ var onCx = newTag ? 1 : 0
+ for (var i = context.length - 1; i >= 0; i--) {
+ if (context[i] == tagName) ++onCx
+ else break
+ }
pos = nextClose.to;
for (var i = 1; i < onCx; i++) {
var next = CodeMirror.scanForClosingTag(cm, pos, null, end);
diff --git a/static/editor.md/lib/codemirror/addon/edit/continuelist.js b/static/editor.md/lib/codemirror/addon/edit/continuelist.js
index ca8d26751..6ec65010d 100644
--- a/static/editor.md/lib/codemirror/addon/edit/continuelist.js
+++ b/static/editor.md/lib/codemirror/addon/edit/continuelist.js
@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
@@ -11,41 +11,91 @@
})(function(CodeMirror) {
"use strict";
- var listRE = /^(\s*)(>[> ]*|[*+-]\s|(\d+)\.)(\s*)/,
- emptyListRE = /^(\s*)(>[> ]*|[*+-]|(\d+)\.)(\s*)$/,
+ var listRE = /^(\s*)(>[> ]*|[*+-] \[[x ]\]\s|[*+-]\s|(\d+)([.)]))(\s*)/,
+ emptyListRE = /^(\s*)(>[> ]*|[*+-] \[[x ]\]|[*+-]|(\d+)[.)])(\s*)$/,
unorderedListRE = /[*+-]\s/;
CodeMirror.commands.newlineAndIndentContinueMarkdownList = function(cm) {
if (cm.getOption("disableInput")) return CodeMirror.Pass;
var ranges = cm.listSelections(), replacements = [];
for (var i = 0; i < ranges.length; i++) {
- var pos = ranges[i].head, match;
+ var pos = ranges[i].head;
+
+ // If we're not in Markdown mode, fall back to normal newlineAndIndent
var eolState = cm.getStateAfter(pos.line);
+ var inner = CodeMirror.innerMode(cm.getMode(), eolState);
+ if (inner.mode.name !== "markdown") {
+ cm.execCommand("newlineAndIndent");
+ return;
+ } else {
+ eolState = inner.state;
+ }
+
var inList = eolState.list !== false;
- var inQuote = eolState.quote !== false;
+ var inQuote = eolState.quote !== 0;
- if (!ranges[i].empty() || (!inList && !inQuote) || !(match = cm.getLine(pos.line).match(listRE))) {
+ var line = cm.getLine(pos.line), match = listRE.exec(line);
+ var cursorBeforeBullet = /^\s*$/.test(line.slice(0, pos.ch));
+ if (!ranges[i].empty() || (!inList && !inQuote) || !match || cursorBeforeBullet) {
cm.execCommand("newlineAndIndent");
return;
}
- if (cm.getLine(pos.line).match(emptyListRE)) {
- cm.replaceRange("", {
+ if (emptyListRE.test(line)) {
+ var endOfQuote = inQuote && />\s*$/.test(line)
+ var endOfList = !/>\s*$/.test(line)
+ if (endOfQuote || endOfList) cm.replaceRange("", {
line: pos.line, ch: 0
}, {
line: pos.line, ch: pos.ch + 1
});
replacements[i] = "\n";
-
} else {
- var indent = match[1], after = match[4];
- var bullet = unorderedListRE.test(match[2]) || match[2].indexOf(">") >= 0
- ? match[2]
- : (parseInt(match[3], 10) + 1) + ".";
-
+ var indent = match[1], after = match[5];
+ var numbered = !(unorderedListRE.test(match[2]) || match[2].indexOf(">") >= 0);
+ var bullet = numbered ? (parseInt(match[3], 10) + 1) + match[4] : match[2].replace("x", " ");
replacements[i] = "\n" + indent + bullet + after;
+
+ if (numbered) incrementRemainingMarkdownListNumbers(cm, pos);
}
}
cm.replaceSelections(replacements);
};
+
+ // Auto-updating Markdown list numbers when a new item is added to the
+ // middle of a list
+ function incrementRemainingMarkdownListNumbers(cm, pos) {
+ var startLine = pos.line, lookAhead = 0, skipCount = 0;
+ var startItem = listRE.exec(cm.getLine(startLine)), startIndent = startItem[1];
+
+ do {
+ lookAhead += 1;
+ var nextLineNumber = startLine + lookAhead;
+ var nextLine = cm.getLine(nextLineNumber), nextItem = listRE.exec(nextLine);
+
+ if (nextItem) {
+ var nextIndent = nextItem[1];
+ var newNumber = (parseInt(startItem[3], 10) + lookAhead - skipCount);
+ var nextNumber = (parseInt(nextItem[3], 10)), itemNumber = nextNumber;
+
+ if (startIndent === nextIndent && !isNaN(nextNumber)) {
+ if (newNumber === nextNumber) itemNumber = nextNumber + 1;
+ if (newNumber > nextNumber) itemNumber = newNumber + 1;
+ cm.replaceRange(
+ nextLine.replace(listRE, nextIndent + itemNumber + nextItem[4] + nextItem[5]),
+ {
+ line: nextLineNumber, ch: 0
+ }, {
+ line: nextLineNumber, ch: nextLine.length
+ });
+ } else {
+ if (startIndent.length > nextIndent.length) return;
+ // This doesn't run if the next line immediately indents, as it is
+ // not clear of the users intention (new indented item or same level)
+ if ((startIndent.length < nextIndent.length) && (lookAhead === 1)) return;
+ skipCount += 1;
+ }
+ }
+ } while (nextItem);
+ }
});
diff --git a/static/editor.md/lib/codemirror/addon/edit/matchbrackets.js b/static/editor.md/lib/codemirror/addon/edit/matchbrackets.js
index 70e1ae18c..43dc8840f 100644
--- a/static/editor.md/lib/codemirror/addon/edit/matchbrackets.js
+++ b/static/editor.md/lib/codemirror/addon/edit/matchbrackets.js
@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
@@ -14,17 +14,31 @@
var Pos = CodeMirror.Pos;
- var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"};
+ var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<", "<": ">>", ">": "<<"};
- function findMatchingBracket(cm, where, strict, config) {
+ function bracketRegex(config) {
+ return config && config.bracketRegex || /[(){}[\]]/
+ }
+
+ function findMatchingBracket(cm, where, config) {
var line = cm.getLineHandle(where.line), pos = where.ch - 1;
- var match = (pos >= 0 && matching[line.text.charAt(pos)]) || matching[line.text.charAt(++pos)];
+ var afterCursor = config && config.afterCursor
+ if (afterCursor == null)
+ afterCursor = /(^| )cm-fat-cursor($| )/.test(cm.getWrapperElement().className)
+ var re = bracketRegex(config)
+
+ // A cursor is defined as between two characters, but in in vim command mode
+ // (i.e. not insert mode), the cursor is visually represented as a
+ // highlighted box on top of the 2nd character. Otherwise, we allow matches
+ // from before or after the cursor.
+ var match = (!afterCursor && pos >= 0 && re.test(line.text.charAt(pos)) && matching[line.text.charAt(pos)]) ||
+ re.test(line.text.charAt(pos + 1)) && matching[line.text.charAt(++pos)];
if (!match) return null;
var dir = match.charAt(1) == ">" ? 1 : -1;
- if (strict && (dir > 0) != (pos == where.ch)) return null;
+ if (config && config.strict && (dir > 0) != (pos == where.ch)) return null;
var style = cm.getTokenTypeAt(Pos(where.line, pos + 1));
- var found = scanForBracket(cm, Pos(where.line, pos + (dir > 0 ? 1 : 0)), dir, style || null, config);
+ var found = scanForBracket(cm, Pos(where.line, pos + (dir > 0 ? 1 : 0)), dir, style, config);
if (found == null) return null;
return {from: Pos(where.line, pos), to: found && found.pos,
match: found && found.ch == match.charAt(0), forward: dir > 0};
@@ -42,7 +56,7 @@
var maxScanLines = (config && config.maxScanLines) || 1000;
var stack = [];
- var re = config && config.bracketRegex ? config.bracketRegex : /[(){}[\]]/;
+ var re = bracketRegex(config)
var lineEnd = dir > 0 ? Math.min(where.line + maxScanLines, cm.lastLine() + 1)
: Math.max(cm.firstLine() - 1, where.line - maxScanLines);
for (var lineNo = where.line; lineNo != lineEnd; lineNo += dir) {
@@ -53,9 +67,10 @@
if (lineNo == where.line) pos = where.ch - (dir < 0 ? 1 : 0);
for (; pos != end; pos += dir) {
var ch = line.charAt(pos);
- if (re.test(ch) && (style === undefined || cm.getTokenTypeAt(Pos(lineNo, pos + 1)) == style)) {
+ if (re.test(ch) && (style === undefined ||
+ (cm.getTokenTypeAt(Pos(lineNo, pos + 1)) || "") == (style || ""))) {
var match = matching[ch];
- if ((match.charAt(1) == ">") == (dir > 0)) stack.push(ch);
+ if (match && (match.charAt(1) == ">") == (dir > 0)) stack.push(ch);
else if (!stack.length) return {pos: Pos(lineNo, pos), ch: ch};
else stack.pop();
}
@@ -66,11 +81,12 @@
function matchBrackets(cm, autoclear, config) {
// Disable brace matching in long lines, since it'll cause hugely slow updates
- var maxHighlightLen = cm.state.matchBrackets.maxHighlightLineLength || 1000;
+ var maxHighlightLen = cm.state.matchBrackets.maxHighlightLineLength || 1000,
+ highlightNonMatching = config && config.highlightNonMatching;
var marks = [], ranges = cm.listSelections();
for (var i = 0; i < ranges.length; i++) {
- var match = ranges[i].empty() && findMatchingBracket(cm, ranges[i].head, false, config);
- if (match && cm.getLine(match.from.line).length <= maxHighlightLen) {
+ var match = ranges[i].empty() && findMatchingBracket(cm, ranges[i].head, config);
+ if (match && (match.match || highlightNonMatching !== false) && cm.getLine(match.from.line).length <= maxHighlightLen) {
var style = match.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket";
marks.push(cm.markText(match.from, Pos(match.from.line, match.from.ch + 1), {className: style}));
if (match.to && cm.getLine(match.to.line).length <= maxHighlightLen)
@@ -80,7 +96,7 @@
if (marks.length) {
// Kludge to work around the IE bug from issue #1193, where text
- // input stops going to the textare whever this fires.
+ // input stops going to the textarea whenever this fires.
if (ie_lt8 && cm.state.focused) cm.focus();
var clear = function() {
@@ -93,26 +109,50 @@
}
}
- var currentlyHighlighted = null;
function doMatchBrackets(cm) {
cm.operation(function() {
- if (currentlyHighlighted) {currentlyHighlighted(); currentlyHighlighted = null;}
- currentlyHighlighted = matchBrackets(cm, false, cm.state.matchBrackets);
+ if (cm.state.matchBrackets.currentlyHighlighted) {
+ cm.state.matchBrackets.currentlyHighlighted();
+ cm.state.matchBrackets.currentlyHighlighted = null;
+ }
+ cm.state.matchBrackets.currentlyHighlighted = matchBrackets(cm, false, cm.state.matchBrackets);
});
}
+ function clearHighlighted(cm) {
+ if (cm.state.matchBrackets && cm.state.matchBrackets.currentlyHighlighted) {
+ cm.state.matchBrackets.currentlyHighlighted();
+ cm.state.matchBrackets.currentlyHighlighted = null;
+ }
+ }
+
CodeMirror.defineOption("matchBrackets", false, function(cm, val, old) {
- if (old && old != CodeMirror.Init)
+ if (old && old != CodeMirror.Init) {
cm.off("cursorActivity", doMatchBrackets);
+ cm.off("focus", doMatchBrackets)
+ cm.off("blur", clearHighlighted)
+ clearHighlighted(cm);
+ }
if (val) {
cm.state.matchBrackets = typeof val == "object" ? val : {};
cm.on("cursorActivity", doMatchBrackets);
+ cm.on("focus", doMatchBrackets)
+ cm.on("blur", clearHighlighted)
}
});
CodeMirror.defineExtension("matchBrackets", function() {matchBrackets(this, true);});
- CodeMirror.defineExtension("findMatchingBracket", function(pos, strict, config){
- return findMatchingBracket(this, pos, strict, config);
+ CodeMirror.defineExtension("findMatchingBracket", function(pos, config, oldConfig){
+ // Backwards-compatibility kludge
+ if (oldConfig || typeof config == "boolean") {
+ if (!oldConfig) {
+ config = config ? {strict: true} : null
+ } else {
+ oldConfig.strict = config
+ config = oldConfig
+ }
+ }
+ return findMatchingBracket(this, pos, config)
});
CodeMirror.defineExtension("scanForBracket", function(pos, dir, style, config){
return scanForBracket(this, pos, dir, style, config);
diff --git a/static/editor.md/lib/codemirror/addon/edit/matchtags.js b/static/editor.md/lib/codemirror/addon/edit/matchtags.js
index fb1911a8d..2203d9390 100644
--- a/static/editor.md/lib/codemirror/addon/edit/matchtags.js
+++ b/static/editor.md/lib/codemirror/addon/edit/matchtags.js
@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
diff --git a/static/editor.md/lib/codemirror/addon/edit/trailingspace.js b/static/editor.md/lib/codemirror/addon/edit/trailingspace.js
index fa7b56be5..c39c310a9 100644
--- a/static/editor.md/lib/codemirror/addon/edit/trailingspace.js
+++ b/static/editor.md/lib/codemirror/addon/edit/trailingspace.js
@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
diff --git a/static/editor.md/lib/codemirror/addon/fold/brace-fold.js b/static/editor.md/lib/codemirror/addon/fold/brace-fold.js
index 1605f6c2a..28cb093f2 100644
--- a/static/editor.md/lib/codemirror/addon/fold/brace-fold.js
+++ b/static/editor.md/lib/codemirror/addon/fold/brace-fold.js
@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
@@ -11,53 +11,67 @@
})(function(CodeMirror) {
"use strict";
-CodeMirror.registerHelper("fold", "brace", function(cm, start) {
- var line = start.line, lineText = cm.getLine(line);
- var startCh, tokenType;
+function bracketFolding(pairs) {
+ return function(cm, start) {
+ var line = start.line, lineText = cm.getLine(line);
- function findOpening(openCh) {
- for (var at = start.ch, pass = 0;;) {
- var found = at <= 0 ? -1 : lineText.lastIndexOf(openCh, at - 1);
- if (found == -1) {
- if (pass == 1) break;
- pass = 1;
- at = lineText.length;
- continue;
+ function findOpening(pair) {
+ var tokenType;
+ for (var at = start.ch, pass = 0;;) {
+ var found = at <= 0 ? -1 : lineText.lastIndexOf(pair[0], at - 1);
+ if (found == -1) {
+ if (pass == 1) break;
+ pass = 1;
+ at = lineText.length;
+ continue;
+ }
+ if (pass == 1 && found < start.ch) break;
+ tokenType = cm.getTokenTypeAt(CodeMirror.Pos(line, found + 1));
+ if (!/^(comment|string)/.test(tokenType)) return {ch: found + 1, tokenType: tokenType, pair: pair};
+ at = found - 1;
}
- if (pass == 1 && found < start.ch) break;
- tokenType = cm.getTokenTypeAt(CodeMirror.Pos(line, found + 1));
- if (!/^(comment|string)/.test(tokenType)) return found + 1;
- at = found - 1;
}
- }
-
- var startToken = "{", endToken = "}", startCh = findOpening("{");
- if (startCh == null) {
- startToken = "[", endToken = "]";
- startCh = findOpening("[");
- }
- if (startCh == null) return;
- var count = 1, lastLine = cm.lastLine(), end, endCh;
- outer: for (var i = line; i <= lastLine; ++i) {
- var text = cm.getLine(i), pos = i == line ? startCh : 0;
- for (;;) {
- var nextOpen = text.indexOf(startToken, pos), nextClose = text.indexOf(endToken, pos);
- if (nextOpen < 0) nextOpen = text.length;
- if (nextClose < 0) nextClose = text.length;
- pos = Math.min(nextOpen, nextClose);
- if (pos == text.length) break;
- if (cm.getTokenTypeAt(CodeMirror.Pos(i, pos + 1)) == tokenType) {
- if (pos == nextOpen) ++count;
- else if (!--count) { end = i; endCh = pos; break outer; }
+ function findRange(found) {
+ var count = 1, lastLine = cm.lastLine(), end, startCh = found.ch, endCh
+ outer: for (var i = line; i <= lastLine; ++i) {
+ var text = cm.getLine(i), pos = i == line ? startCh : 0;
+ for (;;) {
+ var nextOpen = text.indexOf(found.pair[0], pos), nextClose = text.indexOf(found.pair[1], pos);
+ if (nextOpen < 0) nextOpen = text.length;
+ if (nextClose < 0) nextClose = text.length;
+ pos = Math.min(nextOpen, nextClose);
+ if (pos == text.length) break;
+ if (cm.getTokenTypeAt(CodeMirror.Pos(i, pos + 1)) == found.tokenType) {
+ if (pos == nextOpen) ++count;
+ else if (!--count) { end = i; endCh = pos; break outer; }
+ }
+ ++pos;
+ }
}
- ++pos;
+
+ if (end == null || line == end) return null
+ return {from: CodeMirror.Pos(line, startCh),
+ to: CodeMirror.Pos(end, endCh)};
+ }
+
+ var found = []
+ for (var i = 0; i < pairs.length; i++) {
+ var open = findOpening(pairs[i])
+ if (open) found.push(open)
+ }
+ found.sort(function(a, b) { return a.ch - b.ch })
+ for (var i = 0; i < found.length; i++) {
+ var range = findRange(found[i])
+ if (range) return range
}
+ return null
}
- if (end == null || line == end && endCh == startCh) return;
- return {from: CodeMirror.Pos(line, startCh),
- to: CodeMirror.Pos(end, endCh)};
-});
+}
+
+CodeMirror.registerHelper("fold", "brace", bracketFolding([["{", "}"], ["[", "]"]]));
+
+CodeMirror.registerHelper("fold", "brace-paren", bracketFolding([["{", "}"], ["[", "]"], ["(", ")"]]));
CodeMirror.registerHelper("fold", "import", function(cm, start) {
function hasImport(line) {
@@ -72,15 +86,15 @@ CodeMirror.registerHelper("fold", "import", function(cm, start) {
}
}
- var start = start.line, has = hasImport(start), prev;
- if (!has || hasImport(start - 1) || ((prev = hasImport(start - 2)) && prev.end.line == start - 1))
+ var startLine = start.line, has = hasImport(startLine), prev;
+ if (!has || hasImport(startLine - 1) || ((prev = hasImport(startLine - 2)) && prev.end.line == startLine - 1))
return null;
for (var end = has.end;;) {
var next = hasImport(end.line + 1);
if (next == null) break;
end = next.end;
}
- return {from: cm.clipPos(CodeMirror.Pos(start, has.startCh + 1)), to: end};
+ return {from: cm.clipPos(CodeMirror.Pos(startLine, has.startCh + 1)), to: end};
});
CodeMirror.registerHelper("fold", "include", function(cm, start) {
@@ -91,14 +105,14 @@ CodeMirror.registerHelper("fold", "include", function(cm, start) {
if (start.type == "meta" && start.string.slice(0, 8) == "#include") return start.start + 8;
}
- var start = start.line, has = hasInclude(start);
- if (has == null || hasInclude(start - 1) != null) return null;
- for (var end = start;;) {
+ var startLine = start.line, has = hasInclude(startLine);
+ if (has == null || hasInclude(startLine - 1) != null) return null;
+ for (var end = startLine;;) {
var next = hasInclude(end + 1);
if (next == null) break;
++end;
}
- return {from: CodeMirror.Pos(start, has + 1),
+ return {from: CodeMirror.Pos(startLine, has + 1),
to: cm.clipPos(CodeMirror.Pos(end))};
});
diff --git a/static/editor.md/lib/codemirror/addon/fold/comment-fold.js b/static/editor.md/lib/codemirror/addon/fold/comment-fold.js
index b75db7ea2..836101d8b 100644
--- a/static/editor.md/lib/codemirror/addon/fold/comment-fold.js
+++ b/static/editor.md/lib/codemirror/addon/fold/comment-fold.js
@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
@@ -28,7 +28,9 @@ CodeMirror.registerGlobalHelper("fold", "comment", function(mode) {
continue;
}
if (pass == 1 && found < start.ch) return;
- if (/comment/.test(cm.getTokenTypeAt(CodeMirror.Pos(line, found + 1)))) {
+ if (/comment/.test(cm.getTokenTypeAt(CodeMirror.Pos(line, found + 1))) &&
+ (found == 0 || lineText.slice(found - endToken.length, found) == endToken ||
+ !/comment/.test(cm.getTokenTypeAt(CodeMirror.Pos(line, found))))) {
startCh = found + startToken.length;
break;
}
diff --git a/static/editor.md/lib/codemirror/addon/fold/foldcode.js b/static/editor.md/lib/codemirror/addon/fold/foldcode.js
index 62911f935..721bc087e 100644
--- a/static/editor.md/lib/codemirror/addon/fold/foldcode.js
+++ b/static/editor.md/lib/codemirror/addon/fold/foldcode.js
@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
@@ -24,9 +24,11 @@
function getRange(allowFolded) {
var range = finder(cm, pos);
if (!range || range.to.line - range.from.line < minSize) return null;
+ if (force === "fold") return range;
+
var marks = cm.findMarksAt(range.from);
for (var i = 0; i < marks.length; ++i) {
- if (marks[i].__isFold && force !== "fold") {
+ if (marks[i].__isFold) {
if (!allowFolded) return null;
range.cleared = true;
marks[i].clear();
@@ -42,14 +44,14 @@
}
if (!range || range.cleared || force === "unfold") return;
- var myWidget = makeWidget(cm, options);
+ var myWidget = makeWidget(cm, options, range);
CodeMirror.on(myWidget, "mousedown", function(e) {
myRange.clear();
CodeMirror.e_preventDefault(e);
});
var myRange = cm.markText(range.from, range.to, {
replacedWith: myWidget,
- clearOnEnter: true,
+ clearOnEnter: getOption(cm, options, "clearOnEnter"),
__isFold: true
});
myRange.on("clear", function(from, to) {
@@ -58,13 +60,20 @@
CodeMirror.signal(cm, "fold", cm, range.from, range.to);
}
- function makeWidget(cm, options) {
+ function makeWidget(cm, options, range) {
var widget = getOption(cm, options, "widget");
+
+ if (typeof widget == "function") {
+ widget = widget(range.from, range.to);
+ }
+
if (typeof widget == "string") {
var text = document.createTextNode(widget);
widget = document.createElement("span");
widget.appendChild(text);
widget.className = "CodeMirror-foldmarker";
+ } else if (widget) {
+ widget = widget.cloneNode(true)
}
return widget;
}
@@ -92,18 +101,18 @@
cm.foldCode(cm.getCursor(), null, "fold");
};
CodeMirror.commands.unfold = function(cm) {
- cm.foldCode(cm.getCursor(), null, "unfold");
+ cm.foldCode(cm.getCursor(), { scanUp: false }, "unfold");
};
CodeMirror.commands.foldAll = function(cm) {
cm.operation(function() {
for (var i = cm.firstLine(), e = cm.lastLine(); i <= e; i++)
- cm.foldCode(CodeMirror.Pos(i, 0), null, "fold");
+ cm.foldCode(CodeMirror.Pos(i, 0), { scanUp: false }, "fold");
});
};
CodeMirror.commands.unfoldAll = function(cm) {
cm.operation(function() {
for (var i = cm.firstLine(), e = cm.lastLine(); i <= e; i++)
- cm.foldCode(CodeMirror.Pos(i, 0), null, "unfold");
+ cm.foldCode(CodeMirror.Pos(i, 0), { scanUp: false }, "unfold");
});
};
@@ -129,7 +138,8 @@
rangeFinder: CodeMirror.fold.auto,
widget: "\u2194",
minFoldSize: 0,
- scanUp: false
+ scanUp: false,
+ clearOnEnter: true
};
CodeMirror.defineOption("foldOptions", null);
diff --git a/static/editor.md/lib/codemirror/addon/fold/foldgutter.js b/static/editor.md/lib/codemirror/addon/fold/foldgutter.js
index 199120c72..7d46a609b 100644
--- a/static/editor.md/lib/codemirror/addon/fold/foldgutter.js
+++ b/static/editor.md/lib/codemirror/addon/fold/foldgutter.js
@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
@@ -16,21 +16,21 @@
cm.clearGutter(cm.state.foldGutter.options.gutter);
cm.state.foldGutter = null;
cm.off("gutterClick", onGutterClick);
- cm.off("change", onChange);
+ cm.off("changes", onChange);
cm.off("viewportChange", onViewportChange);
cm.off("fold", onFold);
cm.off("unfold", onFold);
- cm.off("swapDoc", updateInViewport);
+ cm.off("swapDoc", onChange);
}
if (val) {
cm.state.foldGutter = new State(parseOptions(val));
updateInViewport(cm);
cm.on("gutterClick", onGutterClick);
- cm.on("change", onChange);
+ cm.on("changes", onChange);
cm.on("viewportChange", onViewportChange);
cm.on("fold", onFold);
cm.on("unfold", onFold);
- cm.on("swapDoc", updateInViewport);
+ cm.on("swapDoc", onChange);
}
});
@@ -50,9 +50,14 @@
}
function isFolded(cm, line) {
- var marks = cm.findMarksAt(Pos(line));
- for (var i = 0; i < marks.length; ++i)
- if (marks[i].__isFold && marks[i].find().from.line == line) return true;
+ var marks = cm.findMarks(Pos(line, 0), Pos(line + 1, 0));
+ for (var i = 0; i < marks.length; ++i) {
+ if (marks[i].__isFold) {
+ var fromPos = marks[i].find(-1);
+ if (fromPos && fromPos.line === line)
+ return marks[i];
+ }
+ }
}
function marker(spec) {
@@ -66,24 +71,36 @@
}
function updateFoldInfo(cm, from, to) {
- var opts = cm.state.foldGutter.options, cur = from;
+ var opts = cm.state.foldGutter.options, cur = from - 1;
var minSize = cm.foldOption(opts, "minFoldSize");
var func = cm.foldOption(opts, "rangeFinder");
+ // we can reuse the built-in indicator element if its className matches the new state
+ var clsFolded = typeof opts.indicatorFolded == "string" && classTest(opts.indicatorFolded);
+ var clsOpen = typeof opts.indicatorOpen == "string" && classTest(opts.indicatorOpen);
cm.eachLine(from, to, function(line) {
+ ++cur;
var mark = null;
+ var old = line.gutterMarkers;
+ if (old) old = old[opts.gutter];
if (isFolded(cm, cur)) {
+ if (clsFolded && old && clsFolded.test(old.className)) return;
mark = marker(opts.indicatorFolded);
} else {
var pos = Pos(cur, 0);
var range = func && func(cm, pos);
- if (range && range.to.line - range.from.line >= minSize)
+ if (range && range.to.line - range.from.line >= minSize) {
+ if (clsOpen && old && clsOpen.test(old.className)) return;
mark = marker(opts.indicatorOpen);
+ }
}
+ if (!mark && !old) return;
cm.setGutterMarker(line, opts.gutter, mark);
- ++cur;
});
}
+ // copied from CodeMirror/src/util/dom.js
+ function classTest(cls) { return new RegExp("(^|\\s)" + cls + "(?:$|\\s)\\s*") }
+
function updateInViewport(cm) {
var vp = cm.getViewport(), state = cm.state.foldGutter;
if (!state) return;
@@ -98,7 +115,9 @@
if (!state) return;
var opts = state.options;
if (gutter != opts.gutter) return;
- cm.foldCode(Pos(line, 0), opts.rangeFinder);
+ var folded = isFolded(cm, line);
+ if (folded) folded.clear();
+ else cm.foldCode(Pos(line, 0), opts);
}
function onChange(cm) {
diff --git a/static/editor.md/lib/codemirror/addon/fold/indent-fold.js b/static/editor.md/lib/codemirror/addon/fold/indent-fold.js
index e29f15e9d..0cc112644 100644
--- a/static/editor.md/lib/codemirror/addon/fold/indent-fold.js
+++ b/static/editor.md/lib/codemirror/addon/fold/indent-fold.js
@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
@@ -11,32 +11,36 @@
})(function(CodeMirror) {
"use strict";
+function lineIndent(cm, lineNo) {
+ var text = cm.getLine(lineNo)
+ var spaceTo = text.search(/\S/)
+ if (spaceTo == -1 || /\bcomment\b/.test(cm.getTokenTypeAt(CodeMirror.Pos(lineNo, spaceTo + 1))))
+ return -1
+ return CodeMirror.countColumn(text, null, cm.getOption("tabSize"))
+}
+
CodeMirror.registerHelper("fold", "indent", function(cm, start) {
- var tabSize = cm.getOption("tabSize"), firstLine = cm.getLine(start.line);
- if (!/\S/.test(firstLine)) return;
- var getIndent = function(line) {
- return CodeMirror.countColumn(line, null, tabSize);
- };
- var myIndent = getIndent(firstLine);
- var lastLineInFold = null;
+ var myIndent = lineIndent(cm, start.line)
+ if (myIndent < 0) return
+ var lastLineInFold = null
+
// Go through lines until we find a line that definitely doesn't belong in
// the block we're folding, or to the end.
for (var i = start.line + 1, end = cm.lastLine(); i <= end; ++i) {
- var curLine = cm.getLine(i);
- var curIndent = getIndent(curLine);
- if (curIndent > myIndent) {
+ var indent = lineIndent(cm, i)
+ if (indent == -1) {
+ } else if (indent > myIndent) {
// Lines with a greater indent are considered part of the block.
lastLineInFold = i;
- } else if (!/\S/.test(curLine)) {
- // Empty lines might be breaks within the block we're trying to fold.
} else {
- // A non-empty line at an indent equal to or less than ours marks the
- // start of another block.
+ // If this line has non-space, non-comment content, and is
+ // indented less or equal to the start line, it is the start of
+ // another block.
break;
}
}
if (lastLineInFold) return {
- from: CodeMirror.Pos(start.line, firstLine.length),
+ from: CodeMirror.Pos(start.line, cm.getLine(start.line).length),
to: CodeMirror.Pos(lastLineInFold, cm.getLine(lastLineInFold).length)
};
});
diff --git a/static/editor.md/lib/codemirror/addon/fold/markdown-fold.js b/static/editor.md/lib/codemirror/addon/fold/markdown-fold.js
index ce84c946c..6a551786d 100644
--- a/static/editor.md/lib/codemirror/addon/fold/markdown-fold.js
+++ b/static/editor.md/lib/codemirror/addon/fold/markdown-fold.js
@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
diff --git a/static/editor.md/lib/codemirror/addon/fold/xml-fold.js b/static/editor.md/lib/codemirror/addon/fold/xml-fold.js
index 504727f38..13bc3838b 100644
--- a/static/editor.md/lib/codemirror/addon/fold/xml-fold.js
+++ b/static/editor.md/lib/codemirror/addon/fold/xml-fold.js
@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
@@ -21,8 +21,8 @@
function Iter(cm, line, ch, range) {
this.line = line; this.ch = ch;
this.cm = cm; this.text = cm.getLine(line);
- this.min = range ? range.from : cm.firstLine();
- this.max = range ? range.to - 1 : cm.lastLine();
+ this.min = range ? Math.max(range.from, cm.firstLine()) : cm.firstLine();
+ this.max = range ? Math.min(range.to - 1, cm.lastLine()) : cm.lastLine();
}
function tagAt(iter, ch) {
@@ -137,12 +137,14 @@
CodeMirror.registerHelper("fold", "xml", function(cm, start) {
var iter = new Iter(cm, start.line, 0);
for (;;) {
- var openTag = toNextTag(iter), end;
- if (!openTag || iter.line != start.line || !(end = toTagEnd(iter))) return;
+ var openTag = toNextTag(iter)
+ if (!openTag || iter.line != start.line) return
+ var end = toTagEnd(iter)
+ if (!end) return
if (!openTag[1] && end != "selfClose") {
- var start = Pos(iter.line, iter.ch);
- var close = findMatchingClose(iter, openTag[2]);
- return close && {from: start, to: close.from};
+ var startPos = Pos(iter.line, iter.ch);
+ var endPos = findMatchingClose(iter, openTag[2]);
+ return endPos && cmp(endPos.from, startPos) > 0 ? {from: startPos, to: endPos.from} : null
}
}
});
@@ -163,10 +165,10 @@
}
};
- CodeMirror.findEnclosingTag = function(cm, pos, range) {
+ CodeMirror.findEnclosingTag = function(cm, pos, range, tag) {
var iter = new Iter(cm, pos.line, pos.ch, range);
for (;;) {
- var open = findMatchingOpen(iter);
+ var open = findMatchingOpen(iter, tag);
if (!open) break;
var forward = new Iter(cm, pos.line, pos.ch, range);
var close = findMatchingClose(forward, open.tag);
diff --git a/static/editor.md/lib/codemirror/addon/hint/anyword-hint.js b/static/editor.md/lib/codemirror/addon/hint/anyword-hint.js
index 8e74a9208..d27a9ec01 100644
--- a/static/editor.md/lib/codemirror/addon/hint/anyword-hint.js
+++ b/static/editor.md/lib/codemirror/addon/hint/anyword-hint.js
@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
@@ -21,7 +21,7 @@
while (start && word.test(curLine.charAt(start - 1))) --start;
var curWord = start != end && curLine.slice(start, end);
- var list = [], seen = {};
+ var list = options && options.list || [], seen = {};
var re = new RegExp(word.source, "g");
for (var dir = -1; dir <= 1; dir += 2) {
var line = cur.line, endLine = Math.min(Math.max(line + dir * range, editor.firstLine()), editor.lastLine()) + dir;
diff --git a/static/editor.md/lib/codemirror/addon/hint/css-hint.js b/static/editor.md/lib/codemirror/addon/hint/css-hint.js
index 488da3449..980d1197e 100644
--- a/static/editor.md/lib/codemirror/addon/hint/css-hint.js
+++ b/static/editor.md/lib/codemirror/addon/hint/css-hint.js
@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
@@ -11,15 +11,25 @@
})(function(CodeMirror) {
"use strict";
- var pseudoClasses = {link: 1, visited: 1, active: 1, hover: 1, focus: 1,
- "first-letter": 1, "first-line": 1, "first-child": 1,
- before: 1, after: 1, lang: 1};
+ var pseudoClasses = {"active":1, "after":1, "before":1, "checked":1, "default":1,
+ "disabled":1, "empty":1, "enabled":1, "first-child":1, "first-letter":1,
+ "first-line":1, "first-of-type":1, "focus":1, "hover":1, "in-range":1,
+ "indeterminate":1, "invalid":1, "lang":1, "last-child":1, "last-of-type":1,
+ "link":1, "not":1, "nth-child":1, "nth-last-child":1, "nth-last-of-type":1,
+ "nth-of-type":1, "only-of-type":1, "only-child":1, "optional":1, "out-of-range":1,
+ "placeholder":1, "read-only":1, "read-write":1, "required":1, "root":1,
+ "selection":1, "target":1, "valid":1, "visited":1
+ };
CodeMirror.registerHelper("hint", "css", function(cm) {
var cur = cm.getCursor(), token = cm.getTokenAt(cur);
var inner = CodeMirror.innerMode(cm.getMode(), token.state);
if (inner.mode.name != "css") return;
+ if (token.type == "keyword" && "!important".indexOf(token.string) == 0)
+ return {list: ["!important"], from: CodeMirror.Pos(cur.line, token.start),
+ to: CodeMirror.Pos(cur.line, token.end)};
+
var start = token.start, end = cur.ch, word = token.string.slice(0, end - start);
if (/[^\w$_-]/.test(word)) {
word = ""; start = end = cur.ch;
diff --git a/static/editor.md/lib/codemirror/addon/hint/html-hint.js b/static/editor.md/lib/codemirror/addon/hint/html-hint.js
index c6769bcae..9878eca6e 100644
--- a/static/editor.md/lib/codemirror/addon/hint/html-hint.js
+++ b/static/editor.md/lib/codemirror/addon/hint/html-hint.js
@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
@@ -98,6 +98,7 @@
dfn: s,
dir: s,
div: s,
+ dialog: { attrs: { open: null } },
dl: s,
dt: s,
em: s,
@@ -322,6 +323,8 @@
itemtype: null,
lang: ["en", "es"],
spellcheck: ["true", "false"],
+ autocorrect: ["true", "false"],
+ autocapitalize: ["true", "false"],
style: null,
tabindex: ["1", "2", "3", "4", "5", "6", "7", "8", "9"],
title: null,
diff --git a/static/editor.md/lib/codemirror/addon/hint/show-hint.css b/static/editor.md/lib/codemirror/addon/hint/show-hint.css
index 924e638f7..5617ccca2 100644
--- a/static/editor.md/lib/codemirror/addon/hint/show-hint.css
+++ b/static/editor.md/lib/codemirror/addon/hint/show-hint.css
@@ -25,8 +25,6 @@
margin: 0;
padding: 0 4px;
border-radius: 2px;
- max-width: 19em;
- overflow: hidden;
white-space: pre;
color: black;
cursor: pointer;
diff --git a/static/editor.md/lib/codemirror/addon/hint/show-hint.js b/static/editor.md/lib/codemirror/addon/hint/show-hint.js
index f54461942..2be2c716e 100644
--- a/static/editor.md/lib/codemirror/addon/hint/show-hint.js
+++ b/static/editor.md/lib/codemirror/addon/hint/show-hint.js
@@ -1,5 +1,7 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
+
+// declare global: DOMRect
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
@@ -24,44 +26,63 @@
return cm.showHint(newOpts);
};
- var asyncRunID = 0;
- function retrieveHints(getter, cm, options, then) {
- if (getter.async) {
- var id = ++asyncRunID;
- getter(cm, function(hints) {
- if (asyncRunID == id) then(hints);
- }, options);
- } else {
- then(getter(cm, options));
- }
- }
-
CodeMirror.defineExtension("showHint", function(options) {
- // We want a single cursor position.
- if (this.listSelections().length > 1 || this.somethingSelected()) return;
+ options = parseOptions(this, this.getCursor("start"), options);
+ var selections = this.listSelections()
+ if (selections.length > 1) return;
+ // By default, don't allow completion when something is selected.
+ // A hint function can have a `supportsSelection` property to
+ // indicate that it can handle selections.
+ if (this.somethingSelected()) {
+ if (!options.hint.supportsSelection) return;
+ // Don't try with cross-line selections
+ for (var i = 0; i < selections.length; i++)
+ if (selections[i].head.line != selections[i].anchor.line) return;
+ }
if (this.state.completionActive) this.state.completionActive.close();
var completion = this.state.completionActive = new Completion(this, options);
- var getHints = completion.options.hint;
- if (!getHints) return;
+ if (!completion.options.hint) return;
CodeMirror.signal(this, "startCompletion", this);
- return retrieveHints(getHints, this, completion.options, function(hints) { completion.showHints(hints); });
+ completion.update(true);
});
+ CodeMirror.defineExtension("closeHint", function() {
+ if (this.state.completionActive) this.state.completionActive.close()
+ })
+
function Completion(cm, options) {
this.cm = cm;
- this.options = this.buildOptions(options);
- this.widget = this.onClose = null;
+ this.options = options;
+ this.widget = null;
+ this.debounce = 0;
+ this.tick = 0;
+ this.startPos = this.cm.getCursor("start");
+ this.startLen = this.cm.getLine(this.startPos.line).length - this.cm.getSelection().length;
+
+ if (this.options.updateOnCursorActivity) {
+ var self = this;
+ cm.on("cursorActivity", this.activityFunc = function() { self.cursorActivity(); });
+ }
}
+ var requestAnimationFrame = window.requestAnimationFrame || function(fn) {
+ return setTimeout(fn, 1000/60);
+ };
+ var cancelAnimationFrame = window.cancelAnimationFrame || clearTimeout;
+
Completion.prototype = {
close: function() {
if (!this.active()) return;
this.cm.state.completionActive = null;
+ this.tick = null;
+ if (this.options.updateOnCursorActivity) {
+ this.cm.off("cursorActivity", this.activityFunc);
+ }
+ if (this.widget && this.data) CodeMirror.signal(this.data, "close");
if (this.widget) this.widget.close();
- if (this.onClose) this.onClose();
CodeMirror.signal(this.cm, "endCompletion", this.cm);
},
@@ -70,92 +91,83 @@
},
pick: function(data, i) {
- var completion = data.list[i];
- if (completion.hint) completion.hint(this.cm, data, completion);
- else this.cm.replaceRange(getText(completion), completion.from || data.from,
- completion.to || data.to, "complete");
- CodeMirror.signal(data, "pick", completion);
- this.close();
+ var completion = data.list[i], self = this;
+ this.cm.operation(function() {
+ if (completion.hint)
+ completion.hint(self.cm, data, completion);
+ else
+ self.cm.replaceRange(getText(completion), completion.from || data.from,
+ completion.to || data.to, "complete");
+ CodeMirror.signal(data, "pick", completion);
+ self.cm.scrollIntoView();
+ });
+ if (this.options.closeOnPick) {
+ this.close();
+ }
},
- showHints: function(data) {
- if (!data || !data.list.length || !this.active()) return this.close();
+ cursorActivity: function() {
+ if (this.debounce) {
+ cancelAnimationFrame(this.debounce);
+ this.debounce = 0;
+ }
- if (this.options.completeSingle && data.list.length == 1)
- this.pick(data, 0);
- else
- this.showWidget(data);
- },
+ var identStart = this.startPos;
+ if(this.data) {
+ identStart = this.data.from;
+ }
- showWidget: function(data) {
- this.widget = new Widget(this, data);
- CodeMirror.signal(data, "shown");
+ var pos = this.cm.getCursor(), line = this.cm.getLine(pos.line);
+ if (pos.line != this.startPos.line || line.length - pos.ch != this.startLen - this.startPos.ch ||
+ pos.ch < identStart.ch || this.cm.somethingSelected() ||
+ (!pos.ch || this.options.closeCharacters.test(line.charAt(pos.ch - 1)))) {
+ this.close();
+ } else {
+ var self = this;
+ this.debounce = requestAnimationFrame(function() {self.update();});
+ if (this.widget) this.widget.disable();
+ }
+ },
- var debounce = 0, completion = this, finished;
- var closeOn = this.options.closeCharacters;
- var startPos = this.cm.getCursor(), startLen = this.cm.getLine(startPos.line).length;
+ update: function(first) {
+ if (this.tick == null) return
+ var self = this, myTick = ++this.tick
+ fetchHints(this.options.hint, this.cm, this.options, function(data) {
+ if (self.tick == myTick) self.finishUpdate(data, first)
+ })
+ },
- var requestAnimationFrame = window.requestAnimationFrame || function(fn) {
- return setTimeout(fn, 1000/60);
- };
- var cancelAnimationFrame = window.cancelAnimationFrame || clearTimeout;
-
- function done() {
- if (finished) return;
- finished = true;
- completion.close();
- completion.cm.off("cursorActivity", activity);
- if (data) CodeMirror.signal(data, "close");
- }
+ finishUpdate: function(data, first) {
+ if (this.data) CodeMirror.signal(this.data, "update");
- function update() {
- if (finished) return;
- CodeMirror.signal(data, "update");
- retrieveHints(completion.options.hint, completion.cm, completion.options, finishUpdate);
- }
- function finishUpdate(data_) {
- data = data_;
- if (finished) return;
- if (!data || !data.list.length) return done();
- if (completion.widget) completion.widget.close();
- completion.widget = new Widget(completion, data);
- }
+ var picked = (this.widget && this.widget.picked) || (first && this.options.completeSingle);
+ if (this.widget) this.widget.close();
- function clearDebounce() {
- if (debounce) {
- cancelAnimationFrame(debounce);
- debounce = 0;
- }
- }
+ this.data = data;
- function activity() {
- clearDebounce();
- var pos = completion.cm.getCursor(), line = completion.cm.getLine(pos.line);
- if (pos.line != startPos.line || line.length - pos.ch != startLen - startPos.ch ||
- pos.ch < startPos.ch || completion.cm.somethingSelected() ||
- (pos.ch && closeOn.test(line.charAt(pos.ch - 1)))) {
- completion.close();
+ if (data && data.list.length) {
+ if (picked && data.list.length == 1) {
+ this.pick(data, 0);
} else {
- debounce = requestAnimationFrame(update);
- if (completion.widget) completion.widget.close();
+ this.widget = new Widget(this, data);
+ CodeMirror.signal(data, "shown");
}
}
- this.cm.on("cursorActivity", activity);
- this.onClose = done;
- },
-
- buildOptions: function(options) {
- var editor = this.cm.options.hintOptions;
- var out = {};
- for (var prop in defaultOptions) out[prop] = defaultOptions[prop];
- if (editor) for (var prop in editor)
- if (editor[prop] !== undefined) out[prop] = editor[prop];
- if (options) for (var prop in options)
- if (options[prop] !== undefined) out[prop] = options[prop];
- return out;
}
};
+ function parseOptions(cm, pos, options) {
+ var editor = cm.options.hintOptions;
+ var out = {};
+ for (var prop in defaultOptions) out[prop] = defaultOptions[prop];
+ if (editor) for (var prop in editor)
+ if (editor[prop] !== undefined) out[prop] = editor[prop];
+ if (options) for (var prop in options)
+ if (options[prop] !== undefined) out[prop] = options[prop];
+ if (out.hint.resolve) out.hint = out.hint.resolve(cm, pos)
+ return out;
+ }
+
function getText(completion) {
if (typeof completion == "string") return completion;
else return completion.text;
@@ -173,6 +185,14 @@
Tab: handle.pick,
Esc: handle.close
};
+
+ var mac = /Mac/.test(navigator.platform);
+
+ if (mac) {
+ baseMap["Ctrl-P"] = function() {handle.moveFocus(-1);};
+ baseMap["Ctrl-N"] = function() {handle.moveFocus(1);};
+ }
+
var custom = completion.options.customKeys;
var ourMap = custom ? {} : baseMap;
function addBinding(key, val) {
@@ -204,58 +224,95 @@
}
function Widget(completion, data) {
+ this.id = "cm-complete-" + Math.floor(Math.random(1e6))
this.completion = completion;
this.data = data;
+ this.picked = false;
var widget = this, cm = completion.cm;
-
- var hints = this.hints = document.createElement("ul");
- hints.className = "CodeMirror-hints";
+ var ownerDocument = cm.getInputField().ownerDocument;
+ var parentWindow = ownerDocument.defaultView || ownerDocument.parentWindow;
+
+ var hints = this.hints = ownerDocument.createElement("ul");
+ hints.setAttribute("role", "listbox")
+ hints.setAttribute("aria-expanded", "true")
+ hints.id = this.id
+ var theme = completion.cm.options.theme;
+ hints.className = "CodeMirror-hints " + theme;
this.selectedHint = data.selectedHint || 0;
var completions = data.list;
for (var i = 0; i < completions.length; ++i) {
- var elt = hints.appendChild(document.createElement("li")), cur = completions[i];
+ var elt = hints.appendChild(ownerDocument.createElement("li")), cur = completions[i];
var className = HINT_ELEMENT_CLASS + (i != this.selectedHint ? "" : " " + ACTIVE_HINT_ELEMENT_CLASS);
if (cur.className != null) className = cur.className + " " + className;
elt.className = className;
+ if (i == this.selectedHint) elt.setAttribute("aria-selected", "true")
+ elt.id = this.id + "-" + i
+ elt.setAttribute("role", "option")
if (cur.render) cur.render(elt, data, cur);
- else elt.appendChild(document.createTextNode(cur.displayText || getText(cur)));
+ else elt.appendChild(ownerDocument.createTextNode(cur.displayText || getText(cur)));
elt.hintId = i;
}
+ var container = completion.options.container || ownerDocument.body;
var pos = cm.cursorCoords(completion.options.alignWithWord ? data.from : null);
var left = pos.left, top = pos.bottom, below = true;
- hints.style.left = left + "px";
- hints.style.top = top + "px";
+ var offsetLeft = 0, offsetTop = 0;
+ if (container !== ownerDocument.body) {
+ // We offset the cursor position because left and top are relative to the offsetParent's top left corner.
+ var isContainerPositioned = ['absolute', 'relative', 'fixed'].indexOf(parentWindow.getComputedStyle(container).position) !== -1;
+ var offsetParent = isContainerPositioned ? container : container.offsetParent;
+ var offsetParentPosition = offsetParent.getBoundingClientRect();
+ var bodyPosition = ownerDocument.body.getBoundingClientRect();
+ offsetLeft = (offsetParentPosition.left - bodyPosition.left - offsetParent.scrollLeft);
+ offsetTop = (offsetParentPosition.top - bodyPosition.top - offsetParent.scrollTop);
+ }
+ hints.style.left = (left - offsetLeft) + "px";
+ hints.style.top = (top - offsetTop) + "px";
+
// If we're at the edge of the screen, then we want the menu to appear on the left of the cursor.
- var winW = window.innerWidth || Math.max(document.body.offsetWidth, document.documentElement.offsetWidth);
- var winH = window.innerHeight || Math.max(document.body.offsetHeight, document.documentElement.offsetHeight);
- (completion.options.container || document.body).appendChild(hints);
- var box = hints.getBoundingClientRect(), overlapY = box.bottom - winH;
+ var winW = parentWindow.innerWidth || Math.max(ownerDocument.body.offsetWidth, ownerDocument.documentElement.offsetWidth);
+ var winH = parentWindow.innerHeight || Math.max(ownerDocument.body.offsetHeight, ownerDocument.documentElement.offsetHeight);
+ container.appendChild(hints);
+ cm.getInputField().setAttribute("aria-autocomplete", "list")
+ cm.getInputField().setAttribute("aria-owns", this.id)
+ cm.getInputField().setAttribute("aria-activedescendant", this.id + "-" + this.selectedHint)
+
+ var box = completion.options.moveOnOverlap ? hints.getBoundingClientRect() : new DOMRect();
+ var scrolls = completion.options.paddingForScrollbar ? hints.scrollHeight > hints.clientHeight + 1 : false;
+
+ // Compute in the timeout to avoid reflow on init
+ var startScroll;
+ setTimeout(function() { startScroll = cm.getScrollInfo(); });
+
+ var overlapY = box.bottom - winH;
if (overlapY > 0) {
var height = box.bottom - box.top, curTop = pos.top - (pos.bottom - box.top);
if (curTop - height > 0) { // Fits above cursor
- hints.style.top = (top = pos.top - height) + "px";
+ hints.style.top = (top = pos.top - height - offsetTop) + "px";
below = false;
} else if (height > winH) {
hints.style.height = (winH - 5) + "px";
- hints.style.top = (top = pos.bottom - box.top) + "px";
+ hints.style.top = (top = pos.bottom - box.top - offsetTop) + "px";
var cursor = cm.getCursor();
if (data.from.ch != cursor.ch) {
pos = cm.cursorCoords(cursor);
- hints.style.left = (left = pos.left) + "px";
+ hints.style.left = (left = pos.left - offsetLeft) + "px";
box = hints.getBoundingClientRect();
}
}
}
var overlapX = box.right - winW;
+ if (scrolls) overlapX += cm.display.nativeBarWidth;
if (overlapX > 0) {
if (box.right - box.left > winW) {
hints.style.width = (winW - 5) + "px";
overlapX -= (box.right - box.left) - winW;
}
- hints.style.left = (left = pos.left - overlapX) + "px";
+ hints.style.left = (left = pos.left - overlapX - offsetLeft) + "px";
}
+ if (scrolls) for (var node = hints.firstChild; node; node = node.nextSibling)
+ node.style.paddingRight = cm.display.nativeBarWidth + "px"
cm.addKeyMap(this.keyMap = buildKeyMap(completion, {
moveFocus: function(n, avoidWrap) { widget.changeActive(widget.selectedHint + n, avoidWrap); },
@@ -273,11 +330,11 @@
cm.on("focus", this.onFocus = function() { clearTimeout(closingOnBlur); });
}
- var startScroll = cm.getScrollInfo();
cm.on("scroll", this.onScroll = function() {
var curScroll = cm.getScrollInfo(), editor = cm.getWrapperElement().getBoundingClientRect();
+ if (!startScroll) startScroll = cm.getScrollInfo();
var newTop = top + startScroll.top - curScroll.top;
- var point = newTop - (window.pageYOffset || (document.documentElement || document.body).scrollTop);
+ var point = newTop - (parentWindow.pageYOffset || (ownerDocument.documentElement || ownerDocument.body).scrollTop);
if (!below) point += hints.offsetHeight;
if (point <= editor.top || point >= editor.bottom) return completion.close();
hints.style.top = newTop + "px";
@@ -301,7 +358,13 @@
setTimeout(function(){cm.focus();}, 20);
});
- CodeMirror.signal(data, "select", completions[0], hints.firstChild);
+ // The first hint doesn't need to be scrolled to on init
+ var selectedHintRange = this.getSelectedHintRange();
+ if (selectedHintRange.from !== 0 || selectedHintRange.to !== 0) {
+ this.scrollToActive();
+ }
+
+ CodeMirror.signal(data, "select", completions[this.selectedHint], hints.childNodes[this.selectedHint]);
return true;
}
@@ -309,8 +372,11 @@
close: function() {
if (this.completion.widget != this) return;
this.completion.widget = null;
- this.hints.parentNode.removeChild(this.hints);
+ if (this.hints.parentNode) this.hints.parentNode.removeChild(this.hints);
this.completion.cm.removeKeyMap(this.keyMap);
+ var input = this.completion.cm.getInputField()
+ input.removeAttribute("aria-activedescendant")
+ input.removeAttribute("aria-owns")
var cm = this.completion.cm;
if (this.completion.options.closeOnUnfocus) {
@@ -320,6 +386,13 @@
cm.off("scroll", this.onScroll);
},
+ disable: function() {
+ this.completion.cm.removeKeyMap(this.keyMap);
+ var widget = this;
+ this.keyMap = {Enter: function() { widget.picked = true; }};
+ this.completion.cm.addKeyMap(this.keyMap);
+ },
+
pick: function() {
this.completion.pick(this.data, this.selectedHint);
},
@@ -331,49 +404,107 @@
i = avoidWrap ? 0 : this.data.list.length - 1;
if (this.selectedHint == i) return;
var node = this.hints.childNodes[this.selectedHint];
- node.className = node.className.replace(" " + ACTIVE_HINT_ELEMENT_CLASS, "");
+ if (node) {
+ node.className = node.className.replace(" " + ACTIVE_HINT_ELEMENT_CLASS, "");
+ node.removeAttribute("aria-selected")
+ }
node = this.hints.childNodes[this.selectedHint = i];
node.className += " " + ACTIVE_HINT_ELEMENT_CLASS;
- if (node.offsetTop < this.hints.scrollTop)
- this.hints.scrollTop = node.offsetTop - 3;
- else if (node.offsetTop + node.offsetHeight > this.hints.scrollTop + this.hints.clientHeight)
- this.hints.scrollTop = node.offsetTop + node.offsetHeight - this.hints.clientHeight + 3;
+ node.setAttribute("aria-selected", "true")
+ this.completion.cm.getInputField().setAttribute("aria-activedescendant", node.id)
+ this.scrollToActive()
CodeMirror.signal(this.data, "select", this.data.list[this.selectedHint], node);
},
+ scrollToActive: function() {
+ var selectedHintRange = this.getSelectedHintRange();
+ var node1 = this.hints.childNodes[selectedHintRange.from];
+ var node2 = this.hints.childNodes[selectedHintRange.to];
+ var firstNode = this.hints.firstChild;
+ if (node1.offsetTop < this.hints.scrollTop)
+ this.hints.scrollTop = node1.offsetTop - firstNode.offsetTop;
+ else if (node2.offsetTop + node2.offsetHeight > this.hints.scrollTop + this.hints.clientHeight)
+ this.hints.scrollTop = node2.offsetTop + node2.offsetHeight - this.hints.clientHeight + firstNode.offsetTop;
+ },
+
screenAmount: function() {
return Math.floor(this.hints.clientHeight / this.hints.firstChild.offsetHeight) || 1;
+ },
+
+ getSelectedHintRange: function() {
+ var margin = this.completion.options.scrollMargin || 0;
+ return {
+ from: Math.max(0, this.selectedHint - margin),
+ to: Math.min(this.data.list.length - 1, this.selectedHint + margin),
+ };
}
};
- CodeMirror.registerHelper("hint", "auto", function(cm, options) {
- var helpers = cm.getHelpers(cm.getCursor(), "hint"), words;
+ function applicableHelpers(cm, helpers) {
+ if (!cm.somethingSelected()) return helpers
+ var result = []
+ for (var i = 0; i < helpers.length; i++)
+ if (helpers[i].supportsSelection) result.push(helpers[i])
+ return result
+ }
+
+ function fetchHints(hint, cm, options, callback) {
+ if (hint.async) {
+ hint(cm, callback, options)
+ } else {
+ var result = hint(cm, options)
+ if (result && result.then) result.then(callback)
+ else callback(result)
+ }
+ }
+
+ function resolveAutoHints(cm, pos) {
+ var helpers = cm.getHelpers(pos, "hint"), words
if (helpers.length) {
- for (var i = 0; i < helpers.length; i++) {
- var cur = helpers[i](cm, options);
- if (cur && cur.list.length) return cur;
+ var resolved = function(cm, callback, options) {
+ var app = applicableHelpers(cm, helpers);
+ function run(i) {
+ if (i == app.length) return callback(null)
+ fetchHints(app[i], cm, options, function(result) {
+ if (result && result.list.length > 0) callback(result)
+ else run(i + 1)
+ })
+ }
+ run(0)
}
+ resolved.async = true
+ resolved.supportsSelection = true
+ return resolved
} else if (words = cm.getHelper(cm.getCursor(), "hintWords")) {
- if (words) return CodeMirror.hint.fromList(cm, {words: words});
+ return function(cm) { return CodeMirror.hint.fromList(cm, {words: words}) }
} else if (CodeMirror.hint.anyword) {
- return CodeMirror.hint.anyword(cm, options);
+ return function(cm, options) { return CodeMirror.hint.anyword(cm, options) }
+ } else {
+ return function() {}
}
+ }
+
+ CodeMirror.registerHelper("hint", "auto", {
+ resolve: resolveAutoHints
});
CodeMirror.registerHelper("hint", "fromList", function(cm, options) {
- var cur = cm.getCursor(), token = cm.getTokenAt(cur);
+ var cur = cm.getCursor(), token = cm.getTokenAt(cur)
+ var term, from = CodeMirror.Pos(cur.line, token.start), to = cur
+ if (token.start < cur.ch && /\w/.test(token.string.charAt(cur.ch - token.start - 1))) {
+ term = token.string.substr(0, cur.ch - token.start)
+ } else {
+ term = ""
+ from = cur
+ }
var found = [];
for (var i = 0; i < options.words.length; i++) {
var word = options.words[i];
- if (word.slice(0, token.string.length) == token.string)
+ if (word.slice(0, term.length) == term)
found.push(word);
}
- if (found.length) return {
- list: found,
- from: CodeMirror.Pos(cur.line, token.start),
- to: CodeMirror.Pos(cur.line, token.end)
- };
+ if (found.length) return {list: found, from: from, to: to};
});
CodeMirror.commands.autocomplete = CodeMirror.showHint;
@@ -383,11 +514,15 @@
completeSingle: true,
alignWithWord: true,
closeCharacters: /[\s()\[\]{};:>,]/,
+ closeOnPick: true,
closeOnUnfocus: true,
- completeOnSingleClick: false,
+ updateOnCursorActivity: true,
+ completeOnSingleClick: true,
container: null,
customKeys: null,
- extraKeys: null
+ extraKeys: null,
+ paddingForScrollbar: true,
+ moveOnOverlap: true,
};
CodeMirror.defineOption("hintOptions", null);
diff --git a/static/editor.md/lib/codemirror/addon/hint/sql-hint.js b/static/editor.md/lib/codemirror/addon/hint/sql-hint.js
index 5b0cc7696..efdce813c 100644
--- a/static/editor.md/lib/codemirror/addon/hint/sql-hint.js
+++ b/static/editor.md/lib/codemirror/addon/hint/sql-hint.js
@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
@@ -14,11 +14,14 @@
var tables;
var defaultTable;
var keywords;
+ var identifierQuote;
var CONS = {
QUERY_DIV: ";",
ALIAS_KEYWORD: "AS"
};
- var Pos = CodeMirror.Pos;
+ var Pos = CodeMirror.Pos, cmpPos = CodeMirror.cmpPos;
+
+ function isArray(val) { return Object.prototype.toString.call(val) == "[object Array]" }
function getKeywords(editor) {
var mode = editor.doc.modeOption;
@@ -26,14 +29,38 @@
return CodeMirror.resolveMode(mode).keywords;
}
+ function getIdentifierQuote(editor) {
+ var mode = editor.doc.modeOption;
+ if (mode === "sql") mode = "text/x-sql";
+ return CodeMirror.resolveMode(mode).identifierQuote || "`";
+ }
+
function getText(item) {
return typeof item == "string" ? item : item.text;
}
- function getItem(list, item) {
- if (!list.slice) return list[item];
- for (var i = list.length - 1; i >= 0; i--) if (getText(list[i]) == item)
- return list[i];
+ function wrapTable(name, value) {
+ if (isArray(value)) value = {columns: value}
+ if (!value.text) value.text = name
+ return value
+ }
+
+ function parseTables(input) {
+ var result = {}
+ if (isArray(input)) {
+ for (var i = input.length - 1; i >= 0; i--) {
+ var item = input[i]
+ result[getText(item).toUpperCase()] = wrapTable(getText(item), item)
+ }
+ } else if (input) {
+ for (var name in input)
+ result[name.toUpperCase()] = wrapTable(name, input[name])
+ }
+ return result
+ }
+
+ function getTable(name) {
+ return tables[name.toUpperCase()]
}
function shallowClone(object) {
@@ -50,29 +77,41 @@
}
function addMatches(result, search, wordlist, formatter) {
- for (var word in wordlist) {
- if (!wordlist.hasOwnProperty(word)) continue;
- if (Array.isArray(wordlist)) {
- word = wordlist[word];
- }
- if (match(search, word)) {
- result.push(formatter(word));
+ if (isArray(wordlist)) {
+ for (var i = 0; i < wordlist.length; i++)
+ if (match(search, wordlist[i])) result.push(formatter(wordlist[i]))
+ } else {
+ for (var word in wordlist) if (wordlist.hasOwnProperty(word)) {
+ var val = wordlist[word]
+ if (!val || val === true)
+ val = word
+ else
+ val = val.displayText ? {text: val.text, displayText: val.displayText} : val.text
+ if (match(search, val)) result.push(formatter(val))
}
}
}
function cleanName(name) {
- // Get rid name from backticks(`) and preceding dot(.)
+ // Get rid name from identifierQuote and preceding dot(.)
if (name.charAt(0) == ".") {
name = name.substr(1);
}
- return name.replace(/`/g, "");
+ // replace duplicated identifierQuotes with single identifierQuotes
+ // and remove single identifierQuotes
+ var nameParts = name.split(identifierQuote+identifierQuote);
+ for (var i = 0; i < nameParts.length; i++)
+ nameParts[i] = nameParts[i].replace(new RegExp(identifierQuote,"g"), "");
+ return nameParts.join(identifierQuote);
}
- function insertBackticks(name) {
+ function insertIdentifierQuotes(name) {
var nameParts = getText(name).split(".");
for (var i = 0; i < nameParts.length; i++)
- nameParts[i] = "`" + nameParts[i] + "`";
+ nameParts[i] = identifierQuote +
+ // duplicate identifierQuotes
+ nameParts[i].replace(new RegExp(identifierQuote,"g"), identifierQuote+identifierQuote) +
+ identifierQuote;
var escaped = nameParts.join(".");
if (typeof name == "string") return escaped;
name = shallowClone(name);
@@ -81,14 +120,14 @@
}
function nameCompletion(cur, token, result, editor) {
- // Try to complete table, colunm names and return start position of completion
- var useBacktick = false;
+ // Try to complete table, column names and return start position of completion
+ var useIdentifierQuotes = false;
var nameParts = [];
var start = token.start;
var cont = true;
while (cont) {
cont = (token.string.charAt(0) == ".");
- useBacktick = useBacktick || (token.string.charAt(0) == "`");
+ useIdentifierQuotes = useIdentifierQuotes || (token.string.charAt(0) == identifierQuote);
start = token.start;
nameParts.unshift(cleanName(token.string));
@@ -103,35 +142,42 @@
// Try to complete table names
var string = nameParts.join(".");
addMatches(result, string, tables, function(w) {
- return useBacktick ? insertBackticks(w) : w;
+ return useIdentifierQuotes ? insertIdentifierQuotes(w) : w;
});
// Try to complete columns from defaultTable
addMatches(result, string, defaultTable, function(w) {
- return useBacktick ? insertBackticks(w) : w;
+ return useIdentifierQuotes ? insertIdentifierQuotes(w) : w;
});
// Try to complete columns
string = nameParts.pop();
var table = nameParts.join(".");
+ var alias = false;
+ var aliasTable = table;
// Check if table is available. If not, find table by Alias
- if (!getItem(tables, table))
+ if (!getTable(table)) {
+ var oldTable = table;
table = findTableByAlias(table, editor);
+ if (table !== oldTable) alias = true;
+ }
- var columns = getItem(tables, table);
- if (columns && Array.isArray(tables) && columns.columns)
+ var columns = getTable(table);
+ if (columns && columns.columns)
columns = columns.columns;
if (columns) {
addMatches(result, string, columns, function(w) {
+ var tableInsert = table;
+ if (alias == true) tableInsert = aliasTable;
if (typeof w == "string") {
- w = table + "." + w;
+ w = tableInsert + "." + w;
} else {
w = shallowClone(w);
- w.text = table + "." + w.text;
+ w.text = tableInsert + "." + w.text;
}
- return useBacktick ? insertBackticks(w) : w;
+ return useIdentifierQuotes ? insertIdentifierQuotes(w) : w;
});
}
@@ -139,21 +185,9 @@
}
function eachWord(lineText, f) {
- if (!lineText) return;
- var excepted = /[,;]/g;
- var words = lineText.split(" ");
- for (var i = 0; i < words.length; i++) {
- f(words[i]?words[i].replace(excepted, '') : '');
- }
- }
-
- function convertCurToNumber(cur) {
- // max characters of a line is 999,999.
- return cur.line + cur.ch / Math.pow(10, 6);
- }
-
- function convertNumberToCur(num) {
- return Pos(Math.floor(num), +num.toString().split('.').pop());
+ var words = lineText.split(/\s+/)
+ for (var i = 0; i < words.length; i++)
+ if (words[i]) f(words[i].replace(/[`,;]/g, ''))
}
function findTableByAlias(alias, editor) {
@@ -178,38 +212,49 @@
separator.push(Pos(editor.lastLine(), editor.getLineHandle(editor.lastLine()).text.length));
//find valid range
- var prevItem = 0;
- var current = convertCurToNumber(editor.getCursor());
- for (var i=0; i< separator.length; i++) {
- var _v = convertCurToNumber(separator[i]);
- if (current > prevItem && current <= _v) {
- validRange = { start: convertNumberToCur(prevItem), end: convertNumberToCur(_v) };
+ var prevItem = null;
+ var current = editor.getCursor()
+ for (var i = 0; i < separator.length; i++) {
+ if ((prevItem == null || cmpPos(current, prevItem) > 0) && cmpPos(current, separator[i]) <= 0) {
+ validRange = {start: prevItem, end: separator[i]};
break;
}
- prevItem = _v;
+ prevItem = separator[i];
}
- var query = doc.getRange(validRange.start, validRange.end, false);
-
- for (var i = 0; i < query.length; i++) {
- var lineText = query[i];
- eachWord(lineText, function(word) {
- var wordUpperCase = word.toUpperCase();
- if (wordUpperCase === aliasUpperCase && getItem(tables, previousWord))
- table = previousWord;
- if (wordUpperCase !== CONS.ALIAS_KEYWORD)
- previousWord = word;
- });
- if (table) break;
+ if (validRange.start) {
+ var query = doc.getRange(validRange.start, validRange.end, false);
+
+ for (var i = 0; i < query.length; i++) {
+ var lineText = query[i];
+ eachWord(lineText, function(word) {
+ var wordUpperCase = word.toUpperCase();
+ if (wordUpperCase === aliasUpperCase && getTable(previousWord))
+ table = previousWord;
+ if (wordUpperCase !== CONS.ALIAS_KEYWORD)
+ previousWord = word;
+ });
+ if (table) break;
+ }
}
return table;
}
CodeMirror.registerHelper("hint", "sql", function(editor, options) {
- tables = (options && options.tables) || {};
+ tables = parseTables(options && options.tables)
var defaultTableName = options && options.defaultTable;
- defaultTable = (defaultTableName && getItem(tables, defaultTableName)) || [];
- keywords = keywords || getKeywords(editor);
+ var disableKeywords = options && options.disableKeywords;
+ defaultTable = defaultTableName && getTable(defaultTableName);
+ keywords = getKeywords(editor);
+ identifierQuote = getIdentifierQuote(editor);
+
+ if (defaultTableName && !defaultTable)
+ defaultTable = findTableByAlias(defaultTableName, editor);
+
+ defaultTable = defaultTable || [];
+
+ if (defaultTable.columns)
+ defaultTable = defaultTable.columns;
var cur = editor.getCursor();
var result = [];
@@ -219,7 +264,7 @@
token.string = token.string.slice(0, cur.ch - token.start);
}
- if (token.string.match(/^[.`\w@]\w*$/)) {
+ if (token.string.match(/^[.`"'\w@][\w$#]*$/g)) {
search = token.string;
start = token.start;
end = token.end;
@@ -227,13 +272,32 @@
start = end = cur.ch;
search = "";
}
- if (search.charAt(0) == "." || search.charAt(0) == "`") {
+ if (search.charAt(0) == "." || search.charAt(0) == identifierQuote) {
start = nameCompletion(cur, token, result, editor);
} else {
- addMatches(result, search, tables, function(w) {return w;});
- addMatches(result, search, defaultTable, function(w) {return w;});
- addMatches(result, search, keywords, function(w) {return w.toUpperCase();});
- }
+ var objectOrClass = function(w, className) {
+ if (typeof w === "object") {
+ w.className = className;
+ } else {
+ w = { text: w, className: className };
+ }
+ return w;
+ };
+ addMatches(result, search, defaultTable, function(w) {
+ return objectOrClass(w, "CodeMirror-hint-table CodeMirror-hint-default-table");
+ });
+ addMatches(
+ result,
+ search,
+ tables, function(w) {
+ return objectOrClass(w, "CodeMirror-hint-table");
+ }
+ );
+ if (!disableKeywords)
+ addMatches(result, search, keywords, function(w) {
+ return objectOrClass(w.toUpperCase(), "CodeMirror-hint-keyword");
+ });
+ }
return {list: result, from: Pos(cur.line, start), to: Pos(cur.line, end)};
});
diff --git a/static/editor.md/lib/codemirror/addon/hint/xml-hint.js b/static/editor.md/lib/codemirror/addon/hint/xml-hint.js
index 9b9baa0c6..2b3153124 100644
--- a/static/editor.md/lib/codemirror/addon/hint/xml-hint.js
+++ b/static/editor.md/lib/codemirror/addon/hint/xml-hint.js
@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
@@ -13,9 +13,15 @@
var Pos = CodeMirror.Pos;
+ function matches(hint, typed, matchInMiddle) {
+ if (matchInMiddle) return hint.indexOf(typed) >= 0;
+ else return hint.lastIndexOf(typed, 0) == 0;
+ }
+
function getHints(cm, options) {
var tags = options && options.schemaInfo;
var quote = (options && options.quoteChar) || '"';
+ var matchInMiddle = options && options.matchInMiddle;
if (!tags) return;
var cur = cm.getCursor(), token = cm.getTokenAt(cur);
if (token.end > cur.ch) {
@@ -23,7 +29,7 @@
token.string = token.string.slice(0, cur.ch - token.start);
}
var inner = CodeMirror.innerMode(cm.getMode(), token.state);
- if (inner.mode.name != "xml") return;
+ if (!inner.mode.xmlCurrentTag) return
var result = [], replaceToken = false, prefix;
var tag = /\btag\b/.test(token.type) && !/>$/.test(token.string);
var tagName = tag && /^\w/.test(token.string), tagStart;
@@ -38,25 +44,28 @@
tagType = "close";
}
- if (!tag && !inner.state.tagName || tagType) {
+ var tagInfo = inner.mode.xmlCurrentTag(inner.state)
+ if (!tag && !tagInfo || tagType) {
if (tagName)
prefix = token.string;
replaceToken = tagType;
- var cx = inner.state.context, curTag = cx && tags[cx.tagName];
- var childList = cx ? curTag && curTag.children : tags["!top"];
+ var context = inner.mode.xmlCurrentContext ? inner.mode.xmlCurrentContext(inner.state) : []
+ var inner = context.length && context[context.length - 1]
+ var curTag = inner && tags[inner]
+ var childList = inner ? curTag && curTag.children : tags["!top"];
if (childList && tagType != "close") {
- for (var i = 0; i < childList.length; ++i) if (!prefix || childList[i].lastIndexOf(prefix, 0) == 0)
+ for (var i = 0; i < childList.length; ++i) if (!prefix || matches(childList[i], prefix, matchInMiddle))
result.push("<" + childList[i]);
} else if (tagType != "close") {
for (var name in tags)
- if (tags.hasOwnProperty(name) && name != "!top" && name != "!attrs" && (!prefix || name.lastIndexOf(prefix, 0) == 0))
+ if (tags.hasOwnProperty(name) && name != "!top" && name != "!attrs" && (!prefix || matches(name, prefix, matchInMiddle)))
result.push("<" + name);
}
- if (cx && (!prefix || tagType == "close" && cx.tagName.lastIndexOf(prefix, 0) == 0))
- result.push("" + cx.tagName + ">");
+ if (inner && (!prefix || tagType == "close" && matches(inner, prefix, matchInMiddle)))
+ result.push("" + inner + ">");
} else {
// Attribute completion
- var curTag = tags[inner.state.tagName], attrs = curTag && curTag.attrs;
+ var curTag = tagInfo && tags[tagInfo.name], attrs = curTag && curTag.attrs;
var globalAttrs = tags["!attrs"];
if (!attrs && !globalAttrs) return;
if (!attrs) {
@@ -86,24 +95,37 @@
quote = token.string.charAt(len - 1);
prefix = token.string.substr(n, len - 2);
}
+ if (n) { // an opening quote
+ var line = cm.getLine(cur.line);
+ if (line.length > token.end && line.charAt(token.end) == quote) token.end++; // include a closing quote
+ }
replaceToken = true;
}
- for (var i = 0; i < atValues.length; ++i) if (!prefix || atValues[i].lastIndexOf(prefix, 0) == 0)
- result.push(quote + atValues[i] + quote);
+ var returnHintsFromAtValues = function(atValues) {
+ if (atValues)
+ for (var i = 0; i < atValues.length; ++i) if (!prefix || matches(atValues[i], prefix, matchInMiddle))
+ result.push(quote + atValues[i] + quote);
+ return returnHints();
+ };
+ if (atValues && atValues.then) return atValues.then(returnHintsFromAtValues);
+ return returnHintsFromAtValues(atValues);
} else { // An attribute name
if (token.type == "attribute") {
prefix = token.string;
replaceToken = true;
}
- for (var attr in attrs) if (attrs.hasOwnProperty(attr) && (!prefix || attr.lastIndexOf(prefix, 0) == 0))
+ for (var attr in attrs) if (attrs.hasOwnProperty(attr) && (!prefix || matches(attr, prefix, matchInMiddle)))
result.push(attr);
}
}
- return {
- list: result,
- from: replaceToken ? Pos(cur.line, tagStart == null ? token.start : tagStart) : cur,
- to: replaceToken ? Pos(cur.line, token.end) : cur
- };
+ function returnHints() {
+ return {
+ list: result,
+ from: replaceToken ? Pos(cur.line, tagStart == null ? token.start : tagStart) : cur,
+ to: replaceToken ? Pos(cur.line, token.end) : cur
+ };
+ }
+ return returnHints();
}
CodeMirror.registerHelper("hint", "xml", getHints);
diff --git a/static/editor.md/lib/codemirror/addon/lint/coffeescript-lint.js b/static/editor.md/lib/codemirror/addon/lint/coffeescript-lint.js
index 7e39428f7..a54c70351 100644
--- a/static/editor.md/lib/codemirror/addon/lint/coffeescript-lint.js
+++ b/static/editor.md/lib/codemirror/addon/lint/coffeescript-lint.js
@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
// Depends on coffeelint.js from http://www.coffeelint.org/js/coffeelint.js
@@ -17,6 +17,12 @@
CodeMirror.registerHelper("lint", "coffeescript", function(text) {
var found = [];
+ if (!window.coffeelint) {
+ if (window.console) {
+ window.console.error("Error: window.coffeelint not defined, CodeMirror CoffeeScript linting cannot run.");
+ }
+ return found;
+ }
var parseError = function(err) {
var loc = err.lineNumber;
found.push({from: CodeMirror.Pos(loc-1, 0),
diff --git a/static/editor.md/lib/codemirror/addon/lint/css-lint.js b/static/editor.md/lib/codemirror/addon/lint/css-lint.js
index 1f61b479b..6058a73eb 100644
--- a/static/editor.md/lib/codemirror/addon/lint/css-lint.js
+++ b/static/editor.md/lib/codemirror/addon/lint/css-lint.js
@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
// Depends on csslint.js from https://github.com/stubbornella/csslint
@@ -15,10 +15,15 @@
})(function(CodeMirror) {
"use strict";
-CodeMirror.registerHelper("lint", "css", function(text) {
+CodeMirror.registerHelper("lint", "css", function(text, options) {
var found = [];
- if (!window.CSSLint) return found;
- var results = CSSLint.verify(text), messages = results.messages, message = null;
+ if (!window.CSSLint) {
+ if (window.console) {
+ window.console.error("Error: window.CSSLint not defined, CodeMirror CSS linting cannot run.");
+ }
+ return found;
+ }
+ var results = CSSLint.verify(text, options), messages = results.messages, message = null;
for ( var i = 0; i < messages.length; i++) {
message = messages[i];
var startLine = message.line -1, endLine = message.line -1, startCol = message.col -1, endCol = message.col;
diff --git a/static/editor.md/lib/codemirror/addon/lint/html-lint.js b/static/editor.md/lib/codemirror/addon/lint/html-lint.js
new file mode 100644
index 000000000..5295c3333
--- /dev/null
+++ b/static/editor.md/lib/codemirror/addon/lint/html-lint.js
@@ -0,0 +1,59 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: https://codemirror.net/LICENSE
+
+// Depends on htmlhint.js from http://htmlhint.com/js/htmlhint.js
+
+// declare global: HTMLHint
+
+(function(mod) {
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
+ mod(require("../../lib/codemirror"), require("htmlhint"));
+ else if (typeof define == "function" && define.amd) // AMD
+ define(["../../lib/codemirror", "htmlhint"], mod);
+ else // Plain browser env
+ mod(CodeMirror, window.HTMLHint);
+})(function(CodeMirror, HTMLHint) {
+ "use strict";
+
+ var defaultRules = {
+ "tagname-lowercase": true,
+ "attr-lowercase": true,
+ "attr-value-double-quotes": true,
+ "doctype-first": false,
+ "tag-pair": true,
+ "spec-char-escape": true,
+ "id-unique": true,
+ "src-not-empty": true,
+ "attr-no-duplication": true
+ };
+
+ CodeMirror.registerHelper("lint", "html", function(text, options) {
+ var found = [];
+ if (HTMLHint && !HTMLHint.verify) {
+ if(typeof HTMLHint.default !== 'undefined') {
+ HTMLHint = HTMLHint.default;
+ } else {
+ HTMLHint = HTMLHint.HTMLHint;
+ }
+ }
+ if (!HTMLHint) HTMLHint = window.HTMLHint;
+ if (!HTMLHint) {
+ if (window.console) {
+ window.console.error("Error: HTMLHint not found, not defined on window, or not available through define/require, CodeMirror HTML linting cannot run.");
+ }
+ return found;
+ }
+ var messages = HTMLHint.verify(text, options && options.rules || defaultRules);
+ for (var i = 0; i < messages.length; i++) {
+ var message = messages[i];
+ var startLine = message.line - 1, endLine = message.line - 1, startCol = message.col - 1, endCol = message.col;
+ found.push({
+ from: CodeMirror.Pos(startLine, startCol),
+ to: CodeMirror.Pos(endLine, endCol),
+ message: message.message,
+ severity : message.type
+ });
+ }
+ return found;
+ });
+});
diff --git a/static/editor.md/lib/codemirror/addon/lint/json-lint.js b/static/editor.md/lib/codemirror/addon/lint/json-lint.js
index 9dbb616b3..ac1d6ec28 100644
--- a/static/editor.md/lib/codemirror/addon/lint/json-lint.js
+++ b/static/editor.md/lib/codemirror/addon/lint/json-lint.js
@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
// Depends on jsonlint.js from https://github.com/zaach/jsonlint
@@ -17,6 +17,15 @@
CodeMirror.registerHelper("lint", "json", function(text) {
var found = [];
+ if (!window.jsonlint) {
+ if (window.console) {
+ window.console.error("Error: window.jsonlint not defined, CodeMirror JSON linting cannot run.");
+ }
+ return found;
+ }
+ // for jsonlint's web dist jsonlint is exported as an object with a single property parser, of which parseError
+ // is a subproperty
+ var jsonlint = window.jsonlint.parser || window.jsonlint
jsonlint.parseError = function(str, hash) {
var loc = hash.loc;
found.push({from: CodeMirror.Pos(loc.first_line - 1, loc.first_column),
diff --git a/static/editor.md/lib/codemirror/addon/lint/lint.css b/static/editor.md/lib/codemirror/addon/lint/lint.css
index 414a9a0e0..e1560db98 100644
--- a/static/editor.md/lib/codemirror/addon/lint/lint.css
+++ b/static/editor.md/lib/codemirror/addon/lint/lint.css
@@ -4,10 +4,10 @@
}
.CodeMirror-lint-tooltip {
- background-color: infobackground;
+ background-color: #ffd;
border: 1px solid black;
border-radius: 4px 4px 4px 4px;
- color: infotext;
+ color: black;
font-family: monospace;
font-size: 10pt;
overflow: hidden;
@@ -25,22 +25,20 @@
-ms-transition: opacity .4s;
}
-.CodeMirror-lint-mark-error, .CodeMirror-lint-mark-warning {
+.CodeMirror-lint-mark {
background-position: left bottom;
background-repeat: repeat-x;
}
-.CodeMirror-lint-mark-error {
- background-image:
- url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJDw4cOCW1/KIAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAHElEQVQI12NggIL/DAz/GdA5/xkY/qPKMDAwAADLZwf5rvm+LQAAAABJRU5ErkJggg==")
- ;
-}
-
.CodeMirror-lint-mark-warning {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJFhQXEbhTg7YAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAMklEQVQI12NkgIIvJ3QXMjAwdDN+OaEbysDA4MPAwNDNwMCwiOHLCd1zX07o6kBVGQEAKBANtobskNMAAAAASUVORK5CYII=");
}
-.CodeMirror-lint-marker-error, .CodeMirror-lint-marker-warning {
+.CodeMirror-lint-mark-error {
+ background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJDw4cOCW1/KIAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAHElEQVQI12NggIL/DAz/GdA5/xkY/qPKMDAwAADLZwf5rvm+LQAAAABJRU5ErkJggg==");
+}
+
+.CodeMirror-lint-marker {
background-position: center center;
background-repeat: no-repeat;
cursor: pointer;
@@ -51,23 +49,31 @@
position: relative;
}
-.CodeMirror-lint-message-error, .CodeMirror-lint-message-warning {
+.CodeMirror-lint-message {
padding-left: 18px;
background-position: top left;
background-repeat: no-repeat;
}
-.CodeMirror-lint-marker-error, .CodeMirror-lint-message-error {
- background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAHlBMVEW7AAC7AACxAAC7AAC7AAAAAAC4AAC5AAD///+7AAAUdclpAAAABnRSTlMXnORSiwCK0ZKSAAAATUlEQVR42mWPOQ7AQAgDuQLx/z8csYRmPRIFIwRGnosRrpamvkKi0FTIiMASR3hhKW+hAN6/tIWhu9PDWiTGNEkTtIOucA5Oyr9ckPgAWm0GPBog6v4AAAAASUVORK5CYII=");
-}
-
.CodeMirror-lint-marker-warning, .CodeMirror-lint-message-warning {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAANlBMVEX/uwDvrwD/uwD/uwD/uwD/uwD/uwD/uwD/uwD6twD/uwAAAADurwD2tQD7uAD+ugAAAAD/uwDhmeTRAAAADHRSTlMJ8mN1EYcbmiixgACm7WbuAAAAVklEQVR42n3PUQqAIBBFUU1LLc3u/jdbOJoW1P08DA9Gba8+YWJ6gNJoNYIBzAA2chBth5kLmG9YUoG0NHAUwFXwO9LuBQL1giCQb8gC9Oro2vp5rncCIY8L8uEx5ZkAAAAASUVORK5CYII=");
}
+.CodeMirror-lint-marker-error, .CodeMirror-lint-message-error {
+ background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAHlBMVEW7AAC7AACxAAC7AAC7AAAAAAC4AAC5AAD///+7AAAUdclpAAAABnRSTlMXnORSiwCK0ZKSAAAATUlEQVR42mWPOQ7AQAgDuQLx/z8csYRmPRIFIwRGnosRrpamvkKi0FTIiMASR3hhKW+hAN6/tIWhu9PDWiTGNEkTtIOucA5Oyr9ckPgAWm0GPBog6v4AAAAASUVORK5CYII=");
+}
+
.CodeMirror-lint-marker-multiple {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAMAAADzjKfhAAAACVBMVEUAAAAAAAC/v7914kyHAAAAAXRSTlMAQObYZgAAACNJREFUeNo1ioEJAAAIwmz/H90iFFSGJgFMe3gaLZ0od+9/AQZ0ADosbYraAAAAAElFTkSuQmCC");
background-repeat: no-repeat;
background-position: right bottom;
width: 100%; height: 100%;
}
+
+.CodeMirror-lint-line-error {
+ background-color: rgba(183, 76, 81, 0.08);
+}
+
+.CodeMirror-lint-line-warning {
+ background-color: rgba(255, 211, 0, 0.1);
+}
diff --git a/static/editor.md/lib/codemirror/addon/lint/lint.js b/static/editor.md/lib/codemirror/addon/lint/lint.js
index 18eb70901..1613deb9d 100644
--- a/static/editor.md/lib/codemirror/addon/lint/lint.js
+++ b/static/editor.md/lib/codemirror/addon/lint/lint.js
@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
@@ -11,12 +11,16 @@
})(function(CodeMirror) {
"use strict";
var GUTTER_ID = "CodeMirror-lint-markers";
+ var LINT_LINE_ID = "CodeMirror-lint-line-";
- function showTooltip(e, content) {
+ function showTooltip(cm, e, content) {
var tt = document.createElement("div");
- tt.className = "CodeMirror-lint-tooltip";
+ tt.className = "CodeMirror-lint-tooltip cm-s-" + cm.options.theme;
tt.appendChild(content.cloneNode(true));
- document.body.appendChild(tt);
+ if (cm.state.lint.options.selfContain)
+ cm.getWrapperElement().appendChild(tt);
+ else
+ document.body.appendChild(tt);
function position(e) {
if (!tt.parentNode) return CodeMirror.off(document, "mousemove", position);
@@ -38,8 +42,8 @@
setTimeout(function() { rm(tt); }, 600);
}
- function showTooltipFor(e, content, node) {
- var tooltip = showTooltip(e, content);
+ function showTooltipFor(cm, e, content, node) {
+ var tooltip = showTooltip(cm, e, content);
function hide() {
CodeMirror.off(node, "mouseout", hide);
if (tooltip) { hideTooltip(tooltip); tooltip = null; }
@@ -55,40 +59,64 @@
CodeMirror.on(node, "mouseout", hide);
}
- function LintState(cm, options, hasGutter) {
+ function LintState(cm, conf, hasGutter) {
this.marked = [];
- this.options = options;
+ if (conf instanceof Function) conf = {getAnnotations: conf};
+ if (!conf || conf === true) conf = {};
+ this.options = {};
+ this.linterOptions = conf.options || {};
+ for (var prop in defaults) this.options[prop] = defaults[prop];
+ for (var prop in conf) {
+ if (defaults.hasOwnProperty(prop)) {
+ if (conf[prop] != null) this.options[prop] = conf[prop];
+ } else if (!conf.options) {
+ this.linterOptions[prop] = conf[prop];
+ }
+ }
this.timeout = null;
this.hasGutter = hasGutter;
this.onMouseOver = function(e) { onMouseOver(cm, e); };
+ this.waitingFor = 0
}
- function parseOptions(cm, options) {
- if (options instanceof Function) return {getAnnotations: options};
- if (!options || options === true) options = {};
- if (!options.getAnnotations) options.getAnnotations = cm.getHelper(CodeMirror.Pos(0, 0), "lint");
- if (!options.getAnnotations) throw new Error("Required option 'getAnnotations' missing (lint addon)");
- return options;
+ var defaults = {
+ highlightLines: false,
+ tooltips: true,
+ delay: 500,
+ lintOnChange: true,
+ getAnnotations: null,
+ async: false,
+ selfContain: null,
+ formatAnnotation: null,
+ onUpdateLinting: null
}
function clearMarks(cm) {
var state = cm.state.lint;
if (state.hasGutter) cm.clearGutter(GUTTER_ID);
+ if (state.options.highlightLines) clearErrorLines(cm);
for (var i = 0; i < state.marked.length; ++i)
state.marked[i].clear();
state.marked.length = 0;
}
- function makeMarker(labels, severity, multiple, tooltips) {
+ function clearErrorLines(cm) {
+ cm.eachLine(function(line) {
+ var has = line.wrapClass && /\bCodeMirror-lint-line-\w+\b/.exec(line.wrapClass);
+ if (has) cm.removeLineClass(line, "wrap", has[0]);
+ })
+ }
+
+ function makeMarker(cm, labels, severity, multiple, tooltips) {
var marker = document.createElement("div"), inner = marker;
- marker.className = "CodeMirror-lint-marker-" + severity;
+ marker.className = "CodeMirror-lint-marker CodeMirror-lint-marker-" + severity;
if (multiple) {
inner = marker.appendChild(document.createElement("div"));
- inner.className = "CodeMirror-lint-marker-multiple";
+ inner.className = "CodeMirror-lint-marker CodeMirror-lint-marker-multiple";
}
if (tooltips != false) CodeMirror.on(inner, "mouseover", function(e) {
- showTooltipFor(e, labels, inner);
+ showTooltipFor(cm, e, labels, inner);
});
return marker;
@@ -112,23 +140,58 @@
var severity = ann.severity;
if (!severity) severity = "error";
var tip = document.createElement("div");
- tip.className = "CodeMirror-lint-message-" + severity;
- tip.appendChild(document.createTextNode(ann.message));
+ tip.className = "CodeMirror-lint-message CodeMirror-lint-message-" + severity;
+ if (typeof ann.messageHTML != 'undefined') {
+ tip.innerHTML = ann.messageHTML;
+ } else {
+ tip.appendChild(document.createTextNode(ann.message));
+ }
return tip;
}
+ function lintAsync(cm, getAnnotations) {
+ var state = cm.state.lint
+ var id = ++state.waitingFor
+ function abort() {
+ id = -1
+ cm.off("change", abort)
+ }
+ cm.on("change", abort)
+ getAnnotations(cm.getValue(), function(annotations, arg2) {
+ cm.off("change", abort)
+ if (state.waitingFor != id) return
+ if (arg2 && annotations instanceof CodeMirror) annotations = arg2
+ cm.operation(function() {updateLinting(cm, annotations)})
+ }, state.linterOptions, cm);
+ }
+
function startLinting(cm) {
- var state = cm.state.lint, options = state.options;
- var passOptions = options.options || options; // Support deprecated passing of `options` property in options
- if (options.async || options.getAnnotations.async)
- options.getAnnotations(cm.getValue(), updateLinting, passOptions, cm);
- else
- updateLinting(cm, options.getAnnotations(cm.getValue(), passOptions, cm));
+ var state = cm.state.lint;
+ if (!state) return;
+ var options = state.options;
+ /*
+ * Passing rules in `options` property prevents JSHint (and other linters) from complaining
+ * about unrecognized rules like `onUpdateLinting`, `delay`, `lintOnChange`, etc.
+ */
+ var getAnnotations = options.getAnnotations || cm.getHelper(CodeMirror.Pos(0, 0), "lint");
+ if (!getAnnotations) return;
+ if (options.async || getAnnotations.async) {
+ lintAsync(cm, getAnnotations)
+ } else {
+ var annotations = getAnnotations(cm.getValue(), state.linterOptions, cm);
+ if (!annotations) return;
+ if (annotations.then) annotations.then(function(issues) {
+ cm.operation(function() {updateLinting(cm, issues)})
+ });
+ else cm.operation(function() {updateLinting(cm, annotations)})
+ }
}
function updateLinting(cm, annotationsNotSorted) {
+ var state = cm.state.lint;
+ if (!state) return;
+ var options = state.options;
clearMarks(cm);
- var state = cm.state.lint, options = state.options;
var annotations = groupByLine(annotationsNotSorted);
@@ -136,6 +199,10 @@
var anns = annotations[line];
if (!anns) continue;
+ // filter out duplicate messages
+ var message = [];
+ anns = anns.filter(function(item) { return message.indexOf(item.message) > -1 ? false : message.push(item.message) });
+
var maxSeverity = null;
var tipLabel = state.hasGutter && document.createDocumentFragment();
@@ -149,27 +216,36 @@
if (state.hasGutter) tipLabel.appendChild(annotationTooltip(ann));
if (ann.to) state.marked.push(cm.markText(ann.from, ann.to, {
- className: "CodeMirror-lint-mark-" + severity,
+ className: "CodeMirror-lint-mark CodeMirror-lint-mark-" + severity,
__annotation: ann
}));
}
-
+ // use original annotations[line] to show multiple messages
if (state.hasGutter)
- cm.setGutterMarker(line, GUTTER_ID, makeMarker(tipLabel, maxSeverity, anns.length > 1,
- state.options.tooltips));
+ cm.setGutterMarker(line, GUTTER_ID, makeMarker(cm, tipLabel, maxSeverity, annotations[line].length > 1,
+ options.tooltips));
+
+ if (options.highlightLines)
+ cm.addLineClass(line, "wrap", LINT_LINE_ID + maxSeverity);
}
if (options.onUpdateLinting) options.onUpdateLinting(annotationsNotSorted, annotations, cm);
}
function onChange(cm) {
var state = cm.state.lint;
+ if (!state) return;
clearTimeout(state.timeout);
- state.timeout = setTimeout(function(){startLinting(cm);}, state.options.delay || 500);
+ state.timeout = setTimeout(function(){startLinting(cm);}, state.options.delay);
}
- function popupSpanTooltip(ann, e) {
+ function popupTooltips(cm, annotations, e) {
var target = e.target || e.srcElement;
- showTooltipFor(e, annotationTooltip(ann), target);
+ var tooltip = document.createDocumentFragment();
+ for (var i = 0; i < annotations.length; i++) {
+ var ann = annotations[i];
+ tooltip.appendChild(annotationTooltip(ann));
+ }
+ showTooltipFor(cm, e, tooltip, target);
}
function onMouseOver(cm, e) {
@@ -177,29 +253,39 @@
if (!/\bCodeMirror-lint-mark-/.test(target.className)) return;
var box = target.getBoundingClientRect(), x = (box.left + box.right) / 2, y = (box.top + box.bottom) / 2;
var spans = cm.findMarksAt(cm.coordsChar({left: x, top: y}, "client"));
+
+ var annotations = [];
for (var i = 0; i < spans.length; ++i) {
var ann = spans[i].__annotation;
- if (ann) return popupSpanTooltip(ann, e);
+ if (ann) annotations.push(ann);
}
+ if (annotations.length) popupTooltips(cm, annotations, e);
}
CodeMirror.defineOption("lint", false, function(cm, val, old) {
if (old && old != CodeMirror.Init) {
clearMarks(cm);
- cm.off("change", onChange);
+ if (cm.state.lint.options.lintOnChange !== false)
+ cm.off("change", onChange);
CodeMirror.off(cm.getWrapperElement(), "mouseover", cm.state.lint.onMouseOver);
+ clearTimeout(cm.state.lint.timeout);
delete cm.state.lint;
}
if (val) {
var gutters = cm.getOption("gutters"), hasLintGutter = false;
for (var i = 0; i < gutters.length; ++i) if (gutters[i] == GUTTER_ID) hasLintGutter = true;
- var state = cm.state.lint = new LintState(cm, parseOptions(cm, val), hasLintGutter);
- cm.on("change", onChange);
- if (state.options.tooltips != false)
+ var state = cm.state.lint = new LintState(cm, val, hasLintGutter);
+ if (state.options.lintOnChange)
+ cm.on("change", onChange);
+ if (state.options.tooltips != false && state.options.tooltips != "gutter")
CodeMirror.on(cm.getWrapperElement(), "mouseover", state.onMouseOver);
startLinting(cm);
}
});
+
+ CodeMirror.defineExtension("performLint", function() {
+ startLinting(this);
+ });
});
diff --git a/static/editor.md/lib/codemirror/addon/lint/yaml-lint.js b/static/editor.md/lib/codemirror/addon/lint/yaml-lint.js
index 3f77e525b..b4ac5abc4 100644
--- a/static/editor.md/lib/codemirror/addon/lint/yaml-lint.js
+++ b/static/editor.md/lib/codemirror/addon/lint/yaml-lint.js
@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
@@ -17,10 +17,23 @@
CodeMirror.registerHelper("lint", "yaml", function(text) {
var found = [];
- try { jsyaml.load(text); }
+ if (!window.jsyaml) {
+ if (window.console) {
+ window.console.error("Error: window.jsyaml not defined, CodeMirror YAML linting cannot run.");
+ }
+ return found;
+ }
+ try { jsyaml.loadAll(text); }
catch(e) {
- var loc = e.mark;
- found.push({ from: CodeMirror.Pos(loc.line, loc.column), to: CodeMirror.Pos(loc.line, loc.column), message: e.message });
+ var loc = e.mark,
+ // js-yaml YAMLException doesn't always provide an accurate lineno
+ // e.g., when there are multiple yaml docs
+ // ---
+ // ---
+ // foo:bar
+ from = loc ? CodeMirror.Pos(loc.line, loc.column) : CodeMirror.Pos(0, 0),
+ to = from;
+ found.push({ from: from, to: to, message: e.message });
}
return found;
});
diff --git a/static/editor.md/lib/codemirror/addon/merge/merge.css b/static/editor.md/lib/codemirror/addon/merge/merge.css
index a6a80e43e..dadd7f59c 100644
--- a/static/editor.md/lib/codemirror/addon/merge/merge.css
+++ b/static/editor.md/lib/codemirror/addon/merge/merge.css
@@ -48,6 +48,12 @@
color: #555;
line-height: 1;
}
+.CodeMirror-merge-scrolllock:after {
+ content: "\21db\00a0\00a0\21da";
+}
+.CodeMirror-merge-scrolllock.CodeMirror-merge-scrolllock-enabled:after {
+ content: "\21db\21da";
+}
.CodeMirror-merge-copybuttons-left, .CodeMirror-merge-copybuttons-right {
position: absolute;
@@ -60,6 +66,7 @@
position: absolute;
cursor: pointer;
color: #44c;
+ z-index: 3;
}
.CodeMirror-merge-copy-reverse {
diff --git a/static/editor.md/lib/codemirror/addon/merge/merge.js b/static/editor.md/lib/codemirror/addon/merge/merge.js
index f1f3aafc4..7fd963f70 100644
--- a/static/editor.md/lib/codemirror/addon/merge/merge.js
+++ b/static/editor.md/lib/codemirror/addon/merge/merge.js
@@ -1,16 +1,16 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
// declare global: diff_match_patch, DIFF_INSERT, DIFF_DELETE, DIFF_EQUAL
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
- mod(require("../../lib/codemirror"), require("diff_match_patch"));
+ mod(require("../../lib/codemirror")); // Note non-packaged dependency diff_match_patch
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror", "diff_match_patch"], mod);
else // Plain browser env
- mod(CodeMirror, diff_match_patch);
-})(function(CodeMirror, diff_match_patch) {
+ mod(CodeMirror);
+})(function(CodeMirror) {
"use strict";
var Pos = CodeMirror.Pos;
var svgNS = "http://www.w3.org/2000/svg";
@@ -37,16 +37,30 @@
constructor: DiffView,
init: function(pane, orig, options) {
this.edit = this.mv.edit;
+ ;(this.edit.state.diffViews || (this.edit.state.diffViews = [])).push(this);
this.orig = CodeMirror(pane, copyObj({value: orig, readOnly: !this.mv.options.allowEditingOriginals}, copyObj(options)));
+ if (this.mv.options.connect == "align") {
+ if (!this.edit.state.trackAlignable) this.edit.state.trackAlignable = new TrackAlignable(this.edit)
+ this.orig.state.trackAlignable = new TrackAlignable(this.orig)
+ }
+ this.lockButton.title = this.edit.phrase("Toggle locked scrolling");
+
+ this.orig.state.diffViews = [this];
+ var classLocation = options.chunkClassLocation || "background";
+ if (Object.prototype.toString.call(classLocation) != "[object Array]") classLocation = [classLocation]
+ this.classes.classLocation = classLocation
- this.diff = getDiff(asString(orig), asString(options.value));
+ this.diff = getDiff(asString(orig), asString(options.value), this.mv.options.ignoreWhitespace);
this.chunks = getChunks(this.diff);
this.diffOutOfDate = this.dealigned = false;
+ this.needsScrollSync = null
this.showDifferences = options.showDifferences !== false;
+ },
+ registerEvents: function(otherDv) {
this.forceUpdate = registerUpdate(this);
setScrollLock(this, true, false);
- registerScroll(this);
+ registerScroll(this, otherDv);
},
setShowDifferences: function(val) {
val = val !== false;
@@ -59,7 +73,7 @@
function ensureDiff(dv) {
if (dv.diffOutOfDate) {
- dv.diff = getDiff(dv.orig.getValue(), dv.edit.getValue());
+ dv.diff = getDiff(dv.orig.getValue(), dv.edit.getValue(), dv.mv.options.ignoreWhitespace);
dv.chunks = getChunks(dv.diff);
dv.diffOutOfDate = false;
CodeMirror.signal(dv.edit, "updateDiff", dv.diff);
@@ -86,10 +100,12 @@
updateMarks(dv.edit, dv.diff, edit, DIFF_INSERT, dv.classes);
updateMarks(dv.orig, dv.diff, orig, DIFF_DELETE, dv.classes);
}
- makeConnections(dv);
if (dv.mv.options.connect == "align")
alignChunks(dv);
+ makeConnections(dv);
+ if (dv.needsScrollSync != null) syncScroll(dv, dv.needsScrollSync)
+
updating = false;
}
function setDealign(fast) {
@@ -111,37 +127,49 @@
// Update faster when a line was added/removed
setDealign(change.text.length - 1 != change.to.line - change.from.line);
}
+ function swapDoc() {
+ dv.diffOutOfDate = true;
+ dv.dealigned = true;
+ update("full");
+ }
dv.edit.on("change", change);
dv.orig.on("change", change);
- dv.edit.on("markerAdded", setDealign);
- dv.edit.on("markerCleared", setDealign);
- dv.orig.on("markerAdded", setDealign);
- dv.orig.on("markerCleared", setDealign);
+ dv.edit.on("swapDoc", swapDoc);
+ dv.orig.on("swapDoc", swapDoc);
+ if (dv.mv.options.connect == "align") {
+ CodeMirror.on(dv.edit.state.trackAlignable, "realign", setDealign)
+ CodeMirror.on(dv.orig.state.trackAlignable, "realign", setDealign)
+ }
dv.edit.on("viewportChange", function() { set(false); });
dv.orig.on("viewportChange", function() { set(false); });
update();
return update;
}
- function registerScroll(dv) {
+ function registerScroll(dv, otherDv) {
dv.edit.on("scroll", function() {
- syncScroll(dv, DIFF_INSERT) && makeConnections(dv);
+ syncScroll(dv, true) && makeConnections(dv);
});
dv.orig.on("scroll", function() {
- syncScroll(dv, DIFF_DELETE) && makeConnections(dv);
+ syncScroll(dv, false) && makeConnections(dv);
+ if (otherDv) syncScroll(otherDv, true) && makeConnections(otherDv);
});
}
- function syncScroll(dv, type) {
+ function syncScroll(dv, toOrig) {
// Change handler will do a refresh after a timeout when diff is out of date
- if (dv.diffOutOfDate) return false;
+ if (dv.diffOutOfDate) {
+ if (dv.lockScroll && dv.needsScrollSync == null) dv.needsScrollSync = toOrig
+ return false
+ }
+ dv.needsScrollSync = null
if (!dv.lockScroll) return true;
var editor, other, now = +new Date;
- if (type == DIFF_INSERT) { editor = dv.edit; other = dv.orig; }
+ if (toOrig) { editor = dv.edit; other = dv.orig; }
else { editor = dv.orig; other = dv.edit; }
// Don't take action if the position of this editor was recently set
// (to prevent feedback loops)
- if (editor.state.scrollSetBy == dv && (editor.state.scrollSetAt || 0) + 50 > now) return false;
+ if (editor.state.scrollSetBy == dv && (editor.state.scrollSetAt || 0) + 250 > now) return false;
var sInfo = editor.getScrollInfo();
if (dv.mv.options.connect == "align") {
@@ -149,9 +177,9 @@
} else {
var halfScreen = .5 * sInfo.clientHeight, midY = sInfo.top + halfScreen;
var mid = editor.lineAtHeight(midY, "local");
- var around = chunkBoundariesAround(dv.chunks, mid, type == DIFF_INSERT);
- var off = getOffsets(editor, type == DIFF_INSERT ? around.edit : around.orig);
- var offOther = getOffsets(other, type == DIFF_INSERT ? around.orig : around.edit);
+ var around = chunkBoundariesAround(dv.chunks, mid, toOrig);
+ var off = getOffsets(editor, toOrig ? around.edit : around.orig);
+ var offOther = getOffsets(other, toOrig ? around.orig : around.edit);
var ratio = (midY - off.top) / (off.bot - off.top);
var targetPos = (offOther.top - halfScreen) + ratio * (offOther.bot - offOther.top);
@@ -184,21 +212,27 @@
function setScrollLock(dv, val, action) {
dv.lockScroll = val;
if (val && action != false) syncScroll(dv, DIFF_INSERT) && makeConnections(dv);
- dv.lockButton.innerHTML = val ? "\u21db\u21da" : "\u21db \u21da";
+ (val ? CodeMirror.addClass : CodeMirror.rmClass)(dv.lockButton, "CodeMirror-merge-scrolllock-enabled");
}
// Updating the marks for editor content
+ function removeClass(editor, line, classes) {
+ var locs = classes.classLocation
+ for (var i = 0; i < locs.length; i++) {
+ editor.removeLineClass(line, locs[i], classes.chunk);
+ editor.removeLineClass(line, locs[i], classes.start);
+ editor.removeLineClass(line, locs[i], classes.end);
+ }
+ }
+
function clearMarks(editor, arr, classes) {
for (var i = 0; i < arr.length; ++i) {
var mark = arr[i];
- if (mark instanceof CodeMirror.TextMarker) {
+ if (mark instanceof CodeMirror.TextMarker)
mark.clear();
- } else if (mark.parent) {
- editor.removeLineClass(mark, "background", classes.chunk);
- editor.removeLineClass(mark, "background", classes.start);
- editor.removeLineClass(mark, "background", classes.end);
- }
+ else if (mark.parent)
+ removeClass(editor, mark, classes);
}
arr.length = 0;
}
@@ -224,28 +258,34 @@
});
}
+ function addClass(editor, lineNr, classes, main, start, end) {
+ var locs = classes.classLocation, line = editor.getLineHandle(lineNr);
+ for (var i = 0; i < locs.length; i++) {
+ if (main) editor.addLineClass(line, locs[i], classes.chunk);
+ if (start) editor.addLineClass(line, locs[i], classes.start);
+ if (end) editor.addLineClass(line, locs[i], classes.end);
+ }
+ return line;
+ }
+
function markChanges(editor, diff, type, marks, from, to, classes) {
var pos = Pos(0, 0);
var top = Pos(from, 0), bot = editor.clipPos(Pos(to - 1));
var cls = type == DIFF_DELETE ? classes.del : classes.insert;
function markChunk(start, end) {
var bfrom = Math.max(from, start), bto = Math.min(to, end);
- for (var i = bfrom; i < bto; ++i) {
- var line = editor.addLineClass(i, "background", classes.chunk);
- if (i == start) editor.addLineClass(line, "background", classes.start);
- if (i == end - 1) editor.addLineClass(line, "background", classes.end);
- marks.push(line);
- }
+ for (var i = bfrom; i < bto; ++i)
+ marks.push(addClass(editor, i, classes, true, i == start, i == end - 1));
// When the chunk is empty, make sure a horizontal line shows up
if (start == end && bfrom == end && bto == end) {
if (bfrom)
- marks.push(editor.addLineClass(bfrom - 1, "background", classes.end));
+ marks.push(addClass(editor, bfrom - 1, classes, false, false, true));
else
- marks.push(editor.addLineClass(bfrom, "background", classes.start));
+ marks.push(addClass(editor, bfrom, classes, false, true, false));
}
}
- var chunkStart = 0;
+ var chunkStart = 0, pending = false;
for (var i = 0; i < diff.length; ++i) {
var part = diff[i], tp = part[0], str = part[1];
if (tp == DIFF_EQUAL) {
@@ -253,10 +293,11 @@
moveOver(pos, str);
var cleanTo = pos.line + (endOfLineClean(diff, i) ? 1 : 0);
if (cleanTo > cleanFrom) {
- if (i) markChunk(chunkStart, cleanFrom);
+ if (pending) { markChunk(chunkStart, cleanFrom); pending = false }
chunkStart = cleanTo;
}
} else {
+ pending = true
if (tp == type) {
var end = moveOver(pos, str, true);
var a = posMax(top, pos), b = posMin(bot, end);
@@ -266,7 +307,7 @@
}
}
}
- if (chunkStart <= pos.line) markChunk(chunkStart, pos.line + 1);
+ if (pending) markChunk(chunkStart, pos.line + 1);
}
// Updating the gap between editor and original
@@ -282,7 +323,9 @@
if (dv.copyButtons) clear(dv.copyButtons);
var vpEdit = dv.edit.getViewport(), vpOrig = dv.orig.getViewport();
- var sTopEdit = dv.edit.getScrollInfo().top, sTopOrig = dv.orig.getScrollInfo().top;
+ var outerTop = dv.mv.wrap.getBoundingClientRect().top
+ var sTopEdit = outerTop - dv.edit.getScrollerElement().getBoundingClientRect().top + dv.edit.getScrollInfo().top
+ var sTopOrig = outerTop - dv.orig.getScrollerElement().getBoundingClientRect().top + dv.orig.getScrollInfo().top;
for (var i = 0; i < dv.chunks.length; i++) {
var ch = dv.chunks[i];
if (ch.editFrom <= vpEdit.to && ch.editTo >= vpEdit.from &&
@@ -303,29 +346,81 @@
return origStart + (editLine - editStart);
}
- function findAlignedLines(dv, other) {
- var linesToAlign = [];
- for (var i = 0; i < dv.chunks.length; i++) {
- var chunk = dv.chunks[i];
- linesToAlign.push([chunk.origTo, chunk.editTo, other ? getMatchingOrigLine(chunk.editTo, other.chunks) : null]);
- }
- if (other) {
- for (var i = 0; i < other.chunks.length; i++) {
- var chunk = other.chunks[i];
- for (var j = 0; j < linesToAlign.length; j++) {
- var align = linesToAlign[j];
- if (align[1] == chunk.editTo) {
- j = -1;
- break;
- } else if (align[1] > chunk.editTo) {
- break;
- }
+ // Combines information about chunks and widgets/markers to return
+ // an array of lines, in a single editor, that probably need to be
+ // aligned with their counterparts in the editor next to it.
+ function alignableFor(cm, chunks, isOrig) {
+ var tracker = cm.state.trackAlignable
+ var start = cm.firstLine(), trackI = 0
+ var result = []
+ for (var i = 0;; i++) {
+ var chunk = chunks[i]
+ var chunkStart = !chunk ? 1e9 : isOrig ? chunk.origFrom : chunk.editFrom
+ for (; trackI < tracker.alignable.length; trackI += 2) {
+ var n = tracker.alignable[trackI] + 1
+ if (n <= start) continue
+ if (n <= chunkStart) result.push(n)
+ else break
+ }
+ if (!chunk) break
+ result.push(start = isOrig ? chunk.origTo : chunk.editTo)
+ }
+ return result
+ }
+
+ // Given information about alignable lines in two editors, fill in
+ // the result (an array of three-element arrays) to reflect the
+ // lines that need to be aligned with each other.
+ function mergeAlignable(result, origAlignable, chunks, setIndex) {
+ var rI = 0, origI = 0, chunkI = 0, diff = 0
+ outer: for (;; rI++) {
+ var nextR = result[rI], nextO = origAlignable[origI]
+ if (!nextR && nextO == null) break
+
+ var rLine = nextR ? nextR[0] : 1e9, oLine = nextO == null ? 1e9 : nextO
+ while (chunkI < chunks.length) {
+ var chunk = chunks[chunkI]
+ if (chunk.origFrom <= oLine && chunk.origTo > oLine) {
+ origI++
+ rI--
+ continue outer;
+ }
+ if (chunk.editTo > rLine) {
+ if (chunk.editFrom <= rLine) continue outer;
+ break
}
- if (j > -1)
- linesToAlign.splice(j - 1, 0, [getMatchingOrigLine(chunk.editTo, dv.chunks), chunk.editTo, chunk.origTo]);
+ diff += (chunk.origTo - chunk.origFrom) - (chunk.editTo - chunk.editFrom)
+ chunkI++
}
+ if (rLine == oLine - diff) {
+ nextR[setIndex] = oLine
+ origI++
+ } else if (rLine < oLine - diff) {
+ nextR[setIndex] = rLine + diff
+ } else {
+ var record = [oLine - diff, null, null]
+ record[setIndex] = oLine
+ result.splice(rI, 0, record)
+ origI++
+ }
+ }
+ }
+
+ function findAlignedLines(dv, other) {
+ var alignable = alignableFor(dv.edit, dv.chunks, false), result = []
+ if (other) for (var i = 0, j = 0; i < other.chunks.length; i++) {
+ var n = other.chunks[i].editTo
+ while (j < alignable.length && alignable[j] < n) j++
+ if (j == alignable.length || alignable[j] != n) alignable.splice(j++, 0, n)
}
- return linesToAlign;
+ for (var i = 0; i < alignable.length; i++)
+ result.push([alignable[i], null, null])
+
+ mergeAlignable(result, alignableFor(dv.orig, dv.chunks, true), dv.chunks, 1)
+ if (other)
+ mergeAlignable(result, alignableFor(other.orig, other.chunks, true), other.chunks, 2)
+
+ return result
}
function alignChunks(dv, force) {
@@ -348,22 +443,26 @@
aligners[i].clear();
aligners.length = 0;
- var cm = [dv.orig, dv.edit], scroll = [];
+ var cm = [dv.edit, dv.orig], scroll = [], offset = []
if (other) cm.push(other.orig);
- for (var i = 0; i < cm.length; i++)
+ for (var i = 0; i < cm.length; i++) {
scroll.push(cm[i].getScrollInfo().top);
+ offset.push(-cm[i].getScrollerElement().getBoundingClientRect().top)
+ }
+ if (offset[0] != offset[1] || cm.length == 3 && offset[1] != offset[2])
+ alignLines(cm, offset, [0, 0, 0], aligners)
for (var ln = 0; ln < linesToAlign.length; ln++)
- alignLines(cm, linesToAlign[ln], aligners);
+ alignLines(cm, offset, linesToAlign[ln], aligners);
for (var i = 0; i < cm.length; i++)
cm[i].scrollTo(null, scroll[i]);
}
- function alignLines(cm, lines, aligners) {
- var maxOffset = 0, offset = [];
+ function alignLines(cm, cmOffset, lines, aligners) {
+ var maxOffset = -1e8, offset = [];
for (var i = 0; i < cm.length; i++) if (lines[i] != null) {
- var off = cm[i].heightAtLine(lines[i], "local");
+ var off = cm[i].heightAtLine(lines[i], "local") - cmOffset[i];
offset[i] = off;
maxOffset = Math.max(maxOffset, off);
}
@@ -383,18 +482,18 @@
var elt = document.createElement("div");
elt.className = "CodeMirror-merge-spacer";
elt.style.height = size + "px"; elt.style.minWidth = "1px";
- return cm.addLineWidget(line, elt, {height: size, above: above});
+ return cm.addLineWidget(line, elt, {height: size, above: above, mergeSpacer: true, handleMouseEvents: true});
}
function drawConnectorsForChunk(dv, chunk, sTopOrig, sTopEdit, w) {
var flip = dv.type == "left";
- var top = dv.orig.heightAtLine(chunk.origFrom, "local") - sTopOrig;
+ var top = dv.orig.heightAtLine(chunk.origFrom, "local", true) - sTopOrig;
if (dv.svg) {
var topLpx = top;
- var topRpx = dv.edit.heightAtLine(chunk.editFrom, "local") - sTopEdit;
+ var topRpx = dv.edit.heightAtLine(chunk.editFrom, "local", true) - sTopEdit;
if (flip) { var tmp = topLpx; topLpx = topRpx; topRpx = tmp; }
- var botLpx = dv.orig.heightAtLine(chunk.origTo, "local") - sTopOrig;
- var botRpx = dv.edit.heightAtLine(chunk.editTo, "local") - sTopEdit;
+ var botLpx = dv.orig.heightAtLine(chunk.origTo, "local", true) - sTopOrig;
+ var botRpx = dv.edit.heightAtLine(chunk.editTo, "local", true) - sTopEdit;
if (flip) { var tmp = botLpx; botLpx = botRpx; botRpx = tmp; }
var curveTop = " C " + w/2 + " " + topRpx + " " + w/2 + " " + topLpx + " " + (w + 2) + " " + topLpx;
var curveBot = " C " + w/2 + " " + botLpx + " " + w/2 + " " + botRpx + " -1 " + botRpx;
@@ -406,12 +505,13 @@
var copy = dv.copyButtons.appendChild(elt("div", dv.type == "left" ? "\u21dd" : "\u21dc",
"CodeMirror-merge-copy"));
var editOriginals = dv.mv.options.allowEditingOriginals;
- copy.title = editOriginals ? "Push to left" : "Revert chunk";
+ copy.title = dv.edit.phrase(editOriginals ? "Push to left" : "Revert chunk");
copy.chunk = chunk;
- copy.style.top = top + "px";
+ copy.style.top = (chunk.origTo > chunk.origFrom ? top : dv.edit.heightAtLine(chunk.editFrom, "local") - sTopEdit) + "px";
+ copy.setAttribute("role", "button");
if (editOriginals) {
- var topReverse = dv.orig.heightAtLine(chunk.editFrom, "local") - sTopEdit;
+ var topReverse = dv.edit.heightAtLine(chunk.editFrom, "local") - sTopEdit;
var copyReverse = dv.copyButtons.appendChild(elt("div", dv.type == "right" ? "\u21dd" : "\u21dc",
"CodeMirror-merge-copy-reverse"));
copyReverse.title = "Push to right";
@@ -419,14 +519,22 @@
origFrom: chunk.editFrom, origTo: chunk.editTo};
copyReverse.style.top = topReverse + "px";
dv.type == "right" ? copyReverse.style.left = "2px" : copyReverse.style.right = "2px";
+ copyReverse.setAttribute("role", "button");
}
}
}
function copyChunk(dv, to, from, chunk) {
if (dv.diffOutOfDate) return;
- to.replaceRange(from.getRange(Pos(chunk.origFrom, 0), Pos(chunk.origTo, 0)),
- Pos(chunk.editFrom, 0), Pos(chunk.editTo, 0));
+ var origStart = chunk.origTo > from.lastLine() ? Pos(chunk.origFrom - 1) : Pos(chunk.origFrom, 0)
+ var origEnd = Pos(chunk.origTo, 0)
+ var editStart = chunk.editTo > to.lastLine() ? Pos(chunk.editFrom - 1) : Pos(chunk.editFrom, 0)
+ var editEnd = Pos(chunk.editTo, 0)
+ var handler = dv.mv.options.revertChunk
+ if (handler)
+ handler(dv.mv, from, origStart, origEnd, to, editStart, editEnd)
+ else
+ to.replaceRange(from.getRange(origStart, origEnd), editStart, editEnd)
}
// Merge view, containing 0, 1, or 2 diff views.
@@ -444,18 +552,18 @@
if (hasLeft) {
left = this.left = new DiffView(this, "left");
- var leftPane = elt("div", null, "CodeMirror-merge-pane");
+ var leftPane = elt("div", null, "CodeMirror-merge-pane CodeMirror-merge-left");
wrap.push(leftPane);
wrap.push(buildGap(left));
}
- var editPane = elt("div", null, "CodeMirror-merge-pane");
+ var editPane = elt("div", null, "CodeMirror-merge-pane CodeMirror-merge-editor");
wrap.push(editPane);
if (hasRight) {
right = this.right = new DiffView(this, "right");
wrap.push(buildGap(right));
- var rightPane = elt("div", null, "CodeMirror-merge-pane");
+ var rightPane = elt("div", null, "CodeMirror-merge-pane CodeMirror-merge-right");
wrap.push(rightPane);
}
@@ -468,18 +576,17 @@
if (left) left.init(leftPane, origLeft, options);
if (right) right.init(rightPane, origRight, options);
-
- if (options.collapseIdentical) {
- updating = true;
+ if (options.collapseIdentical)
this.editor().operation(function() {
collapseIdenticalStretches(self, options.collapseIdentical);
});
- updating = false;
- }
if (options.connect == "align") {
this.aligners = [];
alignChunks(this.left || this.right, true);
}
+ if (left) left.registerEvents(right)
+ if (right) right.registerEvents(left)
+
var onResize = function() {
if (left) makeConnections(left);
@@ -494,7 +601,7 @@
function buildGap(dv) {
var lock = dv.lockButton = elt("div", null, "CodeMirror-merge-scrolllock");
- lock.title = "Toggle locked scrolling";
+ lock.setAttribute("role", "button");
var lockWrap = elt("div", [lock], "CodeMirror-merge-scrolllock-wrap");
CodeMirror.on(lock, "click", function() { setScrollLock(dv, !dv.lockScroll); });
var gapElts = [lockWrap];
@@ -522,7 +629,7 @@
}
MergeView.prototype = {
- constuctor: MergeView,
+ constructor: MergeView,
editor: function() { return this.edit; },
rightOriginal: function() { return this.right && this.right.orig; },
leftOriginal: function() { return this.left && this.left.orig; },
@@ -544,15 +651,15 @@
}
// Operations on diffs
+ var dmp;
+ function getDiff(a, b, ignoreWhitespace) {
+ if (!dmp) dmp = new diff_match_patch();
- var dmp = new diff_match_patch();
- function getDiff(a, b) {
var diff = dmp.diff_main(a, b);
- dmp.diff_cleanupSemantic(diff);
// The library sometimes leaves in empty parts, which confuse the algorithm
for (var i = 0; i < diff.length; ++i) {
var part = diff[i];
- if (!part[1]) {
+ if (ignoreWhitespace ? !/[^ \t]/.test(part[1]) : !part[1]) {
diff.splice(i--, 1);
} else if (i && diff[i - 1][0] == part[0]) {
diff.splice(i--, 1);
@@ -564,12 +671,13 @@
function getChunks(diff) {
var chunks = [];
+ if (!diff.length) return chunks;
var startEdit = 0, startOrig = 0;
var edit = Pos(0, 0), orig = Pos(0, 0);
for (var i = 0; i < diff.length; ++i) {
var part = diff[i], tp = part[0];
if (tp == DIFF_EQUAL) {
- var startOff = startOfLineClean(diff, i) ? 0 : 1;
+ var startOff = !startOfLineClean(diff, i) || edit.line < startEdit || orig.line < startOrig ? 1 : 0;
var cleanFromEdit = edit.line + startOff, cleanFromOrig = orig.line + startOff;
moveOver(edit, part[1], null, orig);
var endOff = endOfLineClean(diff, i) ? 1 : 0;
@@ -592,10 +700,10 @@
function endOfLineClean(diff, i) {
if (i == diff.length - 1) return true;
var next = diff[i + 1][1];
- if (next.length == 1 || next.charCodeAt(0) != 10) return false;
+ if ((next.length == 1 && i < diff.length - 2) || next.charCodeAt(0) != 10) return false;
if (i == diff.length - 2) return true;
next = diff[i + 2][1];
- return next.length > 1 && next.charCodeAt(0) == 10;
+ return (next.length > 1 || i == diff.length - 3) && next.charCodeAt(0) == 10;
}
function startOfLineClean(diff, i) {
@@ -627,7 +735,7 @@
cm.addLineClass(from, "wrap", "CodeMirror-merge-collapsed-line");
var widget = document.createElement("span");
widget.className = "CodeMirror-merge-collapsed-widget";
- widget.title = "Identical text collapsed. Click to expand.";
+ widget.title = cm.phrase("Identical text collapsed. Click to expand.");
var mark = cm.markText(Pos(from, 0), Pos(to - 1), {
inclusiveLeft: true,
inclusiveRight: true,
@@ -638,7 +746,10 @@
mark.clear();
cm.removeLineClass(from, "wrap", "CodeMirror-merge-collapsed-line");
}
- widget.addEventListener("click", clear);
+ if (mark.explicitlyCleared) clear();
+ CodeMirror.on(widget, "click", clear);
+ mark.on("clear", clear);
+ CodeMirror.on(widget, "click", clear);
return {mark: mark, clear: clear};
}
@@ -729,7 +840,170 @@
return out;
}
+ // Tracks collapsed markers and line widgets, in order to be able to
+ // accurately align the content of two editors.
+
+ var F_WIDGET = 1, F_WIDGET_BELOW = 2, F_MARKER = 4
+
+ function TrackAlignable(cm) {
+ this.cm = cm
+ this.alignable = []
+ this.height = cm.doc.height
+ var self = this
+ cm.on("markerAdded", function(_, marker) {
+ if (!marker.collapsed) return
+ var found = marker.find(1)
+ if (found != null) self.set(found.line, F_MARKER)
+ })
+ cm.on("markerCleared", function(_, marker, _min, max) {
+ if (max != null && marker.collapsed)
+ self.check(max, F_MARKER, self.hasMarker)
+ })
+ cm.on("markerChanged", this.signal.bind(this))
+ cm.on("lineWidgetAdded", function(_, widget, lineNo) {
+ if (widget.mergeSpacer) return
+ if (widget.above) self.set(lineNo - 1, F_WIDGET_BELOW)
+ else self.set(lineNo, F_WIDGET)
+ })
+ cm.on("lineWidgetCleared", function(_, widget, lineNo) {
+ if (widget.mergeSpacer) return
+ if (widget.above) self.check(lineNo - 1, F_WIDGET_BELOW, self.hasWidgetBelow)
+ else self.check(lineNo, F_WIDGET, self.hasWidget)
+ })
+ cm.on("lineWidgetChanged", this.signal.bind(this))
+ cm.on("change", function(_, change) {
+ var start = change.from.line, nBefore = change.to.line - change.from.line
+ var nAfter = change.text.length - 1, end = start + nAfter
+ if (nBefore || nAfter) self.map(start, nBefore, nAfter)
+ self.check(end, F_MARKER, self.hasMarker)
+ if (nBefore || nAfter) self.check(change.from.line, F_MARKER, self.hasMarker)
+ })
+ cm.on("viewportChange", function() {
+ if (self.cm.doc.height != self.height) self.signal()
+ })
+ }
+
+ TrackAlignable.prototype = {
+ signal: function() {
+ CodeMirror.signal(this, "realign")
+ this.height = this.cm.doc.height
+ },
+
+ set: function(n, flags) {
+ var pos = -1
+ for (; pos < this.alignable.length; pos += 2) {
+ var diff = this.alignable[pos] - n
+ if (diff == 0) {
+ if ((this.alignable[pos + 1] & flags) == flags) return
+ this.alignable[pos + 1] |= flags
+ this.signal()
+ return
+ }
+ if (diff > 0) break
+ }
+ this.signal()
+ this.alignable.splice(pos, 0, n, flags)
+ },
+
+ find: function(n) {
+ for (var i = 0; i < this.alignable.length; i += 2)
+ if (this.alignable[i] == n) return i
+ return -1
+ },
+
+ check: function(n, flag, pred) {
+ var found = this.find(n)
+ if (found == -1 || !(this.alignable[found + 1] & flag)) return
+ if (!pred.call(this, n)) {
+ this.signal()
+ var flags = this.alignable[found + 1] & ~flag
+ if (flags) this.alignable[found + 1] = flags
+ else this.alignable.splice(found, 2)
+ }
+ },
+
+ hasMarker: function(n) {
+ var handle = this.cm.getLineHandle(n)
+ if (handle.markedSpans) for (var i = 0; i < handle.markedSpans.length; i++)
+ if (handle.markedSpans[i].marker.collapsed && handle.markedSpans[i].to != null)
+ return true
+ return false
+ },
+
+ hasWidget: function(n) {
+ var handle = this.cm.getLineHandle(n)
+ if (handle.widgets) for (var i = 0; i < handle.widgets.length; i++)
+ if (!handle.widgets[i].above && !handle.widgets[i].mergeSpacer) return true
+ return false
+ },
+
+ hasWidgetBelow: function(n) {
+ if (n == this.cm.lastLine()) return false
+ var handle = this.cm.getLineHandle(n + 1)
+ if (handle.widgets) for (var i = 0; i < handle.widgets.length; i++)
+ if (handle.widgets[i].above && !handle.widgets[i].mergeSpacer) return true
+ return false
+ },
+
+ map: function(from, nBefore, nAfter) {
+ var diff = nAfter - nBefore, to = from + nBefore, widgetFrom = -1, widgetTo = -1
+ for (var i = 0; i < this.alignable.length; i += 2) {
+ var n = this.alignable[i]
+ if (n == from && (this.alignable[i + 1] & F_WIDGET_BELOW)) widgetFrom = i
+ if (n == to && (this.alignable[i + 1] & F_WIDGET_BELOW)) widgetTo = i
+ if (n <= from) continue
+ else if (n < to) this.alignable.splice(i--, 2)
+ else this.alignable[i] += diff
+ }
+ if (widgetFrom > -1) {
+ var flags = this.alignable[widgetFrom + 1]
+ if (flags == F_WIDGET_BELOW) this.alignable.splice(widgetFrom, 2)
+ else this.alignable[widgetFrom + 1] = flags & ~F_WIDGET_BELOW
+ }
+ if (widgetTo > -1 && nAfter)
+ this.set(from + nAfter, F_WIDGET_BELOW)
+ }
+ }
+
function posMin(a, b) { return (a.line - b.line || a.ch - b.ch) < 0 ? a : b; }
function posMax(a, b) { return (a.line - b.line || a.ch - b.ch) > 0 ? a : b; }
function posEq(a, b) { return a.line == b.line && a.ch == b.ch; }
+
+ function findPrevDiff(chunks, start, isOrig) {
+ for (var i = chunks.length - 1; i >= 0; i--) {
+ var chunk = chunks[i];
+ var to = (isOrig ? chunk.origTo : chunk.editTo) - 1;
+ if (to < start) return to;
+ }
+ }
+
+ function findNextDiff(chunks, start, isOrig) {
+ for (var i = 0; i < chunks.length; i++) {
+ var chunk = chunks[i];
+ var from = (isOrig ? chunk.origFrom : chunk.editFrom);
+ if (from > start) return from;
+ }
+ }
+
+ function goNearbyDiff(cm, dir) {
+ var found = null, views = cm.state.diffViews, line = cm.getCursor().line;
+ if (views) for (var i = 0; i < views.length; i++) {
+ var dv = views[i], isOrig = cm == dv.orig;
+ ensureDiff(dv);
+ var pos = dir < 0 ? findPrevDiff(dv.chunks, line, isOrig) : findNextDiff(dv.chunks, line, isOrig);
+ if (pos != null && (found == null || (dir < 0 ? pos > found : pos < found)))
+ found = pos;
+ }
+ if (found != null)
+ cm.setCursor(found, 0);
+ else
+ return CodeMirror.Pass;
+ }
+
+ CodeMirror.commands.goNextDiff = function(cm) {
+ return goNearbyDiff(cm, 1);
+ };
+ CodeMirror.commands.goPrevDiff = function(cm) {
+ return goNearbyDiff(cm, -1);
+ };
});
diff --git a/static/editor.md/lib/codemirror/addon/mode/loadmode.js b/static/editor.md/lib/codemirror/addon/mode/loadmode.js
index 10117ec22..fc695d0c3 100644
--- a/static/editor.md/lib/codemirror/addon/mode/loadmode.js
+++ b/static/editor.md/lib/codemirror/addon/mode/loadmode.js
@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
@@ -16,8 +16,8 @@
var countDown = n;
return function() { if (--countDown == 0) cont(); };
}
- function ensureDeps(mode, cont) {
- var deps = CodeMirror.modes[mode].dependencies;
+ function ensureDeps(mode, cont, options) {
+ var modeObj = CodeMirror.modes[mode], deps = modeObj && modeObj.dependencies;
if (!deps) return cont();
var missing = [];
for (var i = 0; i < deps.length; ++i) {
@@ -27,16 +27,18 @@
if (!missing.length) return cont();
var split = splitCallback(cont, missing.length);
for (var i = 0; i < missing.length; ++i)
- CodeMirror.requireMode(missing[i], split);
+ CodeMirror.requireMode(missing[i], split, options);
}
- CodeMirror.requireMode = function(mode, cont) {
+ CodeMirror.requireMode = function(mode, cont, options) {
if (typeof mode != "string") mode = mode.name;
- if (CodeMirror.modes.hasOwnProperty(mode)) return ensureDeps(mode, cont);
+ if (CodeMirror.modes.hasOwnProperty(mode)) return ensureDeps(mode, cont, options);
if (loading.hasOwnProperty(mode)) return loading[mode].push(cont);
- var file = CodeMirror.modeURL.replace(/%N/g, mode);
- if (env == "plain") {
+ var file = options && options.path ? options.path(mode) : CodeMirror.modeURL.replace(/%N/g, mode);
+ if (options && options.loadMode) {
+ options.loadMode(file, function() { ensureDeps(mode, cont, options) })
+ } else if (env == "plain") {
var script = document.createElement("script");
script.src = file;
var others = document.getElementsByTagName("script")[0];
@@ -44,7 +46,7 @@
CodeMirror.on(script, "load", function() {
ensureDeps(mode, function() {
for (var i = 0; i < list.length; ++i) list[i]();
- });
+ }, options);
});
others.parentNode.insertBefore(script, others);
} else if (env == "cjs") {
@@ -55,10 +57,10 @@
}
};
- CodeMirror.autoLoadMode = function(instance, mode) {
+ CodeMirror.autoLoadMode = function(instance, mode, options) {
if (!CodeMirror.modes.hasOwnProperty(mode))
CodeMirror.requireMode(mode, function() {
instance.setOption("mode", instance.getOption("mode"));
- });
+ }, options);
};
});
diff --git a/static/editor.md/lib/codemirror/addon/mode/multiplex.js b/static/editor.md/lib/codemirror/addon/mode/multiplex.js
index 6a95b323c..6140bc4f9 100644
--- a/static/editor.md/lib/codemirror/addon/mode/multiplex.js
+++ b/static/editor.md/lib/codemirror/addon/mode/multiplex.js
@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
@@ -12,14 +12,16 @@
"use strict";
CodeMirror.multiplexingMode = function(outer /*, others */) {
- // Others should be {open, close, mode [, delimStyle] [, innerStyle]} objects
+ // Others should be {open, close, mode [, delimStyle] [, innerStyle] [, parseDelimiters]} objects
var others = Array.prototype.slice.call(arguments, 1);
- var n_others = others.length;
- function indexOf(string, pattern, from) {
- if (typeof pattern == "string") return string.indexOf(pattern, from);
+ function indexOf(string, pattern, from, returnEnd) {
+ if (typeof pattern == "string") {
+ var found = string.indexOf(pattern, from);
+ return returnEnd && found > -1 ? found + pattern.length : found;
+ }
var m = pattern.exec(from ? string.slice(from) : string);
- return m ? m.index + from : -1;
+ return m ? m.index + from + (returnEnd ? m[0].length : 0) : -1;
}
return {
@@ -27,7 +29,8 @@ CodeMirror.multiplexingMode = function(outer /*, others */) {
return {
outer: CodeMirror.startState(outer),
innerActive: null,
- inner: null
+ inner: null,
+ startingInner: false
};
},
@@ -35,21 +38,31 @@ CodeMirror.multiplexingMode = function(outer /*, others */) {
return {
outer: CodeMirror.copyState(outer, state.outer),
innerActive: state.innerActive,
- inner: state.innerActive && CodeMirror.copyState(state.innerActive.mode, state.inner)
+ inner: state.innerActive && CodeMirror.copyState(state.innerActive.mode, state.inner),
+ startingInner: state.startingInner
};
},
token: function(stream, state) {
if (!state.innerActive) {
var cutOff = Infinity, oldContent = stream.string;
- for (var i = 0; i < n_others; ++i) {
+ for (var i = 0; i < others.length; ++i) {
var other = others[i];
var found = indexOf(oldContent, other.open, stream.pos);
if (found == stream.pos) {
- stream.match(other.open);
+ if (!other.parseDelimiters) stream.match(other.open);
+ state.startingInner = !!other.parseDelimiters
state.innerActive = other;
- state.inner = CodeMirror.startState(other.mode, outer.indent ? outer.indent(state.outer, "") : 0);
- return other.delimStyle;
+
+ // Get the outer indent, making sure to handle CodeMirror.Pass
+ var outerIndent = 0;
+ if (outer.indent) {
+ var possibleOuterIndent = outer.indent(state.outer, "", "");
+ if (possibleOuterIndent !== CodeMirror.Pass) outerIndent = possibleOuterIndent;
+ }
+
+ state.inner = CodeMirror.startState(other.mode, outerIndent);
+ return other.delimStyle && (other.delimStyle + " " + other.delimStyle + "-open");
} else if (found != -1 && found < cutOff) {
cutOff = found;
}
@@ -64,18 +77,23 @@ CodeMirror.multiplexingMode = function(outer /*, others */) {
state.innerActive = state.inner = null;
return this.token(stream, state);
}
- var found = curInner.close ? indexOf(oldContent, curInner.close, stream.pos) : -1;
- if (found == stream.pos) {
+ var found = curInner.close && !state.startingInner ?
+ indexOf(oldContent, curInner.close, stream.pos, curInner.parseDelimiters) : -1;
+ if (found == stream.pos && !curInner.parseDelimiters) {
stream.match(curInner.close);
state.innerActive = state.inner = null;
- return curInner.delimStyle;
+ return curInner.delimStyle && (curInner.delimStyle + " " + curInner.delimStyle + "-close");
}
if (found > -1) stream.string = oldContent.slice(0, found);
var innerToken = curInner.mode.token(stream, state.inner);
if (found > -1) stream.string = oldContent;
+ else if (stream.pos > stream.start) state.startingInner = false
+
+ if (found == stream.pos && curInner.parseDelimiters)
+ state.innerActive = state.inner = null;
if (curInner.innerStyle) {
- if (innerToken) innerToken = innerToken + ' ' + curInner.innerStyle;
+ if (innerToken) innerToken = innerToken + " " + curInner.innerStyle;
else innerToken = curInner.innerStyle;
}
@@ -83,10 +101,10 @@ CodeMirror.multiplexingMode = function(outer /*, others */) {
}
},
- indent: function(state, textAfter) {
+ indent: function(state, textAfter, line) {
var mode = state.innerActive ? state.innerActive.mode : outer;
if (!mode.indent) return CodeMirror.Pass;
- return mode.indent(state.innerActive ? state.inner : state.outer, textAfter);
+ return mode.indent(state.innerActive ? state.inner : state.outer, textAfter, line);
},
blankLine: function(state) {
@@ -95,11 +113,11 @@ CodeMirror.multiplexingMode = function(outer /*, others */) {
mode.blankLine(state.innerActive ? state.inner : state.outer);
}
if (!state.innerActive) {
- for (var i = 0; i < n_others; ++i) {
+ for (var i = 0; i < others.length; ++i) {
var other = others[i];
if (other.open === "\n") {
state.innerActive = other;
- state.inner = CodeMirror.startState(other.mode, mode.indent ? mode.indent(state.outer, "") : 0);
+ state.inner = CodeMirror.startState(other.mode, mode.indent ? mode.indent(state.outer, "", "") : 0);
}
}
} else if (state.innerActive.close === "\n") {
diff --git a/static/editor.md/lib/codemirror/addon/mode/multiplex_test.js b/static/editor.md/lib/codemirror/addon/mode/multiplex_test.js
index d33943420..850398867 100644
--- a/static/editor.md/lib/codemirror/addon/mode/multiplex_test.js
+++ b/static/editor.md/lib/codemirror/addon/mode/multiplex_test.js
@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
(function() {
CodeMirror.defineMode("markdown_with_stex", function(){
@@ -29,5 +29,21 @@
MT(
"stexInsideMarkdown",
- "[strong **Equation:**] [delim $][inner&tag \\pi][delim $]");
+ "[strong **Equation:**] [delim&delim-open $][inner&tag \\pi][delim&delim-close $]");
+
+ CodeMirror.defineMode("identical_delim_multiplex", function() {
+ return CodeMirror.multiplexingMode(CodeMirror.getMode({indentUnit: 2}, "javascript"), {
+ open: "#",
+ close: "#",
+ mode: CodeMirror.getMode({}, "markdown"),
+ parseDelimiters: true,
+ innerStyle: "q"
+ });
+ });
+
+ var mode2 = CodeMirror.getMode({}, "identical_delim_multiplex");
+
+ test.mode("identical_delimiters_with_parseDelimiters", mode2, [
+ "[keyword let] [def x] [operator =] [q #foo][q&em *bar*][q #];"
+ ], "multiplexing")
})();
diff --git a/static/editor.md/lib/codemirror/addon/mode/overlay.js b/static/editor.md/lib/codemirror/addon/mode/overlay.js
index e1b9ed375..016e3c28c 100644
--- a/static/editor.md/lib/codemirror/addon/mode/overlay.js
+++ b/static/editor.md/lib/codemirror/addon/mode/overlay.js
@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
// Utility function that allows modes to be combined. The mode given
// as the base argument takes care of most of the normal mode
@@ -68,16 +68,21 @@ CodeMirror.overlayMode = function(base, overlay, combine) {
else return state.overlayCur;
},
- indent: base.indent && function(state, textAfter) {
- return base.indent(state.base, textAfter);
+ indent: base.indent && function(state, textAfter, line) {
+ return base.indent(state.base, textAfter, line);
},
electricChars: base.electricChars,
innerMode: function(state) { return {state: state.base, mode: base}; },
blankLine: function(state) {
- if (base.blankLine) base.blankLine(state.base);
- if (overlay.blankLine) overlay.blankLine(state.overlay);
+ var baseToken, overlayToken;
+ if (base.blankLine) baseToken = base.blankLine(state.base);
+ if (overlay.blankLine) overlayToken = overlay.blankLine(state.overlay);
+
+ return overlayToken == null ?
+ baseToken :
+ (combine && baseToken != null ? baseToken + " " + overlayToken : overlayToken);
}
};
};
diff --git a/static/editor.md/lib/codemirror/addon/mode/simple.js b/static/editor.md/lib/codemirror/addon/mode/simple.js
index 795328b83..0d8cbdebd 100644
--- a/static/editor.md/lib/codemirror/addon/mode/simple.js
+++ b/static/editor.md/lib/codemirror/addon/mode/simple.js
@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
@@ -60,7 +60,7 @@
function ensureState(states, name) {
if (!states.hasOwnProperty(name))
- throw new Error("Undefined state " + name + "in simple mode");
+ throw new Error("Undefined state " + name + " in simple mode");
}
function toRegex(val, caret) {
@@ -68,6 +68,7 @@
var flags = "";
if (val instanceof RegExp) {
if (val.ignoreCase) flags = "i";
+ if (val.unicode) flags += "u"
val = val.source;
} else {
val = String(val);
@@ -77,6 +78,7 @@
function asToken(val) {
if (!val) return null;
+ if (val.apply) return val
if (typeof val == "string") return val.replace(/\./g, " ");
var result = [];
for (var i = 0; i < val.length; i++)
@@ -133,17 +135,18 @@
state.indent.push(stream.indentation() + config.indentUnit);
if (rule.data.dedent)
state.indent.pop();
- if (matches.length > 2) {
- state.pending = [];
+ var token = rule.token
+ if (token && token.apply) token = token(matches)
+ if (matches.length > 2 && rule.token && typeof rule.token != "string") {
for (var j = 2; j < matches.length; j++)
if (matches[j])
- state.pending.push({text: matches[j], token: rule.token[j - 1]});
+ (state.pending || (state.pending = [])).push({text: matches[j], token: rule.token[j - 1]});
stream.backUp(matches[0].length - (matches[1] ? matches[1].length : 0));
- return rule.token[0];
- } else if (rule.token && rule.token.join) {
- return rule.token[0];
+ return token[0];
+ } else if (token && token.join) {
+ return token[0];
} else {
- return rule.token;
+ return token;
}
}
}
diff --git a/static/editor.md/lib/codemirror/addon/runmode/colorize.js b/static/editor.md/lib/codemirror/addon/runmode/colorize.js
index eb7060d0a..3be541150 100644
--- a/static/editor.md/lib/codemirror/addon/runmode/colorize.js
+++ b/static/editor.md/lib/codemirror/addon/runmode/colorize.js
@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
diff --git a/static/editor.md/lib/codemirror/addon/runmode/runmode-standalone.js b/static/editor.md/lib/codemirror/addon/runmode/runmode-standalone.js
index f4f352c80..76cf3d758 100644
--- a/static/editor.md/lib/codemirror/addon/runmode/runmode-standalone.js
+++ b/static/editor.md/lib/codemirror/addon/runmode/runmode-standalone.js
@@ -1,157 +1,334 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-window.CodeMirror = {};
-
-(function() {
-"use strict";
-
-function splitLines(string){ return string.split(/\r?\n|\r/); };
-
-function StringStream(string) {
- this.pos = this.start = 0;
- this.string = string;
- this.lineStart = 0;
-}
-StringStream.prototype = {
- eol: function() {return this.pos >= this.string.length;},
- sol: function() {return this.pos == 0;},
- peek: function() {return this.string.charAt(this.pos) || null;},
- next: function() {
+(function () {
+ 'use strict';
+
+ 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;
+ }
+ }
+
+ 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
+ }
+
+ // 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++);
- },
- eat: function(match) {
+ { return this.string.charAt(this.pos++) }
+ };
+ StringStream.prototype.eat = function (match) {
var ch = this.string.charAt(this.pos);
- if (typeof match == "string") var ok = ch == match;
- else var ok = ch && (match.test ? match.test(ch) : match(ch));
- if (ok) {++this.pos; return ch;}
- },
- eatWhile: function(match) {
+ 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;
- },
- eatSpace: function() {
+ return this.pos > start
+ };
+ StringStream.prototype.eatSpace = function () {
var start = this.pos;
- while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos;
- return this.pos > start;
- },
- skipToEnd: function() {this.pos = this.string.length;},
- skipTo: function(ch) {
+ while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) { ++this.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;}
- },
- backUp: function(n) {this.pos -= n;},
- column: function() {return this.start - this.lineStart;},
- indentation: function() {return 0;},
- match: function(pattern, consume, caseInsensitive) {
+ 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 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;
+ 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;
+ if (match && match.index > 0) { return null }
+ if (match && consume !== false) { this.pos += match[0].length; }
+ return match
}
- },
- current: function(){return this.string.slice(this.start, this.pos);},
- hideFirstChars: function(n, inner) {
+ };
+ StringStream.prototype.current = function (){return this.string.slice(this.start, this.pos)};
+ StringStream.prototype.hideFirstChars = function (n, inner) {
this.lineStart += n;
- try { return inner(); }
+ try { return inner() }
finally { this.lineStart -= n; }
+ };
+ StringStream.prototype.lookAhead = function (n) {
+ var oracle = this.lineOracle;
+ return oracle && oracle.lookAhead(n)
+ };
+ StringStream.prototype.baseToken = function () {
+ var oracle = this.lineOracle;
+ return oracle && oracle.baseToken(this.pos)
+ };
+
+ // Known modes, by name and by MIME
+ var modes = {}, 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;
}
-};
-CodeMirror.StringStream = StringStream;
-
-CodeMirror.startState = function (mode, a1, a2) {
- return mode.startState ? mode.startState(a1, a2) : true;
-};
-
-var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {};
-CodeMirror.defineMode = function (name, mode) {
- if (arguments.length > 2)
- mode.dependencies = Array.prototype.slice.call(arguments, 2);
- modes[name] = mode;
-};
-CodeMirror.defineMIME = function (mime, spec) { mimeModes[mime] = spec; };
-CodeMirror.resolveMode = function(spec) {
- if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) {
- spec = mimeModes[spec];
- } else if (spec && typeof spec.name == "string" && mimeModes.hasOwnProperty(spec.name)) {
- spec = mimeModes[spec.name];
+
+ function defineMIME(mime, spec) {
+ mimeModes[mime] = spec;
}
- if (typeof spec == "string") return {name: spec};
- else return spec || {name: "null"};
-};
-CodeMirror.getMode = function (options, spec) {
- spec = CodeMirror.resolveMode(spec);
- var mfactory = modes[spec.name];
- if (!mfactory) throw new Error("Unknown mode: " + spec);
- return mfactory(options, spec);
-};
-CodeMirror.registerHelper = CodeMirror.registerGlobalHelper = Math.min;
-CodeMirror.defineMode("null", function() {
- return {token: function(stream) {stream.skipToEnd();}};
-});
-CodeMirror.defineMIME("text/plain", "null");
-
-CodeMirror.runMode = function (string, modespec, callback, options) {
- var mode = CodeMirror.getMode({ indentUnit: 2 }, modespec);
-
- if (callback.nodeType == 1) {
- var tabSize = (options && options.tabSize) || 4;
- var node = callback, col = 0;
- node.innerHTML = "";
- callback = function (text, style) {
- if (text == "\n") {
- node.appendChild(document.createElement("br"));
- col = 0;
- return;
- }
- var content = "";
- // replace tabs
- for (var pos = 0; ;) {
- var idx = text.indexOf("\t", pos);
- if (idx == -1) {
- content += text.slice(pos);
- col += text.length - pos;
- break;
- } else {
- col += idx - pos;
- content += text.slice(pos, idx);
- var size = tabSize - col % tabSize;
- col += size;
- for (var i = 0; i < size; ++i) content += " ";
- pos = idx + 1;
- }
- }
- if (style) {
- var sp = node.appendChild(document.createElement("span"));
- sp.className = "cm-" + style.replace(/ +/g, " cm-");
- sp.appendChild(document.createTextNode(content));
- } else {
- node.appendChild(document.createTextNode(content));
+ // 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
}
- var lines = splitLines(string), state = (options && options.state) || CodeMirror.startState(mode);
- for (var i = 0, e = lines.length; i < e; ++i) {
- if (i) callback("\n");
- var stream = new CodeMirror.StringStream(lines[i]);
- if (!stream.string && mode.blankLine) mode.blankLine(state);
- while (!stream.eol()) {
- var style = mode.token(stream, state);
- callback(stream.current(), style, i, stream.start, state);
- stream.start = stream.pos;
+ // 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
}
-};
-})();
+
+ var modeMethods = {
+ __proto__: null,
+ modes: modes,
+ mimeModes: mimeModes,
+ defineMode: defineMode,
+ defineMIME: defineMIME,
+ resolveMode: resolveMode,
+ getMode: getMode,
+ modeExtensions: modeExtensions,
+ extendMode: extendMode,
+ copyState: copyState,
+ innerMode: innerMode,
+ startState: startState
+ };
+
+ // declare global: globalThis, CodeMirror
+
+ // Create a minimal CodeMirror needed to use runMode, and assign to root.
+ var root = typeof globalThis !== 'undefined' ? globalThis : window;
+ root.CodeMirror = {};
+
+ // Copy StringStream and mode methods into CodeMirror object.
+ CodeMirror.StringStream = StringStream;
+ for (var exported in modeMethods) { CodeMirror[exported] = modeMethods[exported]; }
+
+ // Minimal default mode.
+ CodeMirror.defineMode("null", function () { return ({token: function (stream) { return stream.skipToEnd(); }}); });
+ CodeMirror.defineMIME("text/plain", "null");
+
+ CodeMirror.registerHelper = CodeMirror.registerGlobalHelper = Math.min;
+ CodeMirror.splitLines = function(string) { return string.split(/\r?\n|\r/) };
+ CodeMirror.countColumn = countColumn;
+
+ CodeMirror.defaults = { indentUnit: 2 };
+
+ // CodeMirror, copyright (c) by Marijn Haverbeke and others
+ // Distributed under an MIT license: https://codemirror.net/LICENSE
+
+ (function(mod) {
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
+ { mod(require("../../lib/codemirror")); }
+ else if (typeof define == "function" && define.amd) // AMD
+ { define(["../../lib/codemirror"], mod); }
+ else // Plain browser env
+ { mod(CodeMirror); }
+ })(function(CodeMirror) {
+
+ CodeMirror.runMode = function(string, modespec, callback, options) {
+ var mode = CodeMirror.getMode(CodeMirror.defaults, modespec);
+ var tabSize = (options && options.tabSize) || CodeMirror.defaults.tabSize;
+
+ // Create a tokenizing callback function if passed-in callback is a DOM element.
+ if (callback.appendChild) {
+ var ie = /MSIE \d/.test(navigator.userAgent);
+ var ie_lt9 = ie && (document.documentMode == null || document.documentMode < 9);
+ var node = callback, col = 0;
+ node.innerHTML = "";
+ callback = function(text, style) {
+ if (text == "\n") {
+ // Emitting LF or CRLF on IE8 or earlier results in an incorrect display.
+ // Emitting a carriage return makes everything ok.
+ node.appendChild(document.createTextNode(ie_lt9 ? '\r' : text));
+ col = 0;
+ return;
+ }
+ var content = "";
+ // replace tabs
+ for (var pos = 0;;) {
+ var idx = text.indexOf("\t", pos);
+ if (idx == -1) {
+ content += text.slice(pos);
+ col += text.length - pos;
+ break;
+ } else {
+ col += idx - pos;
+ content += text.slice(pos, idx);
+ var size = tabSize - col % tabSize;
+ col += size;
+ for (var i = 0; i < size; ++i) { content += " "; }
+ pos = idx + 1;
+ }
+ }
+ // Create a node with token style and append it to the callback DOM element.
+ if (style) {
+ var sp = node.appendChild(document.createElement("span"));
+ sp.className = "cm-" + style.replace(/ +/g, " cm-");
+ sp.appendChild(document.createTextNode(content));
+ } else {
+ node.appendChild(document.createTextNode(content));
+ }
+ };
+ }
+
+ var lines = CodeMirror.splitLines(string), state = (options && options.state) || CodeMirror.startState(mode);
+ for (var i = 0, e = lines.length; i < e; ++i) {
+ if (i) { callback("\n"); }
+ var stream = new CodeMirror.StringStream(lines[i], null, {
+ lookAhead: function(n) { return lines[i + n] },
+ baseToken: function() {}
+ });
+ if (!stream.string && mode.blankLine) { mode.blankLine(state); }
+ while (!stream.eol()) {
+ var style = mode.token(stream, state);
+ callback(stream.current(), style, i, stream.start, state, mode);
+ stream.start = stream.pos;
+ }
+ }
+ };
+
+ });
+
+}());
diff --git a/static/editor.md/lib/codemirror/addon/runmode/runmode.js b/static/editor.md/lib/codemirror/addon/runmode/runmode.js
index 07d2279f7..f5d58e242 100644
--- a/static/editor.md/lib/codemirror/addon/runmode/runmode.js
+++ b/static/editor.md/lib/codemirror/addon/runmode/runmode.js
@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
@@ -13,11 +13,12 @@
CodeMirror.runMode = function(string, modespec, callback, options) {
var mode = CodeMirror.getMode(CodeMirror.defaults, modespec);
- var ie = /MSIE \d/.test(navigator.userAgent);
- var ie_lt9 = ie && (document.documentMode == null || document.documentMode < 9);
+ var tabSize = (options && options.tabSize) || CodeMirror.defaults.tabSize;
- if (callback.nodeType == 1) {
- var tabSize = (options && options.tabSize) || CodeMirror.defaults.tabSize;
+ // Create a tokenizing callback function if passed-in callback is a DOM element.
+ if (callback.appendChild) {
+ var ie = /MSIE \d/.test(navigator.userAgent);
+ var ie_lt9 = ie && (document.documentMode == null || document.documentMode < 9);
var node = callback, col = 0;
node.innerHTML = "";
callback = function(text, style) {
@@ -45,7 +46,7 @@ CodeMirror.runMode = function(string, modespec, callback, options) {
pos = idx + 1;
}
}
-
+ // Create a node with token style and append it to the callback DOM element.
if (style) {
var sp = node.appendChild(document.createElement("span"));
sp.className = "cm-" + style.replace(/ +/g, " cm-");
@@ -59,11 +60,14 @@ CodeMirror.runMode = function(string, modespec, callback, options) {
var lines = CodeMirror.splitLines(string), state = (options && options.state) || CodeMirror.startState(mode);
for (var i = 0, e = lines.length; i < e; ++i) {
if (i) callback("\n");
- var stream = new CodeMirror.StringStream(lines[i]);
+ var stream = new CodeMirror.StringStream(lines[i], null, {
+ lookAhead: function(n) { return lines[i + n] },
+ baseToken: function() {}
+ });
if (!stream.string && mode.blankLine) mode.blankLine(state);
while (!stream.eol()) {
var style = mode.token(stream, state);
- callback(stream.current(), style, i, stream.start, state);
+ callback(stream.current(), style, i, stream.start, state, mode);
stream.start = stream.pos;
}
}
diff --git a/static/editor.md/lib/codemirror/addon/runmode/runmode.node.js b/static/editor.md/lib/codemirror/addon/runmode/runmode.node.js
index 8b8140b4c..ddd255ab4 100644
--- a/static/editor.md/lib/codemirror/addon/runmode/runmode.node.js
+++ b/static/editor.md/lib/codemirror/addon/runmode/runmode.node.js
@@ -1,120 +1,329 @@
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+'use strict';
+
+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
+}
-/* Just enough of CodeMirror to run runMode under node.js */
+// 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;
+ }
+}
+
+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
+}
-// declare global: StringStream
+// STRING STREAM
-function splitLines(string){ return string.split(/\r?\n|\r/); };
+// Fed to the mode parsers, provides helper functions to make
+// parsers more succinct.
-function StringStream(string) {
+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;
-}
-StringStream.prototype = {
- eol: function() {return this.pos >= this.string.length;},
- sol: function() {return this.pos == 0;},
- peek: function() {return this.string.charAt(this.pos) || null;},
- next: function() {
- if (this.pos < this.string.length)
- return this.string.charAt(this.pos++);
- },
- eat: function(match) {
- var ch = this.string.charAt(this.pos);
- if (typeof match == "string") var ok = ch == match;
- else var ok = ch && (match.test ? match.test(ch) : match(ch));
- if (ok) {++this.pos; return ch;}
- },
- eatWhile: function(match) {
- var start = this.pos;
- while (this.eat(match)){}
- return this.pos > start;
- },
- eatSpace: function() {
- var start = this.pos;
- while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos;
- return this.pos > start;
- },
- skipToEnd: function() {this.pos = this.string.length;},
- skipTo: function(ch) {
- var found = this.string.indexOf(ch, this.pos);
- if (found > -1) {this.pos = found; return true;}
- },
- backUp: function(n) {this.pos -= n;},
- column: function() {return this.start - this.lineStart;},
- indentation: function() {return 0;},
- 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;
+ 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 start = this.pos;
+ while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) { ++this.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
}
- },
- current: function(){return this.string.slice(this.start, this.pos);},
- hideFirstChars: function(n, inner) {
- this.lineStart += n;
- try { return inner(); }
- finally { this.lineStart -= n; }
+ } 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
}
};
-exports.StringStream = StringStream;
-
-exports.startState = function(mode, a1, a2) {
- return mode.startState ? mode.startState(a1, a2) : true;
+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)
};
+StringStream.prototype.baseToken = function () {
+ var oracle = this.lineOracle;
+ return oracle && oracle.baseToken(this.pos)
+};
+
+// Known modes, by name and by MIME
+var modes = {}, mimeModes = {};
-var modes = exports.modes = {}, mimeModes = exports.mimeModes = {};
-exports.defineMode = function(name, mode) {
+// 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);
+ { mode.dependencies = Array.prototype.slice.call(arguments, 2); }
modes[name] = mode;
-};
-exports.defineMIME = function(mime, spec) { mimeModes[mime] = spec; };
+}
-exports.defineMode("null", function() {
- return {token: function(stream) {stream.skipToEnd();}};
-});
-exports.defineMIME("text/plain", "null");
+function defineMIME(mime, spec) {
+ mimeModes[mime] = spec;
+}
-exports.resolveMode = function(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)) {
- spec = mimeModes[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"};
-};
-exports.getMode = function(options, spec) {
- spec = exports.resolveMode(spec);
+ 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) throw new Error("Unknown mode: " + spec);
- return mfactory(options, spec);
+ 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
+}
+
+var modeMethods = {
+ __proto__: null,
+ modes: modes,
+ mimeModes: mimeModes,
+ defineMode: defineMode,
+ defineMIME: defineMIME,
+ resolveMode: resolveMode,
+ getMode: getMode,
+ modeExtensions: modeExtensions,
+ extendMode: extendMode,
+ copyState: copyState,
+ innerMode: innerMode,
+ startState: startState
};
+
+// Copy StringStream and mode methods into exports (CodeMirror) object.
+exports.StringStream = StringStream;
+exports.countColumn = countColumn;
+for (var exported in modeMethods) { exports[exported] = modeMethods[exported]; }
+
+// Shim library CodeMirror with the minimal CodeMirror defined above.
+require.cache[require.resolve("../../lib/codemirror")] = require.cache[require.resolve("./runmode.node")];
+require.cache[require.resolve("../../addon/runmode/runmode")] = require.cache[require.resolve("./runmode.node")];
+
+// Minimal default mode.
+exports.defineMode("null", function () { return ({token: function (stream) { return stream.skipToEnd(); }}); });
+exports.defineMIME("text/plain", "null");
+
exports.registerHelper = exports.registerGlobalHelper = Math.min;
+exports.splitLines = function(string) { return string.split(/\r?\n|\r/) };
+
+exports.defaults = { indentUnit: 2 };
+
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: https://codemirror.net/LICENSE
+
+(function(mod) {
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
+ { mod(require("../../lib/codemirror")); }
+ else if (typeof define == "function" && define.amd) // AMD
+ { define(["../../lib/codemirror"], mod); }
+ else // Plain browser env
+ { mod(CodeMirror); }
+})(function(CodeMirror) {
-exports.runMode = function(string, modespec, callback, options) {
- var mode = exports.getMode({indentUnit: 2}, modespec);
- var lines = splitLines(string), state = (options && options.state) || exports.startState(mode);
+CodeMirror.runMode = function(string, modespec, callback, options) {
+ var mode = CodeMirror.getMode(CodeMirror.defaults, modespec);
+ var tabSize = (options && options.tabSize) || CodeMirror.defaults.tabSize;
+
+ // Create a tokenizing callback function if passed-in callback is a DOM element.
+ if (callback.appendChild) {
+ var ie = /MSIE \d/.test(navigator.userAgent);
+ var ie_lt9 = ie && (document.documentMode == null || document.documentMode < 9);
+ var node = callback, col = 0;
+ node.innerHTML = "";
+ callback = function(text, style) {
+ if (text == "\n") {
+ // Emitting LF or CRLF on IE8 or earlier results in an incorrect display.
+ // Emitting a carriage return makes everything ok.
+ node.appendChild(document.createTextNode(ie_lt9 ? '\r' : text));
+ col = 0;
+ return;
+ }
+ var content = "";
+ // replace tabs
+ for (var pos = 0;;) {
+ var idx = text.indexOf("\t", pos);
+ if (idx == -1) {
+ content += text.slice(pos);
+ col += text.length - pos;
+ break;
+ } else {
+ col += idx - pos;
+ content += text.slice(pos, idx);
+ var size = tabSize - col % tabSize;
+ col += size;
+ for (var i = 0; i < size; ++i) { content += " "; }
+ pos = idx + 1;
+ }
+ }
+ // Create a node with token style and append it to the callback DOM element.
+ if (style) {
+ var sp = node.appendChild(document.createElement("span"));
+ sp.className = "cm-" + style.replace(/ +/g, " cm-");
+ sp.appendChild(document.createTextNode(content));
+ } else {
+ node.appendChild(document.createTextNode(content));
+ }
+ };
+ }
+
+ var lines = CodeMirror.splitLines(string), state = (options && options.state) || CodeMirror.startState(mode);
for (var i = 0, e = lines.length; i < e; ++i) {
- if (i) callback("\n");
- var stream = new exports.StringStream(lines[i]);
- if (!stream.string && mode.blankLine) mode.blankLine(state);
+ if (i) { callback("\n"); }
+ var stream = new CodeMirror.StringStream(lines[i], null, {
+ lookAhead: function(n) { return lines[i + n] },
+ baseToken: function() {}
+ });
+ if (!stream.string && mode.blankLine) { mode.blankLine(state); }
while (!stream.eol()) {
var style = mode.token(stream, state);
- callback(stream.current(), style, i, stream.start, state);
+ callback(stream.current(), style, i, stream.start, state, mode);
stream.start = stream.pos;
}
}
};
-require.cache[require.resolve("../../lib/codemirror")] = require.cache[require.resolve("./runmode.node")];
+});
diff --git a/static/editor.md/lib/codemirror/addon/scroll/annotatescrollbar.js b/static/editor.md/lib/codemirror/addon/scroll/annotatescrollbar.js
index 54aeacf27..c12e44cda 100644
--- a/static/editor.md/lib/codemirror/addon/scroll/annotatescrollbar.js
+++ b/static/editor.md/lib/codemirror/addon/scroll/annotatescrollbar.js
@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
@@ -43,7 +43,7 @@
cm.on("markerAdded", this.resizeHandler);
cm.on("markerCleared", this.resizeHandler);
if (options.listenForChanges !== false)
- cm.on("change", this.changeHandler = function() {
+ cm.on("changes", this.changeHandler = function() {
scheduleRedraw(250);
});
}
@@ -51,7 +51,7 @@
Annotation.prototype.computeScale = function() {
var cm = this.cm;
var hScale = (cm.getWrapperElement().clientHeight - cm.display.barHeight - this.buttonHeight * 2) /
- cm.heightAtLine(cm.lastLine() + 1, "local");
+ cm.getScrollerElement().scrollHeight
if (hScale != this.hScale) {
this.hScale = hScale;
return true;
@@ -68,15 +68,40 @@
var cm = this.cm, hScale = this.hScale;
var frag = document.createDocumentFragment(), anns = this.annotations;
+
+ var wrapping = cm.getOption("lineWrapping");
+ var singleLineH = wrapping && cm.defaultTextHeight() * 1.5;
+ var curLine = null, curLineObj = null;
+
+ function getY(pos, top) {
+ if (curLine != pos.line) {
+ curLine = pos.line
+ curLineObj = cm.getLineHandle(pos.line)
+ var visual = cm.getLineHandleVisualStart(curLineObj)
+ if (visual != curLineObj) {
+ curLine = cm.getLineNumber(visual)
+ curLineObj = visual
+ }
+ }
+ if ((curLineObj.widgets && curLineObj.widgets.length) ||
+ (wrapping && curLineObj.height > singleLineH))
+ return cm.charCoords(pos, "local")[top ? "top" : "bottom"];
+ var topY = cm.heightAtLine(curLineObj, "local");
+ return topY + (top ? 0 : curLineObj.height);
+ }
+
+ var lastLine = cm.lastLine()
if (cm.display.barWidth) for (var i = 0, nextTop; i < anns.length; i++) {
var ann = anns[i];
- var top = nextTop || cm.charCoords(ann.from, "local").top * hScale;
- var bottom = cm.charCoords(ann.to, "local").bottom * hScale;
+ if (ann.to.line > lastLine) continue;
+ var top = nextTop || getY(ann.from, true) * hScale;
+ var bottom = getY(ann.to, false) * hScale;
while (i < anns.length - 1) {
- nextTop = cm.charCoords(anns[i + 1].from, "local").top * hScale;
+ if (anns[i + 1].to.line > lastLine) break;
+ nextTop = getY(anns[i + 1].from, true) * hScale;
if (nextTop > bottom + .9) break;
ann = anns[++i];
- bottom = cm.charCoords(ann.to, "local").bottom * hScale;
+ bottom = getY(ann.to, false) * hScale;
}
if (bottom == top) continue;
var height = Math.max(bottom - top, 3);
@@ -85,6 +110,9 @@
elt.style.cssText = "position: absolute; right: 0px; width: " + Math.max(cm.display.barWidth - 1, 2) + "px; top: "
+ (top + this.buttonHeight) + "px; height: " + height + "px";
elt.className = this.options.className;
+ if (ann.id) {
+ elt.setAttribute("annotation-id", ann.id);
+ }
}
this.div.textContent = "";
this.div.appendChild(frag);
@@ -94,7 +122,7 @@
this.cm.off("refresh", this.resizeHandler);
this.cm.off("markerAdded", this.resizeHandler);
this.cm.off("markerCleared", this.resizeHandler);
- if (this.changeHandler) this.cm.off("change", this.changeHandler);
+ if (this.changeHandler) this.cm.off("changes", this.changeHandler);
this.div.parentNode.removeChild(this.div);
};
});
diff --git a/static/editor.md/lib/codemirror/addon/scroll/scrollpastend.js b/static/editor.md/lib/codemirror/addon/scroll/scrollpastend.js
index 008ae4c7b..2ed9d95e8 100644
--- a/static/editor.md/lib/codemirror/addon/scroll/scrollpastend.js
+++ b/static/editor.md/lib/codemirror/addon/scroll/scrollpastend.js
@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
@@ -40,7 +40,9 @@
if (cm.state.scrollPastEndPadding != padding) {
cm.state.scrollPastEndPadding = padding;
cm.display.lineSpace.parentNode.style.paddingBottom = padding;
+ cm.off("refresh", updateBottomMargin);
cm.setSize();
+ cm.on("refresh", updateBottomMargin);
}
}
});
diff --git a/static/editor.md/lib/codemirror/addon/scroll/simplescrollbars.js b/static/editor.md/lib/codemirror/addon/scroll/simplescrollbars.js
index bb06adb86..750a2bd39 100644
--- a/static/editor.md/lib/codemirror/addon/scroll/simplescrollbars.js
+++ b/static/editor.md/lib/codemirror/addon/scroll/simplescrollbars.js
@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
@@ -59,26 +59,38 @@
CodeMirror.on(this.node, "DOMMouseScroll", onWheel);
}
- Bar.prototype.moveTo = function(pos, update) {
+ Bar.prototype.setPos = function(pos, force) {
if (pos < 0) pos = 0;
if (pos > this.total - this.screen) pos = this.total - this.screen;
- if (pos == this.pos) return;
+ if (!force && pos == this.pos) return false;
this.pos = pos;
this.inner.style[this.orientation == "horizontal" ? "left" : "top"] =
(pos * (this.size / this.total)) + "px";
- if (update !== false) this.scroll(pos, this.orientation);
+ return true
};
+ Bar.prototype.moveTo = function(pos) {
+ if (this.setPos(pos)) this.scroll(pos, this.orientation);
+ }
+
+ var minButtonSize = 10;
+
Bar.prototype.update = function(scrollSize, clientSize, barSize) {
- this.screen = clientSize;
- this.total = scrollSize;
- this.size = barSize;
+ var sizeChanged = this.screen != clientSize || this.total != scrollSize || this.size != barSize
+ if (sizeChanged) {
+ this.screen = clientSize;
+ this.total = scrollSize;
+ this.size = barSize;
+ }
- // FIXME clip to min size?
+ var buttonSize = this.screen * (this.size / this.total);
+ if (buttonSize < minButtonSize) {
+ this.size -= minButtonSize - buttonSize;
+ buttonSize = minButtonSize;
+ }
this.inner.style[this.orientation == "horizontal" ? "width" : "height"] =
- this.screen * (this.size / this.total) + "px";
- this.inner.style[this.orientation == "horizontal" ? "left" : "top"] =
- this.pos * (this.size / this.total) + "px";
+ buttonSize + "px";
+ this.setPos(this.pos, sizeChanged);
};
function SimpleScrollbars(cls, place, scroll) {
@@ -105,7 +117,6 @@
if (needsV) {
this.vert.update(measure.scrollHeight, measure.clientHeight,
measure.viewHeight - (needsH ? width : 0));
- this.vert.node.style.display = "block";
this.vert.node.style.bottom = needsH ? width + "px" : "0";
}
if (needsH) {
@@ -119,11 +130,11 @@
};
SimpleScrollbars.prototype.setScrollTop = function(pos) {
- this.vert.moveTo(pos, false);
+ this.vert.setPos(pos);
};
SimpleScrollbars.prototype.setScrollLeft = function(pos) {
- this.horiz.moveTo(pos, false);
+ this.horiz.setPos(pos);
};
SimpleScrollbars.prototype.clear = function() {
diff --git a/static/editor.md/lib/codemirror/addon/search/jump-to-line.js b/static/editor.md/lib/codemirror/addon/search/jump-to-line.js
new file mode 100644
index 000000000..990c235ef
--- /dev/null
+++ b/static/editor.md/lib/codemirror/addon/search/jump-to-line.js
@@ -0,0 +1,53 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: https://codemirror.net/LICENSE
+
+// Defines jumpToLine command. Uses dialog.js if present.
+
+(function(mod) {
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
+ mod(require("../../lib/codemirror"), require("../dialog/dialog"));
+ else if (typeof define == "function" && define.amd) // AMD
+ define(["../../lib/codemirror", "../dialog/dialog"], mod);
+ else // Plain browser env
+ mod(CodeMirror);
+})(function(CodeMirror) {
+ "use strict";
+
+ // default search panel location
+ CodeMirror.defineOption("search", {bottom: false});
+
+ function dialog(cm, text, shortText, deflt, f) {
+ if (cm.openDialog) cm.openDialog(text, f, {value: deflt, selectValueOnOpen: true, bottom: cm.options.search.bottom});
+ else f(prompt(shortText, deflt));
+ }
+
+ function getJumpDialog(cm) {
+ return cm.phrase("Jump to line:") + ' ' + cm.phrase("(Use line:column or scroll% syntax)") + ' ';
+ }
+
+ function interpretLine(cm, string) {
+ var num = Number(string)
+ if (/^[-+]/.test(string)) return cm.getCursor().line + num
+ else return num - 1
+ }
+
+ CodeMirror.commands.jumpToLine = function(cm) {
+ var cur = cm.getCursor();
+ dialog(cm, getJumpDialog(cm), cm.phrase("Jump to line:"), (cur.line + 1) + ":" + cur.ch, function(posStr) {
+ if (!posStr) return;
+
+ var match;
+ if (match = /^\s*([\+\-]?\d+)\s*\:\s*(\d+)\s*$/.exec(posStr)) {
+ cm.setCursor(interpretLine(cm, match[1]), Number(match[2]))
+ } else if (match = /^\s*([\+\-]?\d+(\.\d+)?)\%\s*/.exec(posStr)) {
+ var line = Math.round(cm.lineCount() * Number(match[1]) / 100);
+ if (/^[-+]/.test(match[1])) line = cur.line + line + 1;
+ cm.setCursor(line - 1, cur.ch);
+ } else if (match = /^\s*\:?\s*([\+\-]?\d+)\s*/.exec(posStr)) {
+ cm.setCursor(interpretLine(cm, match[1]), cur.ch);
+ }
+ });
+ };
+
+ CodeMirror.keyMap["default"]["Alt-G"] = "jumpToLine";
+});
diff --git a/static/editor.md/lib/codemirror/addon/search/match-highlighter.js b/static/editor.md/lib/codemirror/addon/search/match-highlighter.js
index e9a22721f..9b181ebc0 100644
--- a/static/editor.md/lib/codemirror/addon/search/match-highlighter.js
+++ b/static/editor.md/lib/codemirror/addon/search/match-highlighter.js
@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
// Highlighting text that matches the selection
//
@@ -16,81 +16,120 @@
// highlighted only if the selected text is a word. showToken, when enabled,
// will cause the current token to be highlighted when nothing is selected.
// delay is used to specify how much time to wait, in milliseconds, before
-// highlighting the matches.
+// highlighting the matches. If annotateScrollbar is enabled, the occurrences
+// will be highlighted on the scrollbar via the matchesonscrollbar addon.
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
- mod(require("../../lib/codemirror"));
+ mod(require("../../lib/codemirror"), require("./matchesonscrollbar"));
else if (typeof define == "function" && define.amd) // AMD
- define(["../../lib/codemirror"], mod);
+ define(["../../lib/codemirror", "./matchesonscrollbar"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
- var DEFAULT_MIN_CHARS = 2;
- var DEFAULT_TOKEN_STYLE = "matchhighlight";
- var DEFAULT_DELAY = 100;
- var DEFAULT_WORDS_ONLY = false;
+ var defaults = {
+ style: "matchhighlight",
+ minChars: 2,
+ delay: 100,
+ wordsOnly: false,
+ annotateScrollbar: false,
+ showToken: false,
+ trim: true
+ }
function State(options) {
- if (typeof options == "object") {
- this.minChars = options.minChars;
- this.style = options.style;
- this.showToken = options.showToken;
- this.delay = options.delay;
- this.wordsOnly = options.wordsOnly;
- }
- if (this.style == null) this.style = DEFAULT_TOKEN_STYLE;
- if (this.minChars == null) this.minChars = DEFAULT_MIN_CHARS;
- if (this.delay == null) this.delay = DEFAULT_DELAY;
- if (this.wordsOnly == null) this.wordsOnly = DEFAULT_WORDS_ONLY;
+ this.options = {}
+ for (var name in defaults)
+ this.options[name] = (options && options.hasOwnProperty(name) ? options : defaults)[name]
this.overlay = this.timeout = null;
+ this.matchesonscroll = null;
+ this.active = false;
}
CodeMirror.defineOption("highlightSelectionMatches", false, function(cm, val, old) {
if (old && old != CodeMirror.Init) {
- var over = cm.state.matchHighlighter.overlay;
- if (over) cm.removeOverlay(over);
+ removeOverlay(cm);
clearTimeout(cm.state.matchHighlighter.timeout);
cm.state.matchHighlighter = null;
cm.off("cursorActivity", cursorActivity);
+ cm.off("focus", onFocus)
}
if (val) {
- cm.state.matchHighlighter = new State(val);
- highlightMatches(cm);
+ var state = cm.state.matchHighlighter = new State(val);
+ if (cm.hasFocus()) {
+ state.active = true
+ highlightMatches(cm)
+ } else {
+ cm.on("focus", onFocus)
+ }
cm.on("cursorActivity", cursorActivity);
}
});
function cursorActivity(cm) {
var state = cm.state.matchHighlighter;
+ if (state.active || cm.hasFocus()) scheduleHighlight(cm, state)
+ }
+
+ function onFocus(cm) {
+ var state = cm.state.matchHighlighter
+ if (!state.active) {
+ state.active = true
+ scheduleHighlight(cm, state)
+ }
+ }
+
+ function scheduleHighlight(cm, state) {
clearTimeout(state.timeout);
- state.timeout = setTimeout(function() {highlightMatches(cm);}, state.delay);
+ state.timeout = setTimeout(function() {highlightMatches(cm);}, state.options.delay);
+ }
+
+ function addOverlay(cm, query, hasBoundary, style) {
+ var state = cm.state.matchHighlighter;
+ cm.addOverlay(state.overlay = makeOverlay(query, hasBoundary, style));
+ if (state.options.annotateScrollbar && cm.showMatchesOnScrollbar) {
+ var searchFor = hasBoundary ? new RegExp((/\w/.test(query.charAt(0)) ? "\\b" : "") +
+ query.replace(/[\\\[.+*?(){|^$]/g, "\\$&") +
+ (/\w/.test(query.charAt(query.length - 1)) ? "\\b" : "")) : query;
+ state.matchesonscroll = cm.showMatchesOnScrollbar(searchFor, false,
+ {className: "CodeMirror-selection-highlight-scrollbar"});
+ }
+ }
+
+ function removeOverlay(cm) {
+ var state = cm.state.matchHighlighter;
+ if (state.overlay) {
+ cm.removeOverlay(state.overlay);
+ state.overlay = null;
+ if (state.matchesonscroll) {
+ state.matchesonscroll.clear();
+ state.matchesonscroll = null;
+ }
+ }
}
function highlightMatches(cm) {
cm.operation(function() {
var state = cm.state.matchHighlighter;
- if (state.overlay) {
- cm.removeOverlay(state.overlay);
- state.overlay = null;
- }
- if (!cm.somethingSelected() && state.showToken) {
- var re = state.showToken === true ? /[\w$]/ : state.showToken;
+ removeOverlay(cm);
+ if (!cm.somethingSelected() && state.options.showToken) {
+ var re = state.options.showToken === true ? /[\w$]/ : state.options.showToken;
var cur = cm.getCursor(), line = cm.getLine(cur.line), start = cur.ch, end = start;
while (start && re.test(line.charAt(start - 1))) --start;
while (end < line.length && re.test(line.charAt(end))) ++end;
if (start < end)
- cm.addOverlay(state.overlay = makeOverlay(line.slice(start, end), re, state.style));
+ addOverlay(cm, line.slice(start, end), re, state.options.style);
return;
}
var from = cm.getCursor("from"), to = cm.getCursor("to");
if (from.line != to.line) return;
- if (state.wordsOnly && !isWord(cm, from, to)) return;
- var selection = cm.getRange(from, to).replace(/^\s+|\s+$/g, "");
- if (selection.length >= state.minChars)
- cm.addOverlay(state.overlay = makeOverlay(selection, false, state.style));
+ if (state.options.wordsOnly && !isWord(cm, from, to)) return;
+ var selection = cm.getRange(from, to)
+ if (state.options.trim) selection = selection.replace(/^\s+|\s+$/g, "")
+ if (selection.length >= state.options.minChars)
+ addOverlay(cm, selection, false, state.options.style);
});
}
diff --git a/static/editor.md/lib/codemirror/addon/search/matchesonscrollbar.js b/static/editor.md/lib/codemirror/addon/search/matchesonscrollbar.js
index dbd67a4a5..8a4a82758 100644
--- a/static/editor.md/lib/codemirror/addon/search/matchesonscrollbar.js
+++ b/static/editor.md/lib/codemirror/addon/search/matchesonscrollbar.js
@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
@@ -19,6 +19,7 @@
function SearchAnnotation(cm, query, caseFold, options) {
this.cm = cm;
+ this.options = options;
var annotateOptions = {listenForChanges: false};
for (var prop in options) annotateOptions[prop] = options[prop];
if (!annotateOptions.className) annotateOptions.className = "CodeMirror-search-match";
@@ -45,12 +46,13 @@
if (match.from.line >= this.gap.to) break;
if (match.to.line >= this.gap.from) this.matches.splice(i--, 1);
}
- var cursor = this.cm.getSearchCursor(this.query, CodeMirror.Pos(this.gap.from, 0), this.caseFold);
+ var cursor = this.cm.getSearchCursor(this.query, CodeMirror.Pos(this.gap.from, 0), {caseFold: this.caseFold, multiline: this.options.multiline});
+ var maxMatches = this.options && this.options.maxMatches || MAX_MATCHES;
while (cursor.findNext()) {
var match = {from: cursor.from(), to: cursor.to()};
if (match.from.line >= this.gap.to) break;
this.matches.splice(i++, 0, match);
- if (this.matches.length > MAX_MATCHES) break;
+ if (this.matches.length > maxMatches) break;
}
this.gap = null;
};
diff --git a/static/editor.md/lib/codemirror/addon/search/search.js b/static/editor.md/lib/codemirror/addon/search/search.js
index 0251067a7..24a0855e9 100644
--- a/static/editor.md/lib/codemirror/addon/search/search.js
+++ b/static/editor.md/lib/codemirror/addon/search/search.js
@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
// Define search commands. Depends on dialog.js or another
// implementation of the openDialog method.
@@ -18,6 +18,10 @@
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
+
+ // default search panel location
+ CodeMirror.defineOption("search", {bottom: false});
+
function searchOverlay(query, caseInsensitive) {
if (typeof query == "string")
query = new RegExp(query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"), caseInsensitive ? "gi" : "g");
@@ -28,7 +32,7 @@
query.lastIndex = stream.pos;
var match = query.exec(stream.string);
if (match && match.index == stream.pos) {
- stream.pos += match[0].length;
+ stream.pos += match[0].length || 1;
return "searching";
} else if (match) {
stream.pos = match.index;
@@ -39,59 +43,131 @@
}
function SearchState() {
- this.posFrom = this.posTo = this.query = null;
+ this.posFrom = this.posTo = this.lastQuery = this.query = null;
this.overlay = null;
}
+
function getSearchState(cm) {
return cm.state.search || (cm.state.search = new SearchState());
}
+
function queryCaseInsensitive(query) {
return typeof query == "string" && query == query.toLowerCase();
}
+
function getSearchCursor(cm, query, pos) {
// Heuristic: if the query string is all lowercase, do a case insensitive search.
- return cm.getSearchCursor(query, pos, queryCaseInsensitive(query));
+ return cm.getSearchCursor(query, pos, {caseFold: queryCaseInsensitive(query), multiline: true});
}
+
+ function persistentDialog(cm, text, deflt, onEnter, onKeyDown) {
+ cm.openDialog(text, onEnter, {
+ value: deflt,
+ selectValueOnOpen: true,
+ closeOnEnter: false,
+ onClose: function() { clearSearch(cm); },
+ onKeyDown: onKeyDown,
+ bottom: cm.options.search.bottom
+ });
+ }
+
function dialog(cm, text, shortText, deflt, f) {
- if (cm.openDialog) cm.openDialog(text, f, {value: deflt});
+ if (cm.openDialog) cm.openDialog(text, f, {value: deflt, selectValueOnOpen: true, bottom: cm.options.search.bottom});
else f(prompt(shortText, deflt));
}
+
function confirmDialog(cm, text, shortText, fs) {
if (cm.openConfirm) cm.openConfirm(text, fs);
else if (confirm(shortText)) fs[0]();
}
+
+ function parseString(string) {
+ return string.replace(/\\([nrt\\])/g, function(match, ch) {
+ if (ch == "n") return "\n"
+ if (ch == "r") return "\r"
+ if (ch == "t") return "\t"
+ if (ch == "\\") return "\\"
+ return match
+ })
+ }
+
function parseQuery(query) {
var isRE = query.match(/^\/(.*)\/([a-z]*)$/);
if (isRE) {
try { query = new RegExp(isRE[1], isRE[2].indexOf("i") == -1 ? "" : "i"); }
catch(e) {} // Not a regular expression after all, do a string search
+ } else {
+ query = parseString(query)
}
if (typeof query == "string" ? query == "" : query.test(""))
query = /x^/;
return query;
}
- var queryDialog =
- 'Search: (Use /re/ syntax for regexp search) ';
- function doSearch(cm, rev) {
+
+ function startSearch(cm, state, query) {
+ state.queryText = query;
+ state.query = parseQuery(query);
+ cm.removeOverlay(state.overlay, queryCaseInsensitive(state.query));
+ state.overlay = searchOverlay(state.query, queryCaseInsensitive(state.query));
+ cm.addOverlay(state.overlay);
+ if (cm.showMatchesOnScrollbar) {
+ if (state.annotate) { state.annotate.clear(); state.annotate = null; }
+ state.annotate = cm.showMatchesOnScrollbar(state.query, queryCaseInsensitive(state.query));
+ }
+ }
+
+ function doSearch(cm, rev, persistent, immediate) {
var state = getSearchState(cm);
if (state.query) return findNext(cm, rev);
- dialog(cm, queryDialog, "Search for:", cm.getSelection(), function(query) {
- cm.operation(function() {
- if (!query || state.query) return;
- state.query = parseQuery(query);
- cm.removeOverlay(state.overlay, queryCaseInsensitive(state.query));
- state.overlay = searchOverlay(state.query, queryCaseInsensitive(state.query));
- cm.addOverlay(state.overlay);
- if (cm.showMatchesOnScrollbar) {
- if (state.annotate) { state.annotate.clear(); state.annotate = null; }
- state.annotate = cm.showMatchesOnScrollbar(state.query, queryCaseInsensitive(state.query));
+ var q = cm.getSelection() || state.lastQuery;
+ if (q instanceof RegExp && q.source == "x^") q = null
+ if (persistent && cm.openDialog) {
+ var hiding = null
+ var searchNext = function(query, event) {
+ CodeMirror.e_stop(event);
+ if (!query) return;
+ if (query != state.queryText) {
+ startSearch(cm, state, query);
+ state.posFrom = state.posTo = cm.getCursor();
}
- state.posFrom = state.posTo = cm.getCursor();
+ if (hiding) hiding.style.opacity = 1
+ findNext(cm, event.shiftKey, function(_, to) {
+ var dialog
+ if (to.line < 3 && document.querySelector &&
+ (dialog = cm.display.wrapper.querySelector(".CodeMirror-dialog")) &&
+ dialog.getBoundingClientRect().bottom - 4 > cm.cursorCoords(to, "window").top)
+ (hiding = dialog).style.opacity = .4
+ })
+ };
+ persistentDialog(cm, getQueryDialog(cm), q, searchNext, function(event, query) {
+ var keyName = CodeMirror.keyName(event)
+ var extra = cm.getOption('extraKeys'), cmd = (extra && extra[keyName]) || CodeMirror.keyMap[cm.getOption("keyMap")][keyName]
+ if (cmd == "findNext" || cmd == "findPrev" ||
+ cmd == "findPersistentNext" || cmd == "findPersistentPrev") {
+ CodeMirror.e_stop(event);
+ startSearch(cm, getSearchState(cm), query);
+ cm.execCommand(cmd);
+ } else if (cmd == "find" || cmd == "findPersistent") {
+ CodeMirror.e_stop(event);
+ searchNext(query, event);
+ }
+ });
+ if (immediate && q) {
+ startSearch(cm, state, q);
findNext(cm, rev);
+ }
+ } else {
+ dialog(cm, getQueryDialog(cm), "Search for:", q, function(query) {
+ if (query && !state.query) cm.operation(function() {
+ startSearch(cm, state, query);
+ state.posFrom = state.posTo = cm.getCursor();
+ findNext(cm, rev);
+ });
});
- });
+ }
}
- function findNext(cm, rev) {cm.operation(function() {
+
+ function findNext(cm, rev, callback) {cm.operation(function() {
var state = getSearchState(cm);
var cursor = getSearchCursor(cm, state.query, rev ? state.posFrom : state.posTo);
if (!cursor.find(rev)) {
@@ -99,39 +175,87 @@
if (!cursor.find(rev)) return;
}
cm.setSelection(cursor.from(), cursor.to());
- cm.scrollIntoView({from: cursor.from(), to: cursor.to()});
+ cm.scrollIntoView({from: cursor.from(), to: cursor.to()}, 20);
state.posFrom = cursor.from(); state.posTo = cursor.to();
+ if (callback) callback(cursor.from(), cursor.to())
});}
+
function clearSearch(cm) {cm.operation(function() {
var state = getSearchState(cm);
+ state.lastQuery = state.query;
if (!state.query) return;
- state.query = null;
+ state.query = state.queryText = null;
cm.removeOverlay(state.overlay);
if (state.annotate) { state.annotate.clear(); state.annotate = null; }
});}
- var replaceQueryDialog =
- 'Replace: (Use /re/ syntax for regexp search) ';
- var replacementQueryDialog = 'With: ';
- var doReplaceConfirm = "Replace? Yes No Stop ";
+ function el(tag, attrs) {
+ var element = tag ? document.createElement(tag) : document.createDocumentFragment();
+ for (var key in attrs) {
+ element[key] = attrs[key];
+ }
+ for (var i = 2; i < arguments.length; i++) {
+ var child = arguments[i]
+ element.appendChild(typeof child == "string" ? document.createTextNode(child) : child);
+ }
+ return element;
+ }
+
+ function getQueryDialog(cm) {
+ return el("", null,
+ el("span", {className: "CodeMirror-search-label"}, cm.phrase("Search:")), " ",
+ el("input", {type: "text", "style": "width: 10em", className: "CodeMirror-search-field"}), " ",
+ el("span", {style: "color: #888", className: "CodeMirror-search-hint"},
+ cm.phrase("(Use /re/ syntax for regexp search)")));
+ }
+ function getReplaceQueryDialog(cm) {
+ return el("", null, " ",
+ el("input", {type: "text", "style": "width: 10em", className: "CodeMirror-search-field"}), " ",
+ el("span", {style: "color: #888", className: "CodeMirror-search-hint"},
+ cm.phrase("(Use /re/ syntax for regexp search)")));
+ }
+ function getReplacementQueryDialog(cm) {
+ return el("", null,
+ el("span", {className: "CodeMirror-search-label"}, cm.phrase("With:")), " ",
+ el("input", {type: "text", "style": "width: 10em", className: "CodeMirror-search-field"}));
+ }
+ function getDoReplaceConfirm(cm) {
+ return el("", null,
+ el("span", {className: "CodeMirror-search-label"}, cm.phrase("Replace?")), " ",
+ el("button", {}, cm.phrase("Yes")), " ",
+ el("button", {}, cm.phrase("No")), " ",
+ el("button", {}, cm.phrase("All")), " ",
+ el("button", {}, cm.phrase("Stop")));
+ }
+
+ function replaceAll(cm, query, text) {
+ cm.operation(function() {
+ for (var cursor = getSearchCursor(cm, query); cursor.findNext();) {
+ if (typeof query != "string") {
+ var match = cm.getRange(cursor.from(), cursor.to()).match(query);
+ cursor.replace(text.replace(/\$(\d)/g, function(_, i) {return match[i];}));
+ } else cursor.replace(text);
+ }
+ });
+ }
+
function replace(cm, all) {
if (cm.getOption("readOnly")) return;
- dialog(cm, replaceQueryDialog, "Replace:", cm.getSelection(), function(query) {
+ var query = cm.getSelection() || getSearchState(cm).lastQuery;
+ var dialogText = all ? cm.phrase("Replace all:") : cm.phrase("Replace:")
+ var fragment = el("", null,
+ el("span", {className: "CodeMirror-search-label"}, dialogText),
+ getReplaceQueryDialog(cm))
+ dialog(cm, fragment, dialogText, query, function(query) {
if (!query) return;
query = parseQuery(query);
- dialog(cm, replacementQueryDialog, "Replace with:", "", function(text) {
+ dialog(cm, getReplacementQueryDialog(cm), cm.phrase("Replace with:"), "", function(text) {
+ text = parseString(text)
if (all) {
- cm.operation(function() {
- for (var cursor = getSearchCursor(cm, query); cursor.findNext();) {
- if (typeof query != "string") {
- var match = cm.getRange(cursor.from(), cursor.to()).match(query);
- cursor.replace(text.replace(/\$(\d)/g, function(_, i) {return match[i];}));
- } else cursor.replace(text);
- }
- });
+ replaceAll(cm, query, text)
} else {
clearSearch(cm);
- var cursor = getSearchCursor(cm, query, cm.getCursor());
+ var cursor = getSearchCursor(cm, query, cm.getCursor("from"));
var advance = function() {
var start = cursor.from(), match;
if (!(match = cursor.findNext())) {
@@ -141,8 +265,9 @@
}
cm.setSelection(cursor.from(), cursor.to());
cm.scrollIntoView({from: cursor.from(), to: cursor.to()});
- confirmDialog(cm, doReplaceConfirm, "Replace?",
- [function() {doReplace(match);}, advance]);
+ confirmDialog(cm, getDoReplaceConfirm(cm), cm.phrase("Replace?"),
+ [function() {doReplace(match);}, advance,
+ function() {replaceAll(cm, query, text)}]);
};
var doReplace = function(match) {
cursor.replace(typeof query == "string" ? text :
@@ -156,6 +281,9 @@
}
CodeMirror.commands.find = function(cm) {clearSearch(cm); doSearch(cm);};
+ CodeMirror.commands.findPersistent = function(cm) {clearSearch(cm); doSearch(cm, false, true);};
+ CodeMirror.commands.findPersistentNext = function(cm) {doSearch(cm, false, true, true);};
+ CodeMirror.commands.findPersistentPrev = function(cm) {doSearch(cm, true, true, true);};
CodeMirror.commands.findNext = doSearch;
CodeMirror.commands.findPrev = function(cm) {doSearch(cm, true);};
CodeMirror.commands.clearSearch = clearSearch;
diff --git a/static/editor.md/lib/codemirror/addon/search/searchcursor.js b/static/editor.md/lib/codemirror/addon/search/searchcursor.js
index 55c108b5a..230017b78 100644
--- a/static/editor.md/lib/codemirror/addon/search/searchcursor.js
+++ b/static/editor.md/lib/codemirror/addon/search/searchcursor.js
@@ -1,189 +1,305 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
- mod(require("../../lib/codemirror"));
+ mod(require("../../lib/codemirror"))
else if (typeof define == "function" && define.amd) // AMD
- define(["../../lib/codemirror"], mod);
+ define(["../../lib/codemirror"], mod)
else // Plain browser env
- mod(CodeMirror);
+ mod(CodeMirror)
})(function(CodeMirror) {
- "use strict";
- var Pos = CodeMirror.Pos;
-
- function SearchCursor(doc, query, pos, caseFold) {
- this.atOccurrence = false; this.doc = doc;
- if (caseFold == null && typeof query == "string") caseFold = false;
-
- pos = pos ? doc.clipPos(pos) : Pos(0, 0);
- this.pos = {from: pos, to: pos};
-
- // The matches method is filled in based on the type of query.
- // It takes a position and a direction, and returns an object
- // describing the next occurrence of the query, or null if no
- // more matches were found.
- if (typeof query != "string") { // Regexp match
- if (!query.global) query = new RegExp(query.source, query.ignoreCase ? "ig" : "g");
- this.matches = function(reverse, pos) {
- if (reverse) {
- query.lastIndex = 0;
- var line = doc.getLine(pos.line).slice(0, pos.ch), cutOff = 0, match, start;
- for (;;) {
- query.lastIndex = cutOff;
- var newMatch = query.exec(line);
- if (!newMatch) break;
- match = newMatch;
- start = match.index;
- cutOff = match.index + (match[0].length || 1);
- if (cutOff == line.length) break;
- }
- var matchLen = (match && match[0].length) || 0;
- if (!matchLen) {
- if (start == 0 && line.length == 0) {match = undefined;}
- else if (start != doc.getLine(pos.line).length) {
- matchLen++;
- }
- }
- } else {
- query.lastIndex = pos.ch;
- var line = doc.getLine(pos.line), match = query.exec(line);
- var matchLen = (match && match[0].length) || 0;
- var start = match && match.index;
- if (start + matchLen != line.length && !matchLen) matchLen = 1;
- }
- if (match && matchLen)
- return {from: Pos(pos.line, start),
- to: Pos(pos.line, start + matchLen),
- match: match};
- };
- } else { // String query
- var origQuery = query;
- if (caseFold) query = query.toLowerCase();
- var fold = caseFold ? function(str){return str.toLowerCase();} : function(str){return str;};
- var target = query.split("\n");
- // Different methods for single-line and multi-line queries
- if (target.length == 1) {
- if (!query.length) {
- // Empty string would match anything and never progress, so
- // we define it to match nothing instead.
- this.matches = function() {};
- } else {
- this.matches = function(reverse, pos) {
- if (reverse) {
- var orig = doc.getLine(pos.line).slice(0, pos.ch), line = fold(orig);
- var match = line.lastIndexOf(query);
- if (match > -1) {
- match = adjustPos(orig, line, match);
- return {from: Pos(pos.line, match), to: Pos(pos.line, match + origQuery.length)};
- }
- } else {
- var orig = doc.getLine(pos.line).slice(pos.ch), line = fold(orig);
- var match = line.indexOf(query);
- if (match > -1) {
- match = adjustPos(orig, line, match) + pos.ch;
- return {from: Pos(pos.line, match), to: Pos(pos.line, match + origQuery.length)};
- }
- }
- };
- }
+ "use strict"
+ var Pos = CodeMirror.Pos
+
+ function regexpFlags(regexp) {
+ var flags = regexp.flags
+ return flags != null ? flags : (regexp.ignoreCase ? "i" : "")
+ + (regexp.global ? "g" : "")
+ + (regexp.multiline ? "m" : "")
+ }
+
+ function ensureFlags(regexp, flags) {
+ var current = regexpFlags(regexp), target = current
+ for (var i = 0; i < flags.length; i++) if (target.indexOf(flags.charAt(i)) == -1)
+ target += flags.charAt(i)
+ return current == target ? regexp : new RegExp(regexp.source, target)
+ }
+
+ function maybeMultiline(regexp) {
+ return /\\s|\\n|\n|\\W|\\D|\[\^/.test(regexp.source)
+ }
+
+ function searchRegexpForward(doc, regexp, start) {
+ regexp = ensureFlags(regexp, "g")
+ for (var line = start.line, ch = start.ch, last = doc.lastLine(); line <= last; line++, ch = 0) {
+ regexp.lastIndex = ch
+ var string = doc.getLine(line), match = regexp.exec(string)
+ if (match)
+ return {from: Pos(line, match.index),
+ to: Pos(line, match.index + match[0].length),
+ match: match}
+ }
+ }
+
+ function searchRegexpForwardMultiline(doc, regexp, start) {
+ if (!maybeMultiline(regexp)) return searchRegexpForward(doc, regexp, start)
+
+ regexp = ensureFlags(regexp, "gm")
+ var string, chunk = 1
+ for (var line = start.line, last = doc.lastLine(); line <= last;) {
+ // This grows the search buffer in exponentially-sized chunks
+ // between matches, so that nearby matches are fast and don't
+ // require concatenating the whole document (in case we're
+ // searching for something that has tons of matches), but at the
+ // same time, the amount of retries is limited.
+ for (var i = 0; i < chunk; i++) {
+ if (line > last) break
+ var curLine = doc.getLine(line++)
+ string = string == null ? curLine : string + "\n" + curLine
+ }
+ chunk = chunk * 2
+ regexp.lastIndex = start.ch
+ var match = regexp.exec(string)
+ if (match) {
+ var before = string.slice(0, match.index).split("\n"), inside = match[0].split("\n")
+ var startLine = start.line + before.length - 1, startCh = before[before.length - 1].length
+ return {from: Pos(startLine, startCh),
+ to: Pos(startLine + inside.length - 1,
+ inside.length == 1 ? startCh + inside[0].length : inside[inside.length - 1].length),
+ match: match}
+ }
+ }
+ }
+
+ function lastMatchIn(string, regexp, endMargin) {
+ var match, from = 0
+ while (from <= string.length) {
+ regexp.lastIndex = from
+ var newMatch = regexp.exec(string)
+ if (!newMatch) break
+ var end = newMatch.index + newMatch[0].length
+ if (end > string.length - endMargin) break
+ if (!match || end > match.index + match[0].length)
+ match = newMatch
+ from = newMatch.index + 1
+ }
+ return match
+ }
+
+ function searchRegexpBackward(doc, regexp, start) {
+ regexp = ensureFlags(regexp, "g")
+ for (var line = start.line, ch = start.ch, first = doc.firstLine(); line >= first; line--, ch = -1) {
+ var string = doc.getLine(line)
+ var match = lastMatchIn(string, regexp, ch < 0 ? 0 : string.length - ch)
+ if (match)
+ return {from: Pos(line, match.index),
+ to: Pos(line, match.index + match[0].length),
+ match: match}
+ }
+ }
+
+ function searchRegexpBackwardMultiline(doc, regexp, start) {
+ if (!maybeMultiline(regexp)) return searchRegexpBackward(doc, regexp, start)
+ regexp = ensureFlags(regexp, "gm")
+ var string, chunkSize = 1, endMargin = doc.getLine(start.line).length - start.ch
+ for (var line = start.line, first = doc.firstLine(); line >= first;) {
+ for (var i = 0; i < chunkSize && line >= first; i++) {
+ var curLine = doc.getLine(line--)
+ string = string == null ? curLine : curLine + "\n" + string
+ }
+ chunkSize *= 2
+
+ var match = lastMatchIn(string, regexp, endMargin)
+ if (match) {
+ var before = string.slice(0, match.index).split("\n"), inside = match[0].split("\n")
+ var startLine = line + before.length, startCh = before[before.length - 1].length
+ return {from: Pos(startLine, startCh),
+ to: Pos(startLine + inside.length - 1,
+ inside.length == 1 ? startCh + inside[0].length : inside[inside.length - 1].length),
+ match: match}
+ }
+ }
+ }
+
+ var doFold, noFold
+ if (String.prototype.normalize) {
+ doFold = function(str) { return str.normalize("NFD").toLowerCase() }
+ noFold = function(str) { return str.normalize("NFD") }
+ } else {
+ doFold = function(str) { return str.toLowerCase() }
+ noFold = function(str) { return str }
+ }
+
+ // Maps a position in a case-folded line back to a position in the original line
+ // (compensating for codepoints increasing in number during folding)
+ function adjustPos(orig, folded, pos, foldFunc) {
+ if (orig.length == folded.length) return pos
+ for (var min = 0, max = pos + Math.max(0, orig.length - folded.length);;) {
+ if (min == max) return min
+ var mid = (min + max) >> 1
+ var len = foldFunc(orig.slice(0, mid)).length
+ if (len == pos) return mid
+ else if (len > pos) max = mid
+ else min = mid + 1
+ }
+ }
+
+ function searchStringForward(doc, query, start, caseFold) {
+ // Empty string would match anything and never progress, so we
+ // define it to match nothing instead.
+ if (!query.length) return null
+ var fold = caseFold ? doFold : noFold
+ var lines = fold(query).split(/\r|\n\r?/)
+
+ search: for (var line = start.line, ch = start.ch, last = doc.lastLine() + 1 - lines.length; line <= last; line++, ch = 0) {
+ var orig = doc.getLine(line).slice(ch), string = fold(orig)
+ if (lines.length == 1) {
+ var found = string.indexOf(lines[0])
+ if (found == -1) continue search
+ var start = adjustPos(orig, string, found, fold) + ch
+ return {from: Pos(line, adjustPos(orig, string, found, fold) + ch),
+ to: Pos(line, adjustPos(orig, string, found + lines[0].length, fold) + ch)}
} else {
- var origTarget = origQuery.split("\n");
- this.matches = function(reverse, pos) {
- var last = target.length - 1;
- if (reverse) {
- if (pos.line - (target.length - 1) < doc.firstLine()) return;
- if (fold(doc.getLine(pos.line).slice(0, origTarget[last].length)) != target[target.length - 1]) return;
- var to = Pos(pos.line, origTarget[last].length);
- for (var ln = pos.line - 1, i = last - 1; i >= 1; --i, --ln)
- if (target[i] != fold(doc.getLine(ln))) return;
- var line = doc.getLine(ln), cut = line.length - origTarget[0].length;
- if (fold(line.slice(cut)) != target[0]) return;
- return {from: Pos(ln, cut), to: to};
- } else {
- if (pos.line + (target.length - 1) > doc.lastLine()) return;
- var line = doc.getLine(pos.line), cut = line.length - origTarget[0].length;
- if (fold(line.slice(cut)) != target[0]) return;
- var from = Pos(pos.line, cut);
- for (var ln = pos.line + 1, i = 1; i < last; ++i, ++ln)
- if (target[i] != fold(doc.getLine(ln))) return;
- if (fold(doc.getLine(ln).slice(0, origTarget[last].length)) != target[last]) return;
- return {from: from, to: Pos(ln, origTarget[last].length)};
- }
- };
+ var cutFrom = string.length - lines[0].length
+ if (string.slice(cutFrom) != lines[0]) continue search
+ for (var i = 1; i < lines.length - 1; i++)
+ if (fold(doc.getLine(line + i)) != lines[i]) continue search
+ var end = doc.getLine(line + lines.length - 1), endString = fold(end), lastLine = lines[lines.length - 1]
+ if (endString.slice(0, lastLine.length) != lastLine) continue search
+ return {from: Pos(line, adjustPos(orig, string, cutFrom, fold) + ch),
+ to: Pos(line + lines.length - 1, adjustPos(end, endString, lastLine.length, fold))}
}
}
}
- SearchCursor.prototype = {
- findNext: function() {return this.find(false);},
- findPrevious: function() {return this.find(true);},
+ function searchStringBackward(doc, query, start, caseFold) {
+ if (!query.length) return null
+ var fold = caseFold ? doFold : noFold
+ var lines = fold(query).split(/\r|\n\r?/)
- find: function(reverse) {
- var self = this, pos = this.doc.clipPos(reverse ? this.pos.from : this.pos.to);
- function savePosAndFail(line) {
- var pos = Pos(line, 0);
- self.pos = {from: pos, to: pos};
- self.atOccurrence = false;
- return false;
+ search: for (var line = start.line, ch = start.ch, first = doc.firstLine() - 1 + lines.length; line >= first; line--, ch = -1) {
+ var orig = doc.getLine(line)
+ if (ch > -1) orig = orig.slice(0, ch)
+ var string = fold(orig)
+ if (lines.length == 1) {
+ var found = string.lastIndexOf(lines[0])
+ if (found == -1) continue search
+ return {from: Pos(line, adjustPos(orig, string, found, fold)),
+ to: Pos(line, adjustPos(orig, string, found + lines[0].length, fold))}
+ } else {
+ var lastLine = lines[lines.length - 1]
+ if (string.slice(0, lastLine.length) != lastLine) continue search
+ for (var i = 1, start = line - lines.length + 1; i < lines.length - 1; i++)
+ if (fold(doc.getLine(start + i)) != lines[i]) continue search
+ var top = doc.getLine(line + 1 - lines.length), topString = fold(top)
+ if (topString.slice(topString.length - lines[0].length) != lines[0]) continue search
+ return {from: Pos(line + 1 - lines.length, adjustPos(top, topString, top.length - lines[0].length, fold)),
+ to: Pos(line, adjustPos(orig, string, lastLine.length, fold))}
}
+ }
+ }
- for (;;) {
- if (this.pos = this.matches(reverse, pos)) {
- this.atOccurrence = true;
- return this.pos.match || true;
+ function SearchCursor(doc, query, pos, options) {
+ this.atOccurrence = false
+ this.afterEmptyMatch = false
+ this.doc = doc
+ pos = pos ? doc.clipPos(pos) : Pos(0, 0)
+ this.pos = {from: pos, to: pos}
+
+ var caseFold
+ if (typeof options == "object") {
+ caseFold = options.caseFold
+ } else { // Backwards compat for when caseFold was the 4th argument
+ caseFold = options
+ options = null
+ }
+
+ if (typeof query == "string") {
+ if (caseFold == null) caseFold = false
+ this.matches = function(reverse, pos) {
+ return (reverse ? searchStringBackward : searchStringForward)(doc, query, pos, caseFold)
+ }
+ } else {
+ query = ensureFlags(query, "gm")
+ if (!options || options.multiline !== false)
+ this.matches = function(reverse, pos) {
+ return (reverse ? searchRegexpBackwardMultiline : searchRegexpForwardMultiline)(doc, query, pos)
}
+ else
+ this.matches = function(reverse, pos) {
+ return (reverse ? searchRegexpBackward : searchRegexpForward)(doc, query, pos)
+ }
+ }
+ }
+
+ SearchCursor.prototype = {
+ findNext: function() {return this.find(false)},
+ findPrevious: function() {return this.find(true)},
+
+ find: function(reverse) {
+ var head = this.doc.clipPos(reverse ? this.pos.from : this.pos.to);
+ if (this.afterEmptyMatch && this.atOccurrence) {
+ // do not return the same 0 width match twice
+ head = Pos(head.line, head.ch)
if (reverse) {
- if (!pos.line) return savePosAndFail(0);
- pos = Pos(pos.line-1, this.doc.getLine(pos.line-1).length);
+ head.ch--;
+ if (head.ch < 0) {
+ head.line--;
+ head.ch = (this.doc.getLine(head.line) || "").length;
+ }
+ } else {
+ head.ch++;
+ if (head.ch > (this.doc.getLine(head.line) || "").length) {
+ head.ch = 0;
+ head.line++;
+ }
}
- else {
- var maxLine = this.doc.lineCount();
- if (pos.line == maxLine - 1) return savePosAndFail(maxLine);
- pos = Pos(pos.line + 1, 0);
+ if (CodeMirror.cmpPos(head, this.doc.clipPos(head)) != 0) {
+ return this.atOccurrence = false
}
}
+ var result = this.matches(reverse, head)
+ this.afterEmptyMatch = result && CodeMirror.cmpPos(result.from, result.to) == 0
+
+ if (result) {
+ this.pos = result
+ this.atOccurrence = true
+ return this.pos.match || true
+ } else {
+ var end = Pos(reverse ? this.doc.firstLine() : this.doc.lastLine() + 1, 0)
+ this.pos = {from: end, to: end}
+ return this.atOccurrence = false
+ }
},
- from: function() {if (this.atOccurrence) return this.pos.from;},
- to: function() {if (this.atOccurrence) return this.pos.to;},
+ from: function() {if (this.atOccurrence) return this.pos.from},
+ to: function() {if (this.atOccurrence) return this.pos.to},
- replace: function(newText) {
- if (!this.atOccurrence) return;
- var lines = CodeMirror.splitLines(newText);
- this.doc.replaceRange(lines, this.pos.from, this.pos.to);
+ replace: function(newText, origin) {
+ if (!this.atOccurrence) return
+ var lines = CodeMirror.splitLines(newText)
+ this.doc.replaceRange(lines, this.pos.from, this.pos.to, origin)
this.pos.to = Pos(this.pos.from.line + lines.length - 1,
- lines[lines.length - 1].length + (lines.length == 1 ? this.pos.from.ch : 0));
- }
- };
-
- // Maps a position in a case-folded line back to a position in the original line
- // (compensating for codepoints increasing in number during folding)
- function adjustPos(orig, folded, pos) {
- if (orig.length == folded.length) return pos;
- for (var pos1 = Math.min(pos, orig.length);;) {
- var len1 = orig.slice(0, pos1).toLowerCase().length;
- if (len1 < pos) ++pos1;
- else if (len1 > pos) --pos1;
- else return pos1;
+ lines[lines.length - 1].length + (lines.length == 1 ? this.pos.from.ch : 0))
}
}
CodeMirror.defineExtension("getSearchCursor", function(query, pos, caseFold) {
- return new SearchCursor(this.doc, query, pos, caseFold);
- });
+ return new SearchCursor(this.doc, query, pos, caseFold)
+ })
CodeMirror.defineDocExtension("getSearchCursor", function(query, pos, caseFold) {
- return new SearchCursor(this, query, pos, caseFold);
- });
+ return new SearchCursor(this, query, pos, caseFold)
+ })
CodeMirror.defineExtension("selectMatches", function(query, caseFold) {
- var ranges = [], next;
- var cur = this.getSearchCursor(query, this.getCursor("from"), caseFold);
- while (next = cur.findNext()) {
- if (CodeMirror.cmpPos(cur.to(), this.getCursor("to")) > 0) break;
- ranges.push({anchor: cur.from(), head: cur.to()});
+ var ranges = []
+ var cur = this.getSearchCursor(query, this.getCursor("from"), caseFold)
+ while (cur.findNext()) {
+ if (CodeMirror.cmpPos(cur.to(), this.getCursor("to")) > 0) break
+ ranges.push({anchor: cur.from(), head: cur.to()})
}
if (ranges.length)
- this.setSelections(ranges, 0);
- });
+ this.setSelections(ranges, 0)
+ })
});
diff --git a/static/editor.md/lib/codemirror/addon/selection/active-line.js b/static/editor.md/lib/codemirror/addon/selection/active-line.js
index 22da2e0aa..c7b14ce07 100644
--- a/static/editor.md/lib/codemirror/addon/selection/active-line.js
+++ b/static/editor.md/lib/codemirror/addon/selection/active-line.js
@@ -1,11 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-// Because sometimes you need to style the cursor's line.
-//
-// Adds an option 'styleActiveLine' which, when enabled, gives the
-// active line's wrapping the CSS class "CodeMirror-activeline",
-// and gives its background
the class "CodeMirror-activeline-background".
+// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
@@ -18,24 +12,28 @@
"use strict";
var WRAP_CLASS = "CodeMirror-activeline";
var BACK_CLASS = "CodeMirror-activeline-background";
+ var GUTT_CLASS = "CodeMirror-activeline-gutter";
CodeMirror.defineOption("styleActiveLine", false, function(cm, val, old) {
- var prev = old && old != CodeMirror.Init;
- if (val && !prev) {
- cm.state.activeLines = [];
- updateActiveLines(cm, cm.listSelections());
- cm.on("beforeSelectionChange", selectionChange);
- } else if (!val && prev) {
+ var prev = old == CodeMirror.Init ? false : old;
+ if (val == prev) return
+ if (prev) {
cm.off("beforeSelectionChange", selectionChange);
clearActiveLines(cm);
delete cm.state.activeLines;
}
+ if (val) {
+ cm.state.activeLines = [];
+ updateActiveLines(cm, cm.listSelections());
+ cm.on("beforeSelectionChange", selectionChange);
+ }
});
function clearActiveLines(cm) {
for (var i = 0; i < cm.state.activeLines.length; i++) {
cm.removeLineClass(cm.state.activeLines[i], "wrap", WRAP_CLASS);
cm.removeLineClass(cm.state.activeLines[i], "background", BACK_CLASS);
+ cm.removeLineClass(cm.state.activeLines[i], "gutter", GUTT_CLASS);
}
}
@@ -50,7 +48,9 @@
var active = [];
for (var i = 0; i < ranges.length; i++) {
var range = ranges[i];
- if (!range.empty()) continue;
+ var option = cm.getOption("styleActiveLine");
+ if (typeof option == "object" && option.nonEmpty ? range.anchor.line != range.head.line : !range.empty())
+ continue
var line = cm.getLineHandleVisualStart(range.head.line);
if (active[active.length - 1] != line) active.push(line);
}
@@ -60,6 +60,7 @@
for (var i = 0; i < active.length; i++) {
cm.addLineClass(active[i], "wrap", WRAP_CLASS);
cm.addLineClass(active[i], "background", BACK_CLASS);
+ cm.addLineClass(active[i], "gutter", GUTT_CLASS);
}
cm.state.activeLines = active;
});
diff --git a/static/editor.md/lib/codemirror/addon/selection/mark-selection.js b/static/editor.md/lib/codemirror/addon/selection/mark-selection.js
index 5c42d21eb..adfaa62d1 100644
--- a/static/editor.md/lib/codemirror/addon/selection/mark-selection.js
+++ b/static/editor.md/lib/codemirror/addon/selection/mark-selection.js
@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
// Because sometimes you need to mark the selected *text*.
//
@@ -34,11 +34,12 @@
});
function onCursorActivity(cm) {
- cm.operation(function() { update(cm); });
+ if (cm.state.markedSelection)
+ cm.operation(function() { update(cm); });
}
function onChange(cm) {
- if (cm.state.markedSelection.length)
+ if (cm.state.markedSelection && cm.state.markedSelection.length)
cm.operation(function() { clear(cm); });
}
@@ -85,7 +86,7 @@
if (!array.length) return coverRange(cm, from, to);
var coverStart = array[0].find(), coverEnd = array[array.length - 1].find();
- if (!coverStart || !coverEnd || to.line - from.line < CHUNK_SIZE ||
+ if (!coverStart || !coverEnd || to.line - from.line <= CHUNK_SIZE ||
cmp(from, coverEnd.to) >= 0 || cmp(to, coverStart.from) <= 0)
return reset(cm);
diff --git a/static/editor.md/lib/codemirror/addon/selection/selection-pointer.js b/static/editor.md/lib/codemirror/addon/selection/selection-pointer.js
index ef5e404ad..f0bd61a33 100644
--- a/static/editor.md/lib/codemirror/addon/selection/selection-pointer.js
+++ b/static/editor.md/lib/codemirror/addon/selection/selection-pointer.js
@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
diff --git a/static/editor.md/lib/codemirror/addon/tern/tern.css b/static/editor.md/lib/codemirror/addon/tern/tern.css
index 76fba33d4..c4b8a2f77 100644
--- a/static/editor.md/lib/codemirror/addon/tern/tern.css
+++ b/static/editor.md/lib/codemirror/addon/tern/tern.css
@@ -1,6 +1,7 @@
.CodeMirror-Tern-completion {
padding-left: 22px;
position: relative;
+ line-height: 1.5;
}
.CodeMirror-Tern-completion:before {
position: absolute;
diff --git a/static/editor.md/lib/codemirror/addon/tern/tern.js b/static/editor.md/lib/codemirror/addon/tern/tern.js
index b049549d3..a88371747 100644
--- a/static/editor.md/lib/codemirror/addon/tern/tern.js
+++ b/static/editor.md/lib/codemirror/addon/tern/tern.js
@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
// Glue code between CodeMirror and Tern.
//
@@ -59,6 +59,7 @@
this.options = options || {};
var plugins = this.options.plugins || (this.options.plugins = {});
if (!plugins.doc_comment) plugins.doc_comment = true;
+ this.docs = Object.create(null);
if (this.options.useWorker) {
this.server = new WorkerServer(this);
} else {
@@ -69,7 +70,6 @@
plugins: plugins
});
}
- this.docs = Object.create(null);
this.trackChange = function(doc, change) { trackChange(self, doc, change); };
this.cachedArgHints = null;
@@ -124,6 +124,8 @@
var self = this;
var doc = findDoc(this, cm.getDoc());
var request = buildRequest(this, doc, query, pos);
+ var extraOptions = request.query && this.options.queryOptions && this.options.queryOptions[request.query.type]
+ if (extraOptions) for (var prop in extraOptions) request.query[prop] = extraOptions[prop];
this.server.request(request, function (error, data) {
if (!error && self.options.responseFilter)
@@ -133,6 +135,7 @@
},
destroy: function () {
+ closeArgHints(this)
if (this.worker) {
this.worker.terminate();
this.worker = null;
@@ -176,7 +179,7 @@
var data = findDoc(ts, doc);
var argHints = ts.cachedArgHints;
- if (argHints && argHints.doc == doc && cmpPos(argHints.start, change.to) <= 0)
+ if (argHints && argHints.doc == doc && cmpPos(argHints.start, change.to) >= 0)
ts.cachedArgHints = null;
var changed = data.changed;
@@ -214,7 +217,7 @@
var completion = data.completions[i], className = typeToIcon(completion.type);
if (data.guess) className += " " + cls + "guess";
completions.push({text: completion.name + after,
- displayText: completion.name,
+ displayText: completion.displayName || completion.name,
className: className,
data: completion});
}
@@ -228,8 +231,7 @@
var content = ts.options.completionTip ? ts.options.completionTip(cur.data) : cur.data.doc;
if (content) {
tooltip = makeTooltip(node.parentNode.getBoundingClientRect().right + window.pageXOffset,
- node.getBoundingClientRect().top + window.pageYOffset, content);
- tooltip.className += " " + cls + "hint-doc";
+ node.getBoundingClientRect().top + window.pageYOffset, content, cm, cls + "hint-doc");
}
});
c(obj);
@@ -264,7 +266,7 @@
child.target = "_blank";
}
}
- tempTooltip(cm, tip);
+ tempTooltip(cm, tip, ts);
if (c) c();
}, pos);
}
@@ -303,7 +305,7 @@
ts.request(cm, {type: "type", preferFunction: true, end: start}, function(error, data) {
if (error || !data.type || !(/^fn\(/).test(data.type)) return;
ts.cachedArgHints = {
- start: pos,
+ start: start,
type: parseFnType(data.type),
name: data.exprName || data.name || "fn",
guess: data.guess,
@@ -331,7 +333,11 @@
tip.appendChild(document.createTextNode(tp.rettype ? ") ->\u00a0" : ")"));
if (tp.rettype) tip.appendChild(elt("span", cls + "type", tp.rettype));
var place = cm.cursorCoords(null, "page");
- ts.activeArgHints = makeTooltip(place.right + 1, place.bottom, tip);
+ var tooltip = ts.activeArgHints = makeTooltip(place.right + 1, place.bottom, tip, cm)
+ setTimeout(function() {
+ tooltip.clear = onEditorActivity(cm, function() {
+ if (ts.activeArgHints == tooltip) closeArgHints(ts) })
+ }, 20)
}
function parseFnType(text) {
@@ -442,8 +448,8 @@
function atInterestingExpression(cm) {
var pos = cm.getCursor("end"), tok = cm.getTokenAt(pos);
- if (tok.start < pos.ch && (tok.type == "comment" || tok.type == "string")) return false;
- return /\w/.test(cm.getLine(pos.line).slice(Math.max(pos.ch - 1, 0), pos.ch + 1));
+ if (tok.start < pos.ch && tok.type == "comment") return false;
+ return /[\w)\]]/.test(cm.getLine(pos.line).slice(Math.max(pos.ch - 1, 0), pos.ch + 1));
}
// Variable renaming
@@ -464,11 +470,12 @@
ts.request(cm, {type: "refs"}, function(error, data) {
if (error) return showError(ts, cm, error);
var ranges = [], cur = 0;
+ var curPos = cm.getCursor();
for (var i = 0; i < data.refs.length; i++) {
var ref = data.refs[i];
if (ref.file == name) {
ranges.push({anchor: ref.start, head: ref.end});
- if (cmpPos(cur, ref.start) >= 0 && cmpPos(cur, ref.end) <= 0)
+ if (cmpPos(curPos, ref.start) >= 0 && cmpPos(curPos, ref.end) <= 0)
cur = ranges.length - 1;
}
}
@@ -563,7 +570,7 @@
return {type: "part",
name: data.name,
offsetLines: from.line,
- text: doc.getRange(from, Pos(endLine, 0))};
+ text: doc.getRange(from, Pos(endLine, end.line == endLine ? null : 0))};
}
// Generic utilities
@@ -590,41 +597,83 @@
// Tooltips
- function tempTooltip(cm, content) {
+ function tempTooltip(cm, content, ts) {
if (cm.state.ternTooltip) remove(cm.state.ternTooltip);
var where = cm.cursorCoords();
- var tip = cm.state.ternTooltip = makeTooltip(where.right + 1, where.bottom, content);
+ var tip = cm.state.ternTooltip = makeTooltip(where.right + 1, where.bottom, content, cm);
function maybeClear() {
old = true;
if (!mouseOnTip) clear();
}
function clear() {
cm.state.ternTooltip = null;
- if (!tip.parentNode) return;
- cm.off("cursorActivity", clear);
- cm.off('blur', clear);
- cm.off('scroll', clear);
- fadeOut(tip);
+ if (tip.parentNode) fadeOut(tip)
+ clearActivity()
}
var mouseOnTip = false, old = false;
CodeMirror.on(tip, "mousemove", function() { mouseOnTip = true; });
CodeMirror.on(tip, "mouseout", function(e) {
- if (!CodeMirror.contains(tip, e.relatedTarget || e.toElement)) {
+ var related = e.relatedTarget || e.toElement
+ if (!related || !CodeMirror.contains(tip, related)) {
if (old) clear();
else mouseOnTip = false;
}
});
- setTimeout(maybeClear, 1700);
- cm.on("cursorActivity", clear);
- cm.on('blur', clear);
- cm.on('scroll', clear);
+ setTimeout(maybeClear, ts.options.hintDelay ? ts.options.hintDelay : 1700);
+ var clearActivity = onEditorActivity(cm, clear)
+ }
+
+ function onEditorActivity(cm, f) {
+ cm.on("cursorActivity", f)
+ cm.on("blur", f)
+ cm.on("scroll", f)
+ cm.on("setDoc", f)
+ return function() {
+ cm.off("cursorActivity", f)
+ cm.off("blur", f)
+ cm.off("scroll", f)
+ cm.off("setDoc", f)
+ }
}
- function makeTooltip(x, y, content) {
- var node = elt("div", cls + "tooltip", content);
+ function makeTooltip(x, y, content, cm, className) {
+ var node = elt("div", cls + "tooltip" + " " + (className || ""), content);
node.style.left = x + "px";
node.style.top = y + "px";
- document.body.appendChild(node);
+ var container = ((cm.options || {}).hintOptions || {}).container || document.body;
+ container.appendChild(node);
+
+ var pos = cm.cursorCoords();
+ var winW = window.innerWidth;
+ var winH = window.innerHeight;
+ var box = node.getBoundingClientRect();
+ var hints = document.querySelector(".CodeMirror-hints");
+ var overlapY = box.bottom - winH;
+ var overlapX = box.right - winW;
+
+ if (hints && overlapX > 0) {
+ node.style.left = 0;
+ var box = node.getBoundingClientRect();
+ node.style.left = (x = x - hints.offsetWidth - box.width) + "px";
+ overlapX = box.right - winW;
+ }
+ if (overlapY > 0) {
+ var height = box.bottom - box.top, curTop = pos.top - (pos.bottom - box.top);
+ if (curTop - height > 0) { // Fits above cursor
+ node.style.top = (pos.top - height) + "px";
+ } else if (height > winH) {
+ node.style.height = (winH - 5) + "px";
+ node.style.top = (pos.bottom - box.top) + "px";
+ }
+ }
+ if (overlapX > 0) {
+ if (box.right - box.left > winW) {
+ node.style.width = (winW - 5) + "px";
+ overlapX -= (box.right - box.left) - winW;
+ }
+ node.style.left = (x - overlapX) + "px";
+ }
+
return node;
}
@@ -642,11 +691,15 @@
if (ts.options.showError)
ts.options.showError(cm, msg);
else
- tempTooltip(cm, String(msg));
+ tempTooltip(cm, String(msg), ts);
}
function closeArgHints(ts) {
- if (ts.activeArgHints) { remove(ts.activeArgHints); ts.activeArgHints = null; }
+ if (ts.activeArgHints) {
+ if (ts.activeArgHints.clear) ts.activeArgHints.clear()
+ remove(ts.activeArgHints)
+ ts.activeArgHints = null
+ }
}
function docValue(ts, doc) {
diff --git a/static/editor.md/lib/codemirror/addon/tern/worker.js b/static/editor.md/lib/codemirror/addon/tern/worker.js
index 48277af8e..e134ad47d 100644
--- a/static/editor.md/lib/codemirror/addon/tern/worker.js
+++ b/static/editor.md/lib/codemirror/addon/tern/worker.js
@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
// declare global: tern, server
@@ -39,6 +39,6 @@ function startServer(defs, plugins, scripts) {
});
}
-var console = {
+this.console = {
log: function(v) { postMessage({type: "debug", message: v}); }
};
diff --git a/static/editor.md/lib/codemirror/addon/wrap/hardwrap.js b/static/editor.md/lib/codemirror/addon/wrap/hardwrap.js
index fe9b4dd66..516368c80 100644
--- a/static/editor.md/lib/codemirror/addon/wrap/hardwrap.js
+++ b/static/editor.md/lib/codemirror/addon/wrap/hardwrap.js
@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
@@ -29,25 +29,40 @@
return {from: start, to: end};
}
- function findBreakPoint(text, column, wrapOn, killTrailingSpace) {
- for (var at = column; at > 0; --at)
+ function findBreakPoint(text, column, wrapOn, killTrailingSpace, forceBreak) {
+ var at = column
+ while (at < text.length && text.charAt(at) == " ") at++
+ for (; at > 0; --at)
if (wrapOn.test(text.slice(at - 1, at + 1))) break;
- if (at == 0) at = column;
- var endOfText = at;
- if (killTrailingSpace)
- while (text.charAt(endOfText - 1) == " ") --endOfText;
- return {from: endOfText, to: at};
+
+ if (!forceBreak && at <= text.match(/^[ \t]*/)[0].length) {
+ // didn't find a break point before column, in non-forceBreak mode try to
+ // find one after 'column'.
+ for (at = column + 1; at < text.length - 1; ++at) {
+ if (wrapOn.test(text.slice(at - 1, at + 1))) break;
+ }
+ }
+
+ for (var first = true;; first = false) {
+ var endOfText = at;
+ if (killTrailingSpace)
+ while (text.charAt(endOfText - 1) == " ") --endOfText;
+ if (endOfText == 0 && first) at = column;
+ else return {from: endOfText, to: at};
+ }
}
function wrapRange(cm, from, to, options) {
from = cm.clipPos(from); to = cm.clipPos(to);
var column = options.column || 80;
var wrapOn = options.wrapOn || /\s\S|-[^\.\d]/;
+ var forceBreak = options.forceBreak !== false;
var killTrailing = options.killTrailingSpace !== false;
var changes = [], curLine = "", curNo = from.line;
var lines = cm.getRange(from, to, false);
if (!lines.length) return null;
var leadingSpace = lines[0].match(/^[ \t]*/)[0];
+ if (leadingSpace.length >= column) column = leadingSpace.length + 1
for (var i = 0; i < lines.length; ++i) {
var text = lines[i], oldLen = curLine.length, spaceInserted = 0;
@@ -63,7 +78,7 @@
curLine += text;
if (i) {
var firstBreak = curLine.length > column && leadingSpace == spaceTrimmed &&
- findBreakPoint(curLine, column, wrapOn, killTrailing);
+ findBreakPoint(curLine, column, wrapOn, killTrailing, forceBreak);
// If this isn't broken, or is broken at a different point, remove old break
if (!firstBreak || firstBreak.from != oldLen || firstBreak.to != oldLen + spaceInserted) {
changes.push({text: [spaceInserted ? " " : ""],
@@ -75,18 +90,24 @@
}
}
while (curLine.length > column) {
- var bp = findBreakPoint(curLine, column, wrapOn, killTrailing);
- changes.push({text: ["", leadingSpace],
- from: Pos(curNo, bp.from),
- to: Pos(curNo, bp.to)});
- curLine = leadingSpace + curLine.slice(bp.to);
- ++curNo;
+ var bp = findBreakPoint(curLine, column, wrapOn, killTrailing, forceBreak);
+ if (bp.from != bp.to ||
+ forceBreak && leadingSpace !== curLine.slice(0, bp.to)) {
+ changes.push({text: ["", leadingSpace],
+ from: Pos(curNo, bp.from),
+ to: Pos(curNo, bp.to)});
+ curLine = leadingSpace + curLine.slice(bp.to);
+ ++curNo;
+ } else {
+ break;
+ }
}
}
if (changes.length) cm.operation(function() {
for (var i = 0; i < changes.length; ++i) {
var change = changes[i];
- cm.replaceRange(change.text, change.from, change.to);
+ if (change.text || CodeMirror.cmpPos(change.from, change.to))
+ cm.replaceRange(change.text, change.from, change.to);
}
});
return changes.length ? {from: changes[0].from, to: CodeMirror.changeEnd(changes[changes.length - 1])} : null;
diff --git a/static/editor.md/lib/codemirror/addons.min.js b/static/editor.md/lib/codemirror/addons.min.js
index a09808009..314965c15 100644
--- a/static/editor.md/lib/codemirror/addons.min.js
+++ b/static/editor.md/lib/codemirror/addons.min.js
@@ -1,4 +1,2 @@
-/*! Editor.md v1.5.0 | addons.min.js | Open source online markdown editor. | MIT License | By: Pandao | https://github.com/pandao/editor.md | 2015-06-09 */
-!function(e){"object"==typeof exports&&"object"==typeof module?e(require("../../lib/codemirror")):"function"==typeof define&&define.amd?define(["../../lib/codemirror"],e):e(CodeMirror)}(function(e){e.defineOption("showTrailingSpace",!1,function(t,i,o){o==e.Init&&(o=!1),o&&!i?t.removeOverlay("trailingspace"):!o&&i&&t.addOverlay({token:function(e){for(var t=e.string.length,i=t;i&&/\s/.test(e.string.charAt(i-1));--i);return i>e.pos?(e.pos=i,null):(e.pos=t,"trailingspace")},name:"trailingspace"})})}),function(e){"object"==typeof exports&&"object"==typeof module?e(require("../../lib/codemirror")):"function"==typeof define&&define.amd?define(["../../lib/codemirror"],e):e(CodeMirror)}(function(e){function t(e,t,i){var o,r=e.getWrapperElement();return o=r.appendChild(document.createElement("div")),i?o.className="CodeMirror-dialog CodeMirror-dialog-bottom":o.className="CodeMirror-dialog CodeMirror-dialog-top","string"==typeof t?o.innerHTML=t:o.appendChild(t),o}function i(e,t){e.state.currentNotificationClose&&e.state.currentNotificationClose(),e.state.currentNotificationClose=t}e.defineExtension("openDialog",function(o,r,n){function a(e){if("string"==typeof e)h.value=e;else{if(c)return;c=!0,l.parentNode.removeChild(l),d.focus(),n.onClose&&n.onClose(l)}}n||(n={}),i(this,null);var s,l=t(this,o,n.bottom),c=!1,d=this,h=l.getElementsByTagName("input")[0];return h?(n.value&&(h.value=n.value,h.select()),n.onInput&&e.on(h,"input",function(e){n.onInput(e,h.value,a)}),n.onKeyUp&&e.on(h,"keyup",function(e){n.onKeyUp(e,h.value,a)}),e.on(h,"keydown",function(t){n&&n.onKeyDown&&n.onKeyDown(t,h.value,a)||((27==t.keyCode||n.closeOnEnter!==!1&&13==t.keyCode)&&(h.blur(),e.e_stop(t),a()),13==t.keyCode&&r(h.value,t))}),n.closeOnBlur!==!1&&e.on(h,"blur",a),h.focus()):(s=l.getElementsByTagName("button")[0])&&(e.on(s,"click",function(){a(),d.focus()}),n.closeOnBlur!==!1&&e.on(s,"blur",a),s.focus()),a}),e.defineExtension("openConfirm",function(o,r,n){function a(){c||(c=!0,s.parentNode.removeChild(s),d.focus())}i(this,null);var s=t(this,o,n&&n.bottom),l=s.getElementsByTagName("button"),c=!1,d=this,h=1;l[0].focus();for(var u=0;u
=h&&a()},200)}),e.on(f,"focus",function(){++h})}}),e.defineExtension("openNotification",function(o,r){function n(){l||(l=!0,clearTimeout(a),s.parentNode.removeChild(s))}i(this,n);var a,s=t(this,o,r&&r.bottom),l=!1,c=r&&"undefined"!=typeof r.duration?r.duration:5e3;return e.on(s,"click",function(t){e.e_preventDefault(t),n()}),c&&(a=setTimeout(n,c)),n})}),function(e){"object"==typeof exports&&"object"==typeof module?e(require("../../lib/codemirror")):"function"==typeof define&&define.amd?define(["../../lib/codemirror"],e):e(CodeMirror)}(function(e){"use strict";function t(e,t,r,n){if(this.atOccurrence=!1,this.doc=e,null==n&&"string"==typeof t&&(n=!1),r=r?e.clipPos(r):o(0,0),this.pos={from:r,to:r},"string"!=typeof t)t.global||(t=new RegExp(t.source,t.ignoreCase?"ig":"g")),this.matches=function(i,r){if(i){t.lastIndex=0;for(var n,a,s=e.getLine(r.line).slice(0,r.ch),l=0;;){t.lastIndex=l;var c=t.exec(s);if(!c)break;if(n=c,a=n.index,l=n.index+(n[0].length||1),l==s.length)break}var d=n&&n[0].length||0;d||(0==a&&0==s.length?n=void 0:a!=e.getLine(r.line).length&&d++)}else{t.lastIndex=r.ch;var s=e.getLine(r.line),n=t.exec(s),d=n&&n[0].length||0,a=n&&n.index;a+d==s.length||d||(d=1)}return n&&d?{from:o(r.line,a),to:o(r.line,a+d),match:n}:void 0};else{var a=t;n&&(t=t.toLowerCase());var s=n?function(e){return e.toLowerCase()}:function(e){return e},l=t.split("\n");if(1==l.length)t.length?this.matches=function(r,n){if(r){var l=e.getLine(n.line).slice(0,n.ch),c=s(l),d=c.lastIndexOf(t);if(d>-1)return d=i(l,c,d),{from:o(n.line,d),to:o(n.line,d+a.length)}}else{var l=e.getLine(n.line).slice(n.ch),c=s(l),d=c.indexOf(t);if(d>-1)return d=i(l,c,d)+n.ch,{from:o(n.line,d),to:o(n.line,d+a.length)}}}:this.matches=function(){};else{var c=a.split("\n");this.matches=function(t,i){var r=l.length-1;if(t){if(i.line-(l.length-1)=1;--d,--a)if(l[d]!=s(e.getLine(a)))return;var h=e.getLine(a),u=h.length-c[0].length;if(s(h.slice(u))!=l[0])return;return{from:o(a,u),to:n}}if(!(i.line+(l.length-1)>e.lastLine())){var h=e.getLine(i.line),u=h.length-c[0].length;if(s(h.slice(u))==l[0]){for(var f=o(i.line,u),a=i.line+1,d=1;r>d;++d,++a)if(l[d]!=s(e.getLine(a)))return;if(s(e.getLine(a).slice(0,c[r].length))==l[r])return{from:f,to:o(a,c[r].length)}}}}}}}function i(e,t,i){if(e.length==t.length)return i;for(var o=Math.min(i,e.length);;){var r=e.slice(0,o).toLowerCase().length;if(i>r)++o;else{if(!(r>i))return o;--o}}}var o=e.Pos;t.prototype={findNext:function(){return this.find(!1)},findPrevious:function(){return this.find(!0)},find:function(e){function t(e){var t=o(e,0);return i.pos={from:t,to:t},i.atOccurrence=!1,!1}for(var i=this,r=this.doc.clipPos(e?this.pos.from:this.pos.to);;){if(this.pos=this.matches(e,r))return this.atOccurrence=!0,this.pos.match||!0;if(e){if(!r.line)return t(0);r=o(r.line-1,this.doc.getLine(r.line-1).length)}else{var n=this.doc.lineCount();if(r.line==n-1)return t(n);r=o(r.line+1,0)}}},from:function(){return this.atOccurrence?this.pos.from:void 0},to:function(){return this.atOccurrence?this.pos.to:void 0},replace:function(t){if(this.atOccurrence){var i=e.splitLines(t);this.doc.replaceRange(i,this.pos.from,this.pos.to),this.pos.to=o(this.pos.from.line+i.length-1,i[i.length-1].length+(1==i.length?this.pos.from.ch:0))}}},e.defineExtension("getSearchCursor",function(e,i,o){return new t(this.doc,e,i,o)}),e.defineDocExtension("getSearchCursor",function(e,i,o){return new t(this,e,i,o)}),e.defineExtension("selectMatches",function(t,i){for(var o,r=[],n=this.getSearchCursor(t,this.getCursor("from"),i);(o=n.findNext())&&!(e.cmpPos(n.to(),this.getCursor("to"))>0);)r.push({anchor:n.from(),head:n.to()});r.length&&this.setSelections(r,0)})}),function(e){"object"==typeof exports&&"object"==typeof module?e(require("../../lib/codemirror"),require("./searchcursor"),require("../dialog/dialog")):"function"==typeof define&&define.amd?define(["../../lib/codemirror","./searchcursor","../dialog/dialog"],e):e(CodeMirror)}(function(e){"use strict";function t(e,t){return"string"==typeof e?e=new RegExp(e.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&"),t?"gi":"g"):e.global||(e=new RegExp(e.source,e.ignoreCase?"gi":"g")),{token:function(t){e.lastIndex=t.pos;var i=e.exec(t.string);return i&&i.index==t.pos?(t.pos+=i[0].length,"searching"):void(i?t.pos=i.index:t.skipToEnd())}}}function i(){this.posFrom=this.posTo=this.query=null,this.overlay=null}function o(e){return e.state.search||(e.state.search=new i)}function r(e){return"string"==typeof e&&e==e.toLowerCase()}function n(e,t,i){return e.getSearchCursor(t,i,r(t))}function a(e,t,i,o,r){e.openDialog?e.openDialog(t,r,{value:o}):r(prompt(i,o))}function s(e,t,i,o){e.openConfirm?e.openConfirm(t,o):confirm(i)&&o[0]()}function l(e){var t=e.match(/^\/(.*)\/([a-z]*)$/);if(t)try{e=new RegExp(t[1],-1==t[2].indexOf("i")?"":"i")}catch(i){}return("string"==typeof e?""==e:e.test(""))&&(e=/x^/),e}function c(e,i){var n=o(e);return n.query?d(e,i):void a(e,f,"Search for:",e.getSelection(),function(o){e.operation(function(){o&&!n.query&&(n.query=l(o),e.removeOverlay(n.overlay,r(n.query)),n.overlay=t(n.query,r(n.query)),e.addOverlay(n.overlay),e.showMatchesOnScrollbar&&(n.annotate&&(n.annotate.clear(),n.annotate=null),n.annotate=e.showMatchesOnScrollbar(n.query,r(n.query))),n.posFrom=n.posTo=e.getCursor(),d(e,i))})})}function d(t,i){t.operation(function(){var r=o(t),a=n(t,r.query,i?r.posFrom:r.posTo);(a.find(i)||(a=n(t,r.query,i?e.Pos(t.lastLine()):e.Pos(t.firstLine(),0)),a.find(i)))&&(t.setSelection(a.from(),a.to()),t.scrollIntoView({from:a.from(),to:a.to()}),r.posFrom=a.from(),r.posTo=a.to())})}function h(e){e.operation(function(){var t=o(e);t.query&&(t.query=null,e.removeOverlay(t.overlay),t.annotate&&(t.annotate.clear(),t.annotate=null))})}function u(e,t){e.getOption("readOnly")||a(e,g,"Replace:",e.getSelection(),function(i){i&&(i=l(i),a(e,p,"Replace with:","",function(o){if(t)e.operation(function(){for(var t=n(e,i);t.findNext();)if("string"!=typeof i){var r=e.getRange(t.from(),t.to()).match(i);t.replace(o.replace(/\$(\d)/g,function(e,t){return r[t]}))}else t.replace(o)});else{h(e);var r=n(e,i,e.getCursor()),a=function(){var t,o=r.from();!(t=r.findNext())&&(r=n(e,i),!(t=r.findNext())||o&&r.from().line==o.line&&r.from().ch==o.ch)||(e.setSelection(r.from(),r.to()),e.scrollIntoView({from:r.from(),to:r.to()}),s(e,m,"Replace?",[function(){l(t)},a]))},l=function(e){r.replace("string"==typeof i?o:o.replace(/\$(\d)/g,function(t,i){return e[i]})),a()};a()}}))})}var f='Search: (Use /re/ syntax for regexp search) ',g='Replace: (Use /re/ syntax for regexp search) ',p='With: ',m="Replace? Yes No Stop ";e.commands.find=function(e){h(e),c(e)},e.commands.findNext=c,e.commands.findPrev=function(e){c(e,!0)},e.commands.clearSearch=h,e.commands.replace=u,e.commands.replaceAll=function(e){u(e,!0)}}),function(e){"object"==typeof exports&&"object"==typeof module?e(require("../../lib/codemirror")):"function"==typeof define&&define.amd?define(["../../lib/codemirror"],e):e(CodeMirror)}(function(e){"use strict";function t(e,t){function i(e){clearTimeout(o.doRedraw),o.doRedraw=setTimeout(function(){o.redraw()},e)}this.cm=e,this.options=t,this.buttonHeight=t.scrollButtonHeight||e.getOption("scrollButtonHeight"),this.annotations=[],this.doRedraw=this.doUpdate=null,this.div=e.getWrapperElement().appendChild(document.createElement("div")),this.div.style.cssText="position: absolute; right: 0; top: 0; z-index: 7; pointer-events: none",this.computeScale();var o=this;e.on("refresh",this.resizeHandler=function(){clearTimeout(o.doUpdate),o.doUpdate=setTimeout(function(){o.computeScale()&&i(20)},100)}),e.on("markerAdded",this.resizeHandler),e.on("markerCleared",this.resizeHandler),t.listenForChanges!==!1&&e.on("change",this.changeHandler=function(){i(250)})}e.defineExtension("annotateScrollbar",function(e){return"string"==typeof e&&(e={className:e}),new t(this,e)}),e.defineOption("scrollButtonHeight",0),t.prototype.computeScale=function(){var e=this.cm,t=(e.getWrapperElement().clientHeight-e.display.barHeight-2*this.buttonHeight)/e.heightAtLine(e.lastLine()+1,"local");return t!=this.hScale?(this.hScale=t,!0):void 0},t.prototype.update=function(e){this.annotations=e,this.redraw()},t.prototype.redraw=function(e){e!==!1&&this.computeScale();var t=this.cm,i=this.hScale,o=document.createDocumentFragment(),r=this.annotations;if(t.display.barWidth)for(var n,a=0;ac+.9));)s=r[++a],c=t.charCoords(s.to,"local").bottom*i;if(c!=l){var d=Math.max(c-l,3),h=o.appendChild(document.createElement("div"));h.style.cssText="position: absolute; right: 0px; width: "+Math.max(t.display.barWidth-1,2)+"px; top: "+(l+this.buttonHeight)+"px; height: "+d+"px",h.className=this.options.className}}this.div.textContent="",this.div.appendChild(o)},t.prototype.clear=function(){this.cm.off("refresh",this.resizeHandler),this.cm.off("markerAdded",this.resizeHandler),this.cm.off("markerCleared",this.resizeHandler),this.changeHandler&&this.cm.off("change",this.changeHandler),this.div.parentNode.removeChild(this.div)}}),function(e){"object"==typeof exports&&"object"==typeof module?e(require("../../lib/codemirror"),require("./searchcursor"),require("../scroll/annotatescrollbar")):"function"==typeof define&&define.amd?define(["../../lib/codemirror","./searchcursor","../scroll/annotatescrollbar"],e):e(CodeMirror)}(function(e){"use strict";function t(e,t,i,o){this.cm=e;var r={listenForChanges:!1};for(var n in o)r[n]=o[n];r.className||(r.className="CodeMirror-search-match"),this.annotation=e.annotateScrollbar(r),this.query=t,this.caseFold=i,this.gap={from:e.firstLine(),to:e.lastLine()+1},this.matches=[],this.update=null,this.findMatches(),this.annotation.update(this.matches);var a=this;e.on("change",this.changeHandler=function(e,t){a.onChange(t)})}function i(e,t,i){return t>=e?e:Math.max(t,e+i)}e.defineExtension("showMatchesOnScrollbar",function(e,i,o){return"string"==typeof o&&(o={className:o}),o||(o={}),new t(this,e,i,o)});var o=1e3;t.prototype.findMatches=function(){if(this.gap){for(var t=0;t=this.gap.to)break;i.to.line>=this.gap.from&&this.matches.splice(t--,1)}for(var r=this.cm.getSearchCursor(this.query,e.Pos(this.gap.from,0),this.caseFold);r.findNext();){var i={from:r.from(),to:r.to()};if(i.from.line>=this.gap.to)break;if(this.matches.splice(t++,0,i),this.matches.length>o)break}this.gap=null}},t.prototype.onChange=function(t){var o=t.from.line,r=e.changeEnd(t).line,n=r-t.to.line;if(this.gap?(this.gap.from=Math.min(i(this.gap.from,o,n),t.from.line),this.gap.to=Math.max(i(this.gap.to,o,n),t.from.line)):this.gap={from:t.from.line,to:r+1},n)for(var a=0;ac.ch&&(v=v.slice(0,v.length-d.end+c.ch));var w=v.toLowerCase();if(!v||"string"==d.type&&(d.end!=c.ch||!/[\"\']/.test(d.string.charAt(d.string.length-1))||1==d.string.length)||"tag"==d.type&&"closeTag"==u.type||d.string.indexOf("/")==d.string.length-1||p&&r(p,w)>-1||n(t,v,c,u,!0))return e.Pass;var b=m&&r(m,w)>-1;o[l]={indent:b,text:">"+(b?"\n\n":"")+""+v+">",newPos:b?e.Pos(c.line+1,0):e.Pos(c.line,c.ch+1)}}for(var l=i.length-1;l>=0;l--){var y=o[l];t.replaceRange(y.text,i[l].head,i[l].anchor,"+insert");var k=t.listSelections().slice(0);k[l]={head:y.newPos,anchor:y.newPos},t.setSelections(k),y.indent&&(t.indentLine(y.newPos.line,null,!0),t.indentLine(y.newPos.line+1,null,!0))}}function i(t,i){for(var o=t.listSelections(),r=[],a=i?"/":"",s=0;s";else{if("htmlmixed"!=t.getMode().name||"css"!=d.mode.name)return e.Pass;r[s]=a+"style>"}else{if(!h.context||!h.context.tagName||n(t,h.context.tagName,l,h))return e.Pass;r[s]=a+h.context.tagName+">"}}t.replaceSelections(r),o=t.listSelections();for(var s=0;si;++i)if(e[i]==t)return i;return-1}function n(t,i,o,r,n){if(!e.scanForClosingTag)return!1;var a=Math.min(t.lastLine()+1,o.line+500),s=e.scanForClosingTag(t,o,null,a);if(!s||s.tag!=i)return!1;for(var l=r.context,c=n?1:0;l&&l.tagName==i;l=l.prev)++c;o=s.to;for(var d=1;c>d;d++){var h=e.scanForClosingTag(t,o,null,a);if(!h||h.tag!=i)return!1;o=h.to}return!0}e.defineOption("autoCloseTags",!1,function(i,r,n){if(n!=e.Init&&n&&i.removeKeyMap("autoCloseTags"),r){var a={name:"autoCloseTags"};("object"!=typeof r||r.whenClosing)&&(a["'/'"]=function(e){return o(e)}),("object"!=typeof r||r.whenOpening)&&(a["'>'"]=function(e){return t(e)}),i.addKeyMap(a)}});var a=["area","base","br","col","command","embed","hr","img","input","keygen","link","meta","param","source","track","wbr"],s=["applet","blockquote","body","button","div","dl","fieldset","form","frameset","h1","h2","h3","h4","h5","h6","head","html","iframe","layer","legend","object","ol","p","select","table","ul"];e.commands.closeTag=function(e){return i(e)}}),function(e){"object"==typeof exports&&"object"==typeof module?e(require("../../lib/codemirror")):"function"==typeof define&&define.amd?define(["../../lib/codemirror"],e):e(CodeMirror)}(function(e){"use strict";function t(t,r,n,a){function s(e){var i=l(t,r);if(!i||i.to.line-i.from.linet.firstLine();)r=e.Pos(r.line-1,0),d=s(!1);if(d&&!d.cleared&&"unfold"!==a){var h=i(t,n);e.on(h,"mousedown",function(t){u.clear(),e.e_preventDefault(t)});var u=t.markText(d.from,d.to,{replacedWith:h,clearOnEnter:!0,__isFold:!0});u.on("clear",function(i,o){e.signal(t,"unfold",t,i,o)}),e.signal(t,"fold",t,d.from,d.to)}}function i(e,t){var i=o(e,t,"widget");if("string"==typeof i){var r=document.createTextNode(i);i=document.createElement("span"),i.appendChild(r),i.className="CodeMirror-foldmarker"}return i}function o(e,t,i){if(t&&void 0!==t[i])return t[i];var o=e.options.foldOptions;return o&&void 0!==o[i]?o[i]:r[i]}e.newFoldFunction=function(e,i){return function(o,r){t(o,r,{rangeFinder:e,widget:i})}},e.defineExtension("foldCode",function(e,i,o){t(this,e,i,o)}),e.defineExtension("isFolded",function(e){for(var t=this.findMarksAt(e),i=0;i=i;i++)t.foldCode(e.Pos(i,0),null,"fold")})},e.commands.unfoldAll=function(t){t.operation(function(){for(var i=t.firstLine(),o=t.lastLine();o>=i;i++)t.foldCode(e.Pos(i,0),null,"unfold")})},e.registerHelper("fold","combine",function(){var e=Array.prototype.slice.call(arguments,0);return function(t,i){for(var o=0;o=s&&(i=r(n.indicatorOpen))}e.setGutterMarker(t,n.gutter,i),++a})}function a(e){var t=e.getViewport(),i=e.state.foldGutter;i&&(e.operation(function(){n(e,t.from,t.to)}),i.from=t.from,i.to=t.to)}function s(e,t,i){var o=e.state.foldGutter;if(o){var r=o.options;i==r.gutter&&e.foldCode(h(t,0),r.rangeFinder)}}function l(e){var t=e.state.foldGutter;if(t){var i=t.options;t.from=t.to=0,clearTimeout(t.changeUpdate),t.changeUpdate=setTimeout(function(){a(e)},i.foldOnChangeTimeSpan||600)}}function c(e){var t=e.state.foldGutter;if(t){var i=t.options;clearTimeout(t.changeUpdate),t.changeUpdate=setTimeout(function(){var i=e.getViewport();t.from==t.to||i.from-t.to>20||t.from-i.to>20?a(e):e.operation(function(){i.fromt.to&&(n(e,t.to,i.to),t.to=i.to)})},i.updateViewportTimeSpan||400)}}function d(e,t){var i=e.state.foldGutter;if(i){var o=t.line;o>=i.from&&o=l;++l){var d=t.getLine(l),h=n(d);if(h>a)s=l;else if(/\S/.test(d))break}return s?{from:e.Pos(i.line,r.length),to:e.Pos(s,t.getLine(s).length)}:void 0}})}),function(e){"object"==typeof exports&&"object"==typeof module?e(require("../../lib/codemirror")):"function"==typeof define&&define.amd?define(["../../lib/codemirror"],e):e(CodeMirror)}(function(e){"use strict";e.registerHelper("fold","brace",function(t,i){function o(o){for(var r=i.ch,l=0;;){var c=0>=r?-1:s.lastIndexOf(o,r-1);if(-1!=c){if(1==l&&c=g;++g)for(var p=t.getLine(g),m=g==a?r:0;;){var v=p.indexOf(l,m),w=p.indexOf(c,m);if(0>v&&(v=p.length),0>w&&(w=p.length),m=Math.min(v,w),m==p.length)break;if(t.getTokenTypeAt(e.Pos(g,m+1))==n)if(m==v)++u;else if(!--u){d=g,h=m;break e}++m}if(null!=d&&(a!=d||h!=r))return{from:e.Pos(a,r),to:e.Pos(d,h)}}}),e.registerHelper("fold","import",function(t,i){function o(i){if(it.lastLine())return null;var o=t.getTokenAt(e.Pos(i,1));if(/\S/.test(o.string)||(o=t.getTokenAt(e.Pos(i,o.end+1))),"keyword"!=o.type||"import"!=o.string)return null;for(var r=i,n=Math.min(t.lastLine(),i+10);n>=r;++r){var a=t.getLine(r),s=a.indexOf(";");if(-1!=s)return{startCh:o.end,end:e.Pos(r,s)}}}var r,i=i.line,n=o(i);if(!n||o(i-1)||(r=o(i-2))&&r.end.line==i-1)return null;for(var a=n.end;;){var s=o(a.line+1);if(null==s)break;a=s.end}return{from:t.clipPos(e.Pos(i,n.startCh+1)),to:a}}),e.registerHelper("fold","include",function(t,i){function o(i){if(it.lastLine())return null;var o=t.getTokenAt(e.Pos(i,1));return/\S/.test(o.string)||(o=t.getTokenAt(e.Pos(i,o.end+1))),"meta"==o.type&&"#include"==o.string.slice(0,8)?o.start+8:void 0}var i=i.line,r=o(i);if(null==r||null!=o(i-1))return null;for(var n=i;;){var a=o(n+1);if(null==a)break;++n}return{from:e.Pos(i,r+1),to:t.clipPos(e.Pos(n))}})}),function(e){"object"==typeof exports&&"object"==typeof module?e(require("../../lib/codemirror")):"function"==typeof define&&define.amd?define(["../../lib/codemirror"],e):e(CodeMirror)}(function(e){"use strict";function t(e,t){return e.line-t.line||e.ch-t.ch}function i(e,t,i,o){this.line=t,this.ch=i,this.cm=e,this.text=e.getLine(t),this.min=o?o.from:e.firstLine(),this.max=o?o.to-1:e.lastLine()}function o(e,t){var i=e.cm.getTokenTypeAt(u(e.line,t));return i&&/\btag\b/.test(i)}function r(e){return e.line>=e.max?void 0:(e.ch=0,e.text=e.cm.getLine(++e.line),!0)}function n(e){return e.line<=e.min?void 0:(e.text=e.cm.getLine(--e.line),e.ch=e.text.length,!0)}function a(e){for(;;){var t=e.text.indexOf(">",e.ch);if(-1==t){if(r(e))continue;return}{if(o(e,t+1)){var i=e.text.lastIndexOf("/",t),n=i>-1&&!/\S/.test(e.text.slice(i+1,t));return e.ch=t+1,n?"selfClose":"regular"}e.ch=t+1}}}function s(e){for(;;){var t=e.ch?e.text.lastIndexOf("<",e.ch-1):-1;if(-1==t){if(n(e))continue;return}if(o(e,t+1)){p.lastIndex=t,e.ch=t;var i=p.exec(e.text);if(i&&i.index==t)return i}else e.ch=t}}function l(e){for(;;){p.lastIndex=e.ch;var t=p.exec(e.text);if(!t){if(r(e))continue;return}{if(o(e,t.index+1))return e.ch=t.index+t[0].length,t;e.ch=t.index+1}}}function c(e){for(;;){var t=e.ch?e.text.lastIndexOf(">",e.ch-1):-1;if(-1==t){if(n(e))continue;return}{if(o(e,t+1)){var i=e.text.lastIndexOf("/",t),r=i>-1&&!/\S/.test(e.text.slice(i+1,t));return e.ch=t+1,r?"selfClose":"regular"}e.ch=t}}}function d(e,t){for(var i=[];;){var o,r=l(e),n=e.line,s=e.ch-(r?r[0].length:0);if(!r||!(o=a(e)))return;if("selfClose"!=o)if(r[1]){for(var c=i.length-1;c>=0;--c)if(i[c]==r[2]){i.length=c;break}if(0>c&&(!t||t==r[2]))return{tag:r[2],from:u(n,s),to:u(e.line,e.ch)}}else i.push(r[2])}}function h(e,t){for(var i=[];;){var o=c(e);if(!o)return;if("selfClose"!=o){var r=e.line,n=e.ch,a=s(e);if(!a)return;if(a[1])i.push(a[2]);else{for(var l=i.length-1;l>=0;--l)if(i[l]==a[2]){i.length=l;break}if(0>l&&(!t||t==a[2]))return{tag:a[2],from:u(e.line,e.ch),to:u(r,n)}}}else s(e)}}var u=e.Pos,f="A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD",g=f+"-:.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040",p=new RegExp("<(/?)(["+f+"]["+g+"]*)","g");e.registerHelper("fold","xml",function(e,t){for(var o=new i(e,t.line,0);;){var r,n=l(o);if(!n||o.line!=t.line||!(r=a(o)))return;if(!n[1]&&"selfClose"!=r){var t=u(o.line,o.ch),s=d(o,n[2]);return s&&{from:t,to:s.from}}}}),e.findMatchingTag=function(e,o,r){var n=new i(e,o.line,o.ch,r);if(-1!=n.text.indexOf(">")||-1!=n.text.indexOf("<")){var l=a(n),c=l&&u(n.line,n.ch),f=l&&s(n);if(l&&f&&!(t(n,o)>0)){var g={from:u(n.line,n.ch),to:c,tag:f[2]};return"selfClose"==l?{open:g,close:null,at:"open"}:f[1]?{open:h(n,f[2]),close:g,at:"close"}:(n=new i(e,c.line,c.ch,r),{open:g,close:d(n,f[2]),at:"open"})}}},e.findEnclosingTag=function(e,t,o){for(var r=new i(e,t.line,t.ch,o);;){var n=h(r);if(!n)break;var a=new i(e,t.line,t.ch,o),s=d(a,n.tag);if(s)return{open:n,close:s}}},e.scanForClosingTag=function(e,t,o,r){var n=new i(e,t.line,t.ch,r?{from:0,to:r}:null);return d(n,o)}}),function(e){"object"==typeof exports&&"object"==typeof module?e(require("../../lib/codemirror")):"function"==typeof define&&define.amd?define(["../../lib/codemirror"],e):e(CodeMirror)}(function(e){"use strict";e.registerHelper("fold","markdown",function(t,i){function o(i){var o=t.getTokenTypeAt(e.Pos(i,0));return o&&/\bheader\b/.test(o)}function r(e,t,i){var r=t&&t.match(/^#+/);return r&&o(e)?r[0].length:(r=i&&i.match(/^[=\-]+\s*$/),r&&o(e+1)?"="==i[0]?1:2:n)}var n=100,a=t.getLine(i.line),s=t.getLine(i.line+1),l=r(i.line,a,s);if(l===n)return void 0;for(var c=t.lastLine(),d=i.line,h=t.getLine(d+2);c>d&&!(r(d+1,s,h)<=l);)++d,s=h,h=t.getLine(d+2);return{from:e.Pos(i.line,a.length),to:e.Pos(d,t.getLine(d).length)}})}),function(e){"object"==typeof exports&&"object"==typeof module?e(require("../../lib/codemirror")):"function"==typeof define&&define.amd?define(["../../lib/codemirror"],e):e(CodeMirror)}(function(e){"use strict";e.registerGlobalHelper("fold","comment",function(e){return e.blockCommentStart&&e.blockCommentEnd},function(t,i){var o=t.getModeAt(i),r=o.blockCommentStart,n=o.blockCommentEnd;if(r&&n){for(var a,s=i.line,l=t.getLine(s),c=i.ch,d=0;;){var h=0>=c?-1:l.lastIndexOf(r,c-1);if(-1!=h){if(1==d&&h=m;++m)for(var v=t.getLine(m),w=m==s?a:0;;){var b=v.indexOf(r,w),y=v.indexOf(n,w);if(0>b&&(b=v.length),0>y&&(y=v.length),w=Math.min(b,y),w==v.length)break;if(w==b)++g;else if(!--g){u=m,f=w;break e}++w}if(null!=u&&(s!=u||f!=a))return{from:e.Pos(s,a),to:e.Pos(u,f)}}})}),function(e){"object"==typeof exports&&"object"==typeof module?e(require("../../lib/codemirror")):"function"==typeof define&&define.amd?define(["../../lib/codemirror"],e):e(CodeMirror)}(function(e){"use strict";e.overlayMode=function(t,i,o){return{startState:function(){return{base:e.startState(t),overlay:e.startState(i),basePos:0,baseCur:null,overlayPos:0,overlayCur:null,streamSeen:null}},copyState:function(o){return{base:e.copyState(t,o.base),overlay:e.copyState(i,o.overlay),basePos:o.basePos,baseCur:null,overlayPos:o.overlayPos,overlayCur:null}},token:function(e,r){return(e!=r.streamSeen||Math.min(r.basePos,r.overlayPos)=i.ch+1)return/\bstring2?\b/.test(s);a.start=a.pos}}function o(o,r){for(var n={name:"autoCloseBrackets",Backspace:function(i){if(i.getOption("disableInput"))return e.Pass;for(var r=i.listSelections(),n=0;n=0;n--){var s=r[n].head;i.replaceRange("",c(s.line,s.ch-1),c(s.line,s.ch+1))}}},a="",s=0;s1&&r.indexOf(t)>=0&&n.getRange(c(p.line,p.ch-2),p)==t+t&&(p.ch<=2||n.getRange(c(p.line,p.ch-3),c(p.line,p.ch-2))!=t))f="addFour";else if('"'==t||"'"==t){if(e.isWordChar(d)||!i(n,p,t))return e.Pass;f="both"}else{if(!(n.getLine(p.line).length==p.ch||a.indexOf(d)>=0||l.test(d)))return e.Pass;f="both"}else f="surround";if(s){if(s!=f)return e.Pass}else s=f}n.operation(function(){if("skip"==s)n.execCommand("goCharRight");else if("skipThree"==s)for(var e=0;3>e;e++)n.execCommand("goCharRight");else if("surround"==s){for(var i=n.getSelections(),e=0;es&&e.addOverlay(t.overlay=a(n.slice(s,l),i,t.style)))}var c=e.getCursor("from"),d=e.getCursor("to");if(c.line==d.line&&(!t.wordsOnly||r(e,c,d))){var h=e.getRange(c,d).replace(/^\s+|\s+$/g,"");h.length>=t.minChars&&e.addOverlay(t.overlay=a(h,!1,t.style))}})}function r(e,t,i){var o=e.getRange(t,i);if(null!==o.match(/^\w+$/)){if(t.ch>0){var r={line:t.line,ch:t.ch-1},n=e.getRange(r,t);if(null===n.match(/\W/))return!1}if(i.che.pos?(e.pos=n,null):(e.pos=t,"trailingspace")},name:"trailingspace"})})}),function(e){"object"==typeof exports&&"object"==typeof module?e(require("../../lib/codemirror")):"function"==typeof define&&define.amd?define(["../../lib/codemirror"],e):e(CodeMirror)}(function(u){function d(e,t,n){var e=e.getWrapperElement(),o=e.appendChild(document.createElement("div"));return o.className=n?"CodeMirror-dialog CodeMirror-dialog-bottom":"CodeMirror-dialog CodeMirror-dialog-top","string"==typeof t?o.innerHTML=t:o.appendChild(t),u.addClass(e,"dialog-opened"),o}function h(e,t){e.state.currentNotificationClose&&e.state.currentNotificationClose(),e.state.currentNotificationClose=t}u.defineExtension("openDialog",function(e,t,n){n=n||{},h(this,null);var o=d(this,e,n.bottom),r=!1,i=this;function a(e){"string"==typeof e?l.value=e:r||(r=!0,u.rmClass(o.parentNode,"dialog-opened"),o.parentNode.removeChild(o),i.focus(),n.onClose&&n.onClose(o))}var l=o.getElementsByTagName("input")[0];return l?(l.focus(),n.value&&(l.value=n.value,!1!==n.selectValueOnOpen)&&l.select(),n.onInput&&u.on(l,"input",function(e){n.onInput(e,l.value,a)}),n.onKeyUp&&u.on(l,"keyup",function(e){n.onKeyUp(e,l.value,a)}),u.on(l,"keydown",function(e){n&&n.onKeyDown&&n.onKeyDown(e,l.value,a)||((27==e.keyCode||!1!==n.closeOnEnter&&13==e.keyCode)&&(l.blur(),u.e_stop(e),a()),13==e.keyCode&&t(l.value,e))}),!1!==n.closeOnBlur&&u.on(o,"focusout",function(e){null!==e.relatedTarget&&a()})):(e=o.getElementsByTagName("button")[0])&&(u.on(e,"click",function(){a(),i.focus()}),!1!==n.closeOnBlur&&u.on(e,"blur",a),e.focus()),a}),u.defineExtension("openConfirm",function(e,t,n){h(this,null);var o=d(this,e,n&&n.bottom),r=o.getElementsByTagName("button"),i=!1,a=this,l=1;function s(){i||(i=!0,u.rmClass(o.parentNode,"dialog-opened"),o.parentNode.removeChild(o),a.focus())}r[0].focus();for(var c=0;ce.length-n)break;(!o||a>o.index+o[0].length)&&(o=i),r=i.index+1}return o}function C(e,t,n){t=p(t,"g");for(var o=n.line,r=n.ch,i=e.firstLine();i<=o;o--,r=-1){var a=e.getLine(o),a=b(a,t,r<0?0:a.length-r);if(a)return{from:y(o,a.index),to:y(o,a.index+a[0].length),match:a}}}function a(e,t,n){if(!g(t))return C(e,t,n);t=p(t,"gm");for(var o=1,r=e.getLine(n.line).length-n.ch,i=n.line,a=e.firstLine();a<=i;){for(var l=0;l>1,l=o(e.slice(0,a)).length;if(l==n)return a;n(this.doc.getLine(t.line)||"").length&&(t.ch=0,t.line++)),0!=r.cmpPos(t,this.doc.clipPos(t)))?this.atOccurrence=!1:(t=this.matches(e,t),this.afterEmptyMatch=t&&0==r.cmpPos(t.from,t.to),t?(this.pos=t,this.atOccurrence=!0,this.pos.match||!0):(t=y(e?this.doc.firstLine():this.doc.lastLine()+1,0),this.pos={from:t,to:t},this.atOccurrence=!1))},from:function(){if(this.atOccurrence)return this.pos.from},to:function(){if(this.atOccurrence)return this.pos.to},replace:function(e,t){this.atOccurrence&&(e=r.splitLines(e),this.doc.replaceRange(e,this.pos.from,this.pos.to,t),this.pos.to=y(this.pos.from.line+e.length-1,e[e.length-1].length+(1==e.length?this.pos.from.ch:0)))}},r.defineExtension("getSearchCursor",function(e,t,n){return new o(this.doc,e,t,n)}),r.defineDocExtension("getSearchCursor",function(e,t,n){return new o(this,e,t,n)}),r.defineExtension("selectMatches",function(e,t){for(var n=[],o=this.getSearchCursor(e,this.getCursor("from"),t);o.findNext()&&!(0r.cursorCoords(t,"window").top&&((i=n).style.opacity=.4)}))},e=C(l=r),s=f,c=function(e,t){var n=u.keyName(e),o=r.getOption("extraKeys"),o=o&&o[n]||u.keyMap[r.getOption("keyMap")][n];"findNext"==o||"findPrev"==o||"findPersistentNext"==o||"findPersistentPrev"==o?(u.e_stop(e),m(r,d(r),t),r.execCommand(o)):"find"!=o&&"findPersistent"!=o||(u.e_stop(e),a(t,e))},l.openDialog(e,a,{value:s,selectValueOnOpen:!0,closeOnEnter:!1,onClose:function(){y(l)},onKeyDown:c,bottom:l.options.search.bottom}),n&&f&&(m(r,o,f),v(r,t))):p(r,C(r),"Search for:",f,function(e){e&&!o.query&&r.operation(function(){m(r,o,e),o.posFrom=o.posTo=r.getCursor(),v(r,t)})})}function v(n,o,r){n.operation(function(){var e=d(n),t=h(n,e.query,o?e.posFrom:e.posTo);(t.find(o)||(t=h(n,e.query,o?u.Pos(n.lastLine()):u.Pos(n.firstLine(),0))).find(o))&&(n.setSelection(t.from(),t.to()),n.scrollIntoView({from:t.from(),to:t.to()},20),e.posFrom=t.from(),e.posTo=t.to(),r)&&r(t.from(),t.to())})}function y(t){t.operation(function(){var e=d(t);e.lastQuery=e.query,e.query&&(e.query=e.queryText=null,t.removeOverlay(e.overlay),e.annotate)&&(e.annotate.clear(),e.annotate=null)})}function b(e,t){var n,o=e?document.createElement(e):document.createDocumentFragment();for(n in t)o[n]=t[n];for(var r=2;ra?o.charCoords(e,"local")[t?"top":"bottom"]:o.heightAtLine(s,"local")+(t?0:s.height)}var f=o.lastLine();if(o.display.barWidth)for(var u,d=0;df)){for(var p,g,m=u||c(h.from,!0)*t,v=c(h.to,!1)*t;df)&&!(v+.9<(u=c(r[d+1].from,!0)*t));)v=c((h=r[++d]).to,!1)*t;v!=m&&(p=Math.max(v-m,3),(g=n.appendChild(document.createElement("div"))).style.cssText="position: absolute; right: 0px; width: "+Math.max(o.display.barWidth-1,2)+"px; top: "+(m+this.buttonHeight)+"px; height: "+p+"px",g.className=this.options.className,h.id)&&g.setAttribute("annotation-id",h.id)}}this.div.textContent="",this.div.appendChild(n)},t.prototype.clear=function(){this.cm.off("refresh",this.resizeHandler),this.cm.off("markerAdded",this.resizeHandler),this.cm.off("markerCleared",this.resizeHandler),this.changeHandler&&this.cm.off("changes",this.changeHandler),this.div.parentNode.removeChild(this.div)}}),function(e){"object"==typeof exports&&"object"==typeof module?e(require("../../lib/codemirror"),require("./searchcursor"),require("../scroll/annotatescrollbar")):"function"==typeof define&&define.amd?define(["../../lib/codemirror","./searchcursor","../scroll/annotatescrollbar"],e):e(CodeMirror)}(function(s){"use strict";function o(e,t,n,o){this.cm=e;var r,i={listenForChanges:!1};for(r in this.options=o)i[r]=o[r];i.className||(i.className="CodeMirror-search-match"),this.annotation=e.annotateScrollbar(i),this.query=t,this.caseFold=n,this.gap={from:e.firstLine(),to:e.lastLine()+1},this.matches=[],this.update=null,this.findMatches(),this.annotation.update(this.matches);var a=this;e.on("change",this.changeHandler=function(e,t){a.onChange(t)})}s.defineExtension("showMatchesOnScrollbar",function(e,t,n){return new o(this,e,t,n=(n="string"==typeof n?{className:n}:n)||{})});function c(e,t,n){return e<=t?e:Math.max(t,e+n)}o.prototype.findMatches=function(){if(this.gap){for(var e=0;e=this.gap.to)break;t.to.line>=this.gap.from&&this.matches.splice(e--,1)}for(var t,n=this.cm.getSearchCursor(this.query,s.Pos(this.gap.from,0),{caseFold:this.caseFold,multiline:this.options.multiline}),o=this.options&&this.options.maxMatches||1e3;n.findNext();){if((t={from:n.from(),to:n.to()}).from.line>=this.gap.to)break;if(this.matches.splice(e++,0,t),this.matches.length>o)break}this.gap=null}},o.prototype.onChange=function(e){var t=e.from.line,n=s.changeEnd(e).line,o=n-e.to.line;if(this.gap?(this.gap.from=Math.min(c(this.gap.from,t,o),e.from.line),this.gap.to=Math.max(c(this.gap.to,t,o),e.from.line)):this.gap={from:e.from.line,to:n+1},o)for(var r=0;r'"]=function(e){var t=e;if(t.getOption("disableInput"))return y.Pass;for(var n=t.listSelections(),o=[],r=t.getOption("autoCloseTags"),i=0;ia.ch?u.slice(0,u.length-l.end+a.ch):u).toLowerCase();if(!u||"string"==l.type&&(l.end!=a.ch||!/[\"\']/.test(l.string.charAt(l.string.length-1))||1==l.string.length)||"tag"==l.type&&f.close||l.string.indexOf("/")==a.ch-l.start-1||h&&-1 ",newPos:y.Pos(a.line,a.ch+2)}:(l=d&&-1"+(l?"\n\n":"")+""+u+">",newPos:l?y.Pos(a.line+1,0):y.Pos(a.line,a.ch+1)})}for(var g="object"==typeof r&&r.dontIndentOnAutoClose,i=n.length-1;0<=i;i--){var m=o[i],v=(t.replaceRange(m.text,n[i].head,n[i].anchor,"+insert"),t.listSelections().slice(0));v[i]={head:m.newPos,anchor:m.newPos},t.setSelections(v),!g&&m.indent&&(t.indentLine(m.newPos.line,null,!0),t.indentLine(m.newPos.line+1,null,!0))}}),e.addKeyMap(n))});var b=["area","base","br","col","command","embed","hr","img","input","keygen","link","meta","param","source","track","wbr"],C=["applet","blockquote","body","button","div","dl","fieldset","form","frameset","h1","h2","h3","h4","h5","h6","head","html","iframe","layer","legend","object","ol","p","select","table","ul"];function o(e,t){for(var n=e.listSelections(),o=[],r=t?"/":"",i=e.getOption("autoCloseTags"),i="object"==typeof i&&i.dontIndentOnSlash,a=0;a"!=e.getLine(l.line).charAt(s.end)&&(u+=">"),o[a]=u}if(e.replaceSelections(o),n=e.listSelections(),!i)for(a=0;ar.firstLine();)i=f.Pos(i.line-1,0),c=t(!1);c&&!c.cleared&&"unfold"!==a&&(n=function(e,t,n){e=u(e,t,"widget");"function"==typeof e&&(e=e(n.from,n.to));"string"==typeof e?(t=document.createTextNode(e),(e=document.createElement("span")).appendChild(t),e.className="CodeMirror-foldmarker"):e=e&&e.cloneNode(!0);return e}(r,e,c),f.on(n,"mousedown",function(e){o.clear(),f.e_preventDefault(e)}),(o=r.markText(c.from,c.to,{replacedWith:n,clearOnEnter:u(r,e,"clearOnEnter"),__isFold:!0})).on("clear",function(e,t){f.signal(r,"unfold",r,e,t)}),f.signal(r,"fold",r,c.from,c.to))}f.newFoldFunction=function(n,o){return function(e,t){r(e,t,{rangeFinder:n,widget:o})}},f.defineExtension("foldCode",function(e,t,n){r(this,e,t,n)}),f.defineExtension("isFolded",function(e){for(var t=this.findMarksAt(e),n=0;n=l){if(f&&n&&f.test(n.className))return;t=h(i.indicatorOpen)}}(t||n)&&r.setGutterMarker(e,i.gutter,t)})}function n(e){return new RegExp("(^|\\s)"+e+"(?:$|\\s)\\s*")}function a(e){var t=e.getViewport(),n=e.state.foldGutter;n&&(e.operation(function(){i(e,t.from,t.to)}),n.from=t.from,n.to=t.to)}function l(e,t,n){var o=e.state.foldGutter;o&&n==(n=o.options).gutter&&((o=d(e,t))?o.clear():e.foldCode(u(t,0),n))}function s(e){var t,n=e.state.foldGutter;n&&(t=n.options,n.from=n.to=0,clearTimeout(n.changeUpdate),n.changeUpdate=setTimeout(function(){a(e)},t.foldOnChangeTimeSpan||600))}function c(t){var e,n=t.state.foldGutter;n&&(e=n.options,clearTimeout(n.changeUpdate),n.changeUpdate=setTimeout(function(){var e=t.getViewport();n.from==n.to||20n.to&&(i(t,n.to,e.to),n.to=e.to)})},e.updateViewportTimeSpan||400))}function f(e,t){var n=e.state.foldGutter;n&&(t=t.line)>=n.from&&ti.lastLine())return null;var t=i.getTokenAt(h.Pos(e,1));if("keyword"!=(t=/\S/.test(t.string)?t:i.getTokenAt(h.Pos(e,t.end+1))).type||"import"!=t.string)return null;for(var n=e,o=Math.min(i.lastLine(),e+10);n<=o;++n){var r=i.getLine(n).indexOf(";");if(-1!=r)return{startCh:t.end,end:h.Pos(n,r)}}}var n,e=e.line,o=t(e);if(!o||t(e-1)||(n=t(e-2))&&n.end.line==e-1)return null;for(var r=o.end;;){var a=t(r.line+1);if(null==a)break;r=a.end}return{from:i.clipPos(h.Pos(e,o.startCh+1)),to:r}}),h.registerHelper("fold","include",function(n,e){function t(e){var t;return en.lastLine()?null:(t=n.getTokenAt(h.Pos(e,1)),"meta"==(t=/\S/.test(t.string)?t:n.getTokenAt(h.Pos(e,t.end+1))).type&&"#include"==t.string.slice(0,8)?t.start+8:void 0)}var e=e.line,o=t(e);if(null==o||null!=t(e-1))return null;for(var r=e;;){if(null==t(r+1))break;++r}return{from:h.Pos(e,o+1),to:n.clipPos(h.Pos(r))}})}),function(e){"object"==typeof exports&&"object"==typeof module?e(require("../../lib/codemirror")):"function"==typeof define&&define.amd?define(["../../lib/codemirror"],e):e(CodeMirror)}(function(e){"use strict";var s=e.Pos;function l(e,t){return e.line-t.line||e.ch-t.ch}var t="A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD",o=new RegExp("<(/?)(["+t+"][A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD-:.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040]*)","g");function c(e,t,n,o){this.line=t,this.ch=n,this.cm=e,this.text=e.getLine(t),this.min=o?Math.max(o.from,e.firstLine()):e.firstLine(),this.max=o?Math.min(o.to-1,e.lastLine()):e.lastLine()}function f(e,t){e=e.cm.getTokenTypeAt(s(e.line,t));return e&&/\btag\b/.test(e)}function r(e){return!(e.line>=e.max)&&(e.ch=0,e.text=e.cm.getLine(++e.line),1)}function u(e){return!(e.line<=e.min)&&(e.text=e.cm.getLine(--e.line),e.ch=e.text.length,1)}function d(e){for(;;){var t,n=e.text.indexOf(">",e.ch);if(-1==n){if(r(e))continue;return}if(f(e,n+1))return t=-1<(t=e.text.lastIndexOf("/",n))&&!/\S/.test(e.text.slice(t+1,n)),e.ch=n+1,t?"selfClose":"regular";e.ch=n+1}}function h(e){for(;;){var t=e.ch?e.text.lastIndexOf("<",e.ch-1):-1;if(-1==t){if(u(e))continue;return}if(f(e,t+1)){o.lastIndex=t,e.ch=t;var n=o.exec(e.text);if(n&&n.index==t)return n}else e.ch=t}}function p(e){for(;;){o.lastIndex=e.ch;var t=o.exec(e.text);if(!t){if(r(e))continue;return}if(f(e,t.index+1))return e.ch=t.index+t[0].length,t;e.ch=t.index+1}}function g(e,t){for(var n=[];;){var o,r=p(e),i=e.line,a=e.ch-(r?r[0].length:0);if(!r||!(o=d(e)))return;if("selfClose"!=o)if(r[1]){for(var l=n.length-1;0<=l;--l)if(n[l]==r[2]){n.length=l;break}if(l<0&&(!t||t==r[2]))return{tag:r[2],from:s(i,a),to:s(e.line,e.ch)}}else n.push(r[2])}}function m(e,t){for(var n=[];;){var o=function(e){for(;;){var t,n=e.ch?e.text.lastIndexOf(">",e.ch-1):-1;if(-1==n){if(u(e))continue;return}if(f(e,n+1))return t=-1<(t=e.text.lastIndexOf("/",n))&&!/\S/.test(e.text.slice(t+1,n)),e.ch=n+1,t?"selfClose":"regular";e.ch=n}}(e);if(!o)return;if("selfClose"==o)h(e);else{var o=e.line,r=e.ch,i=h(e);if(!i)return;if(i[1])n.push(i[2]);else{for(var a=n.length-1;0<=a;--a)if(n[a]==i[2]){n.length=a;break}if(a<0&&(!t||t==i[2]))return{tag:i[2],from:s(e.line,e.ch),to:s(o,r)}}}}}e.registerHelper("fold","xml",function(e,t){for(var n=new c(e,t.line,0);;){var o=p(n);if(!o||n.line!=t.line)return;var r=d(n);if(!r)return;if(!o[1]&&"selfClose"!=r)return r=s(n.line,n.ch),(o=g(n,o[2]))&&0")||-1!=o.text.indexOf("<")){var r=d(o),i=r&&s(o.line,o.ch),a=r&&h(o);if(r&&a&&!(0",triples:"",explode:"[]{}"},C=b.Pos;function x(e,t){return"pairs"==t&&"string"==typeof e?e:("object"==typeof e&&null!=e[t]?e:n)[t]}b.defineOption("autoCloseBrackets",!1,function(e,t,n){n&&n!=b.Init&&(e.removeKeyMap(r),e.state.closeBrackets=null),t&&(o(x(t,"pairs")),e.state.closeBrackets=t,e.addKeyMap(r))});var r={Backspace:function(e){var t=O(e);if(!t||e.getOption("disableInput"))return b.Pass;for(var n=x(t,"pairs"),o=e.listSelections(),r=0;r=e.options.minChars&&s(l,n,!1,e.options.style))}})}o.defineOption("highlightSelectionMatches",!1,function(e,t,n){n&&n!=o.Init&&(c(e),clearTimeout(e.state.matchHighlighter.timeout),e.state.matchHighlighter=null,e.off("cursorActivity",i),e.off("focus",a)),t&&(n=e.state.matchHighlighter=new r(t),e.hasFocus()?(n.active=!0,f(e)):e.on("focus",a),e.on("cursorActivity",i))})});
\ No newline at end of file
diff --git a/static/editor.md/lib/codemirror/codemirror.min.css b/static/editor.md/lib/codemirror/codemirror.min.css
index c2d0882c4..857d82815 100644
--- a/static/editor.md/lib/codemirror/codemirror.min.css
+++ b/static/editor.md/lib/codemirror/codemirror.min.css
@@ -1,3 +1,2 @@
-/* CodeMirror v5.0 | CodeMirror, copyright (c) by Marijn Haverbeke and others | Distributed under an MIT license: http://codemirror.net/LICENSE */
-.CodeMirror{font-family:monospace;height:300px;color:black}.CodeMirror-lines{padding:4px 0}.CodeMirror pre{padding:0 4px}.CodeMirror-scrollbar-filler,.CodeMirror-gutter-filler{background-color:white}.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;-moz-box-sizing:content-box;box-sizing:content-box}.CodeMirror-guttermarker{color:black}.CodeMirror-guttermarker-subtle{color:#999}.CodeMirror div.CodeMirror-cursor{border-left:1px solid black}.CodeMirror div.CodeMirror-secondarycursor{border-left:1px solid silver}.CodeMirror.cm-fat-cursor div.CodeMirror-cursor{width:auto;border:0;background:#7e7}.CodeMirror.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}@-moz-keyframes blink{0%{background:#7e7}50%{background:0}100%{background:#7e7}}@-webkit-keyframes blink{0%{background:#7e7}50%{background:0}100%{background:#7e7}}@keyframes blink{0%{background:#7e7}50%{background:0}100%{background:#7e7}}.cm-tab{display:inline-block;text-decoration:inherit}.CodeMirror-ruler{border-left:1px solid #ccc;position:absolute}.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-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-header{color:blue}.cm-s-default .cm-quote{color:#090}.cm-s-default .cm-hr{color:#999}.cm-s-default .cm-link{color:#00c}.cm-negative{color:#d44}.cm-positive{color:#292}.cm-header,.cm-strong{font-weight:bold}.cm-em{font-style:italic}.cm-link{text-decoration:underline}.cm-strikethrough{text-decoration:line-through}.cm-s-default .cm-error{color:#f00}.cm-invalidchar{color:#f00}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:white}.CodeMirror-scroll{overflow:scroll!important;margin-bottom:-30px;margin-right:-30px;padding-bottom:30px;height:100%;outline:0;position:relative;-moz-box-sizing:content-box;box-sizing:content-box}.CodeMirror-sizer{position:relative;border-right:30px solid transparent;-moz-box-sizing:content-box;box-sizing:content-box}.CodeMirror-vscrollbar,.CodeMirror-hscrollbar,.CodeMirror-scrollbar-filler,.CodeMirror-gutter-filler{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;z-index:3}.CodeMirror-gutter{white-space:normal;height:100%;-moz-box-sizing:content-box;box-sizing:content-box;display:inline-block;margin-bottom:-30px;*zoom:1;*display:inline}.CodeMirror-gutter-wrapper{position:absolute;z-index:4;height:100%}.CodeMirror-gutter-elt{position:absolute;cursor:default;z-index:4}.CodeMirror-gutter-wrapper{-webkit-user-select:none;-moz-user-select:none;user-select:none}.CodeMirror-lines{cursor:text;min-height:1px}.CodeMirror pre{-moz-border-radius:0;-webkit-border-radius:0;border-radius:0;border-width:0;background:transparent;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}.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-code{outline:0}.CodeMirror-measure{position:absolute;width:100%;height:0;overflow:hidden;visibility:hidden}.CodeMirror-measure pre{position:static}.CodeMirror div.CodeMirror-cursor{position:absolute;border-right:0;width:0}div.CodeMirror-cursors{visibility:hidden;position:relative;z-index:3}.CodeMirror-focused div.CodeMirror-cursors{visibility:visible}.CodeMirror-selected{background:#d9d9d9}.CodeMirror-focused .CodeMirror-selected{background:#d7d4f0}.CodeMirror-crosshair{cursor:crosshair}.CodeMirror ::selection{background:#d7d4f0}.CodeMirror ::-moz-selection{background:#d7d4f0}.cm-searching{background:#ffa;background:rgba(255,255,0,.4)}.CodeMirror span{*vertical-align:text-bottom}.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}
\ No newline at end of file
+/*! Editor.md v1.7.17 | codemirror.min.css | Open source online markdown editor. | MIT License | By: IBM Skills Network | https://github.com/ibm-skills-network/editor.md | 2026-03-31 */
+.CodeMirror{font-family:monospace;height:300px;color:#000;direction:ltr}.CodeMirror-lines{padding:4px 0}.CodeMirror pre.CodeMirror-line,.CodeMirror pre.CodeMirror-line-like{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-fat-cursor .CodeMirror-line::selection,.cm-fat-cursor .CodeMirror-line>span::selection,.cm-fat-cursor .CodeMirror-line>span>span::selection{background:0 0}.cm-fat-cursor .CodeMirror-line::-moz-selection,.cm-fat-cursor .CodeMirror-line>span::-moz-selection,.cm-fat-cursor .CodeMirror-line>span>span::-moz-selection{background:0 0}.cm-fat-cursor{caret-color:transparent}@-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:0;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:#0b0}div.CodeMirror span.CodeMirror-nonmatchingbracket{color:#a22}.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:-50px;margin-right:-50px;padding-bottom:50px;height:100%;outline:0;position:relative;z-index:0}.CodeMirror-sizer{position:relative;border-right:50px solid transparent}.CodeMirror-gutter-filler,.CodeMirror-hscrollbar,.CodeMirror-scrollbar-filler,.CodeMirror-vscrollbar{position:absolute;z-index:6;display:none;outline:0}.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:-50px}.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.CodeMirror-line,.CodeMirror pre.CodeMirror-line-like{-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.CodeMirror-line,.CodeMirror-wrap pre.CodeMirror-line-like{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;padding:.1px}.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-color:#ffa;background-color: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}
\ No newline at end of file
diff --git a/static/editor.md/lib/codemirror/codemirror.min.js b/static/editor.md/lib/codemirror/codemirror.min.js
index aa2eb131c..46f857f56 100644
--- a/static/editor.md/lib/codemirror/codemirror.min.js
+++ b/static/editor.md/lib/codemirror/codemirror.min.js
@@ -1,54 +1,2 @@
-/*!
-// CodeMirror v5.0, 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(mod){if(typeof exports=="object"&&typeof module=="object"){module.exports=mod()}else{if(typeof define=="function"&&define.amd){return define([],mod)}else{this.CodeMirror=mod()}}})(function(){var gecko=/gecko\/\d/i.test(navigator.userAgent);var ie_upto10=/MSIE \d/.test(navigator.userAgent);var ie_11up=/Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(navigator.userAgent);var ie=ie_upto10||ie_11up;var ie_version=ie&&(ie_upto10?document.documentMode||6:ie_11up[1]);var webkit=/WebKit\//.test(navigator.userAgent);var qtwebkit=webkit&&/Qt\/\d+\.\d+/.test(navigator.userAgent);var chrome=/Chrome\//.test(navigator.userAgent);var presto=/Opera\//.test(navigator.userAgent);var safari=/Apple Computer/.test(navigator.vendor);var mac_geMountainLion=/Mac OS X 1\d\D([8-9]|\d\d)\D/.test(navigator.userAgent);var phantom=/PhantomJS/.test(navigator.userAgent);var ios=/AppleWebKit/.test(navigator.userAgent)&&/Mobile\/\w+/.test(navigator.userAgent);var mobile=ios||/Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(navigator.userAgent);var mac=ios||/Mac/.test(navigator.platform);var windows=/win/i.test(navigator.platform);var presto_version=presto&&navigator.userAgent.match(/Version\/(\d*\.\d*)/);if(presto_version){presto_version=Number(presto_version[1])}if(presto_version&&presto_version>=15){presto=false;webkit=true}var flipCtrlCmd=mac&&(qtwebkit||presto&&(presto_version==null||presto_version<12.11));var captureRightClick=gecko||(ie&&ie_version>=9);var sawReadOnlySpans=false,sawCollapsedSpans=false;function CodeMirror(place,options){if(!(this instanceof CodeMirror)){return new CodeMirror(place,options)}this.options=options=options?copyObj(options):{};copyObj(defaults,options,false);setGuttersForLineNumbers(options);var doc=options.value;if(typeof doc=="string"){doc=new Doc(doc,options.mode)}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"}if(options.autofocus&&!mobile){display.input.focus()}initScrollbars(this);this.state={keyMaps:[],overlays:[],modeGen:0,overwrite:false,focused:false,suppressEdits:false,pasteIncoming:false,cutIncoming:false,draggingText:false,highlight:new Delayed(),keySeq:null};var cm=this;if(ie&&ie_version<11){setTimeout(function(){cm.display.input.reset(true)},20)}registerEventHandlers(this);ensureGlobalHandlers();startOperation(this);this.curOp.forceUpdate=true;attachDoc(this,doc);if((options.autofocus&&!mobile)||cm.hasFocus()){setTimeout(bind(onFocus,this),20)}else{onBlur(this)}for(var opt in optionHandlers){if(optionHandlers.hasOwnProperty(opt)){optionHandlers[opt](this,options[opt],Init)}}maybeUpdateLineNumberWidth(this);if(options.finishInit){options.finishInit(this)}for(var i=0;id.maxLineLength){d.maxLineLength=len;d.maxLine=line}})}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)}}}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}}function NativeScrollbars(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.checkedOverlay=false;if(ie&&ie_version<8){this.horiz.style.minHeight=this.vert.style.minWidth="18px"}}NativeScrollbars.prototype=copyObj({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);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=(measure.scrollWidth-measure.clientWidth+totalWidth)+"px"}else{this.horiz.style.display="";this.horiz.firstChild.style.width="0"}if(!this.checkedOverlay&&measure.clientHeight>0){if(sWidth==0){this.overlayHack()}this.checkedOverlay=true}return{right:needsV?sWidth:0,bottom:needsH?sWidth:0}},setScrollLeft:function(pos){if(this.horiz.scrollLeft!=pos){this.horiz.scrollLeft=pos}},setScrollTop:function(pos){if(this.vert.scrollTop!=pos){this.vert.scrollTop=pos}},overlayHack:function(){var w=mac&&!mac_geMountainLion?"12px":"18px";this.horiz.style.minHeight=this.vert.style.minWidth=w;var self=this;var barMouseDown=function(e){if(e_target(e)!=self.vert&&e_target(e)!=self.horiz){operation(self.cm,onMouseDown)(e)}};on(this.vert,"mousedown",barMouseDown);on(this.horiz,"mousedown",barMouseDown)},clear:function(){var parent=this.horiz.parentNode;parent.removeChild(this.horiz);parent.removeChild(this.vert)}},NativeScrollbars.prototype);function NullScrollbars(){}NullScrollbars.prototype=copyObj({update:function(){return{bottom:0,right:0}},setScrollLeft:function(){},setScrollTop:function(){},clear:function(){}},NullScrollbars.prototype);CodeMirror.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 CodeMirror.scrollbarModel[cm.options.scrollbarStyle](function(node){cm.display.wrapper.insertBefore(node,cm.display.scrollbarFiller);on(node,"mousedown",function(){if(cm.state.focused){setTimeout(function(){cm.display.input.focus()},0)}});node.setAttribute("cm-not-content","true")},function(pos,axis){if(axis=="horizontal"){setScrollLeft(cm,pos)}else{setScrollTop(cm,pos)}},cm);if(cm.display.scrollbars.addClass){addClass(cm.display.wrapper,cm.display.scrollbars.addClass)}}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}}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";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=""}}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);if(viewport&&viewport.ensure){var ensureFrom=viewport.ensure.from.line,ensureTo=viewport.ensure.to.line;if(ensureFrom=to){from=lineAtHeight(doc,heightAtLine(getLine(doc,ensureTo))-display.wrapper.clientHeight);to=ensureTo}}}return{from:from,to:Math.max(to,from+1)}}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=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)}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.viewFromto&&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));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}var focused=activeElt();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;if(focused&&activeElt()!=focused&&focused.offsetHeight){focused.focus()}removeChildren(display.cursorDiv);removeChildren(display.selectionDiv);display.gutters.style.height=0;if(different){display.lastWrapHeight=update.wrapperHeight;display.lastWrapWidth=update.wrapperWidth;startWorker(cm,400)}display.updateLineNumbers=null;return true}function postUpdateDisplay(cm,update){var force=update.force,viewport=update.viewport;for(var first=true;;first=false){if(first&&cm.options.lineWrapping&&update.oldDisplayWidth!=displayWidth(cm)){force=true}else{force=false;if(viewport&&viewport.top!=null){viewport={top:Math.min(cm.doc.height+paddingVert(cm.display)-displayHeight(cm),viewport.top)}}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);setDocumentHeight(cm,barMeasure);updateScrollbars(cm,barMeasure)}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);setDocumentHeight(cm,barMeasure);updateScrollbars(cm,barMeasure);update.finish()}}function setDocumentHeight(cm,measure){cm.display.sizer.style.minHeight=measure.docHeight+"px";var total=measure.docHeight+cm.display.barHeight;
-cm.display.heightForcer.style.top=total+"px";cm.display.gutters.style.height=Math.max(total+scrollGap(cm),measure.clientHeight)+"px"}function updateHeightsInViewport(cm){var display=cm.display;var prevBottom=display.lineDiv.offsetTop;for(var i=0;i0.001||diff<-0.001){updateLineHeight(cur.line,height);updateWidgetHeight(cur.line);if(cur.rest){for(var j=0;j-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 updateLineForChanges(cm,lineView,lineN,dims){for(var j=0;j1){if(lastCopied&&lastCopied.join("\n")==inserted){multiPaste=sel.ranges.length%lastCopied.length==0&&map(lastCopied,splitLines)}else{if(textLines.length==sel.ranges.length){multiPaste=map(textLines,function(l){return[l]})}}}for(var i=sel.ranges.length-1;i>=0;i--){var range=sel.ranges[i];var from=range.from(),to=range.to();if(range.empty()){if(deleted&&deleted>0){from=Pos(from.line,from.ch-deleted)}else{if(cm.state.overwrite&&!cm.state.pasteIncoming){to=Pos(to.line,Math.min(getLine(doc,to.line).text.length,to.ch+lst(textLines).length))}}}var updateInput=cm.curOp.updateInput;var changeEvent={from:from,to:to,text:multiPaste?multiPaste[i%multiPaste.length]:textLines,origin:cm.state.pasteIncoming?"paste":cm.state.cutIncoming?"cut":"+input"};makeChange(cm.doc,changeEvent);signalLater(cm,"inputRead",cm,changeEvent);if(inserted&&!cm.state.pasteIncoming&&cm.options.electricChars&&cm.options.smartIndent&&range.head.ch<100&&(!i||sel.ranges[i-1].head.line!=range.head.line)){var mode=cm.getModeAt(range.head);var end=changeEnd(changeEvent);if(mode.electricChars){for(var j=0;j-1){indentLine(cm,end.line,"smart");break}}}else{if(mode.electricInput){if(mode.electricInput.test(getLine(doc,end.line).text.slice(0,end.ch))){indentLine(cm,end.line,"smart")}}}}}ensureCursorVisible(cm);cm.curOp.updateInput=updateInput;cm.curOp.typing=true;cm.state.pasteIncoming=cm.state.cutIncoming=false}function copyableRanges(cm){var text=[],ranges=[];for(var i=0;i=9&&input.hasSelection){input.hasSelection=null}input.poll()});on(te,"paste",function(){if(webkit&&!cm.state.fakedLastChar&&!(new Date-cm.state.lastMiddleDown<200)){var start=te.selectionStart,end=te.selectionEnd;te.value+="$";te.selectionEnd=end;te.selectionStart=start;cm.state.fakedLastChar=true}cm.state.pasteIncoming=true;input.fastPoll()});function prepareCopyCut(e){if(cm.somethingSelected()){lastCopied=cm.getSelections();if(input.inaccurateSelection){input.prevInput="";input.inaccurateSelection=false;te.value=lastCopied.join("\n");selectInput(te)}}else{var ranges=copyableRanges(cm);lastCopied=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)){return}cm.state.pasteIncoming=true;input.focus()});on(display.lineSpace,"selectstart",function(e){if(!eventInWidget(display,e)){e_preventDefault(e)}})},prepareSelection:function(){var cm=this.cm,display=cm.display,doc=cm.doc;var result=prepareSelection(cm);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},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:function(typing){if(this.contextMenuPending){return}var minimal,selected,cm=this.cm,doc=cm.doc;if(cm.somethingSelected()){this.prevInput="";var range=doc.sel.primary();minimal=hasCopyEvent&&(range.to().line-range.from().line>100||(selected=cm.getSelection()).length>1000);var content=minimal?"-":selected||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}}}this.inaccurateSelection=minimal},getField:function(){return this.textarea},supportsTouch:function(){return false},focus:function(){if(this.cm.options.readOnly!="nocursor"&&(!mobile||activeElt()!=this.textarea)){try{this.textarea.focus()}catch(e){}}},blur:function(){this.textarea.blur()},resetPosition:function(){this.wrapper.style.top=this.wrapper.style.left=0},receivedFocus:function(){this.slowPoll()},slowPoll:function(){var input=this;if(input.pollingFast){return}input.polling.set(this.cm.options.pollInterval,function(){input.poll();if(input.cm.state.focused){input.slowPoll()}})},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)},poll:function(){var cm=this.cm,input=this.textarea,prevInput=this.prevInput;if(!cm.state.focused||(hasSelection(input)&&!prevInput)||isReadOnly(cm)||cm.options.disableInput||cm.state.keySeq){return false}if(cm.state.pasteIncoming&&cm.state.fakedLastChar){input.value=input.value.substring(0,input.value.length-1);cm.state.fakedLastChar=false}var text=input.value;if(text==prevInput&&!cm.somethingSelected()){return false}if(ie&&ie_version>=9&&this.hasSelection===text||mac&&/[\uf700-\uf7ff]/.test(text)){cm.display.input.reset();return false}if(text.charCodeAt(0)==8203&&cm.doc.sel==cm.display.selForContextMenu&&!prevInput){prevInput="\u200b"
-}var same=0,l=Math.min(prevInput.length,text.length);while(same1000||text.indexOf("\n")>-1){input.value=self.prevInput=""}else{self.prevInput=text}});return true},ensurePolled:function(){if(this.pollingFast&&this.poll()){this.pollingFast=false}},onKeyPress:function(){if(ie&&ie_version>=9){this.hasSelection=null}this.fastPoll()},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}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;input.wrapper.style.position="absolute";te.style.cssText="position: fixed; width: 30px; height: 30px; top: "+(e.clientY-5)+"px; left: "+(e.clientX-5)+"px; z-index: 1000; background: "+(ie?"rgba(255, 255, 255, .05)":"transparent")+"; outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);";if(webkit){var oldScrollY=window.scrollY}display.input.focus();if(webkit){window.scrollTo(null,oldScrollY)}display.input.reset();if(!cm.somethingSelected()){te.value=input.prevInput=" "}input.contextMenuPending=true;display.selForContextMenu=cm.doc.sel;clearTimeout(display.detectingSelectAll);function prepareSelectAllHack(){if(te.selectionStart!=null){var selected=cm.somethingSelected();var extval=te.value="\u200b"+(selected?te.value:"");input.prevInput=selected?"":"\u200b";te.selectionStart=1;te.selectionEnd=extval.length;display.selForContextMenu=cm.doc.sel}}function rehide(){input.contextMenuPending=false;input.wrapper.style.position="relative";te.style.cssText=oldCSS;if(ie&&ie_version<9){display.scrollbars.setScrollTop(display.scroller.scrollTop=scrollPos)}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){operation(cm,commands.selectAll)(cm)}else{if(i++<10){display.detectingSelectAll=setTimeout(poll,500)}else{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)}},setUneditable:nothing,needsContentAttribute:false},TextareaInput.prototype);function ContentEditableInput(cm){this.cm=cm;this.lastAnchorNode=this.lastAnchorOffset=this.lastFocusNode=this.lastFocusOffset=null;this.polling=new Delayed()}ContentEditableInput.prototype=copyObj({init:function(display){var input=this,cm=input.cm;var div=input.div=display.lineDiv;div.contentEditable="true";disableBrowserMagic(div);on(div,"paste",function(e){var pasted=e.clipboardData&&e.clipboardData.getData("text/plain");if(pasted){e.preventDefault();cm.replaceSelection(pasted,null,"paste")}});on(div,"compositionstart",function(e){var data=e.data;input.composing={sel:cm.doc.sel,data:data,startData:data};if(!data){return}var prim=cm.doc.sel.primary();var line=cm.getLine(prim.head.line);var found=line.indexOf(data,Math.max(0,prim.head.ch-data.length));if(found>-1&&found<=prim.head.ch){input.composing.sel=simpleSelection(Pos(prim.head.line,found),Pos(prim.head.line,found+data.length))}});on(div,"compositionupdate",function(e){input.composing.data=e.data});on(div,"compositionend",function(e){var ours=input.composing;if(!ours){return}if(e.data!=ours.startData&&!/\u200b/.test(e.data)){ours.data=e.data}setTimeout(function(){if(!ours.handled){input.applyComposition(ours)}if(input.composing==ours){input.composing=null}},50)});on(div,"touchstart",function(){input.forceCompositionEnd()});on(div,"input",function(){if(input.composing){return}if(!input.pollContent()){runInOp(input.cm,function(){regChange(cm)})}});function onCopyCut(e){if(cm.somethingSelected()){lastCopied=cm.getSelections();if(e.type=="cut"){cm.replaceSelection("",null,"cut")}}else{var ranges=copyableRanges(cm);lastCopied=ranges.text;if(e.type=="cut"){cm.operation(function(){cm.setSelections(ranges.ranges,0,sel_dontScroll);cm.replaceSelection("",null,"cut")})}}if(e.clipboardData&&!ios){e.preventDefault();e.clipboardData.clearData();e.clipboardData.setData("text/plain",lastCopied.join("\n"))}else{var kludge=hiddenTextarea(),te=kludge.firstChild;cm.display.lineSpace.insertBefore(kludge,cm.display.lineSpace.firstChild);te.value=lastCopied.join("\n");var hadFocus=document.activeElement;selectInput(te);setTimeout(function(){cm.display.lineSpace.removeChild(kludge);hadFocus.focus()},50)}}on(div,"copy",onCopyCut);on(div,"cut",onCopyCut)},prepareSelection:function(){var result=prepareSelection(this.cm,false);result.focus=this.cm.state.focused;return result},showSelection:function(info){if(!info||!this.cm.display.view.length){return
-}if(info.focus){this.showPrimarySelection()}this.showMultipleSelections(info)},showPrimarySelection:function(){var sel=window.getSelection(),prim=this.cm.doc.sel.primary();var curAnchor=domToPos(this.cm,sel.anchorNode,sel.anchorOffset);var curFocus=domToPos(this.cm,sel.focusNode,sel.focusOffset);if(curAnchor&&!curAnchor.bad&&curFocus&&!curFocus.bad&&cmp(minPos(curAnchor,curFocus),prim.from())==0&&cmp(maxPos(curAnchor,curFocus),prim.to())==0){return}var start=posToDOM(this.cm,prim.from());var end=posToDOM(this.cm,prim.to());if(!start&&!end){return}var view=this.cm.display.view;var old=sel.rangeCount&&sel.getRangeAt(0);if(!start){start={node:view[0].measure.map[2],offset:0}}else{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]}}}try{var rng=range(start.node,start.offset,end.offset,end.node)}catch(e){}if(rng){sel.removeAllRanges();sel.addRange(rng);if(old&&sel.anchorNode==null){sel.addRange(old)}}this.rememberSelection()},showMultipleSelections:function(info){removeChildrenAndAdd(this.cm.display.cursorDiv,info.cursors);removeChildrenAndAdd(this.cm.display.selectionDiv,info.selection)},rememberSelection:function(){var sel=window.getSelection();this.lastAnchorNode=sel.anchorNode;this.lastAnchorOffset=sel.anchorOffset;this.lastFocusNode=sel.focusNode;this.lastFocusOffset=sel.focusOffset},selectionInEditor:function(){var sel=window.getSelection();if(!sel.rangeCount){return false}var node=sel.getRangeAt(0).commonAncestorContainer;return contains(this.div,node)},focus:function(){if(this.cm.options.readOnly!="nocursor"){this.div.focus()}},blur:function(){this.div.blur()},getField:function(){return this.div},supportsTouch:function(){return true},receivedFocus:function(){var input=this;if(this.selectionInEditor()){this.pollSelection()}else{runInOp(this.cm,function(){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)},pollSelection:function(){if(this.composing){return}var sel=window.getSelection(),cm=this.cm;if(sel.anchorNode!=this.lastAnchorNode||sel.anchorOffset!=this.lastAnchorOffset||sel.focusNode!=this.lastFocusNode||sel.focusOffset!=this.lastFocusOffset){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}})}}},pollContent:function(){var cm=this.cm,display=cm.display,sel=cm.doc.sel.primary();var from=sel.from(),to=sel.to();if(from.linedisplay.viewTo-1){return false}var fromIndex;if(from.line==display.viewFrom||(fromIndex=findViewIndex(cm,from.line))==0){var fromLine=lineNo(display.view[0].line);var fromNode=display.view[0].node}else{var fromLine=lineNo(display.view[fromIndex].line);var fromNode=display.view[fromIndex-1].node.nextSibling}var toIndex=findViewIndex(cm,to.line);if(toIndex==display.view.length-1){var toLine=display.viewTo-1;var toNode=display.view[toIndex].node}else{var toLine=lineNo(display.view[toIndex+1].line)-1;var toNode=display.view[toIndex+1].node.previousSibling}var newText=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(cutFront1||newText[0]||cmp(chFrom,chTo)){replaceRange(cm.doc,newText,chFrom,chTo,"+input");return true}},ensurePolled:function(){this.forceCompositionEnd()},reset:function(){this.forceCompositionEnd()},forceCompositionEnd:function(){if(!this.composing||this.composing.handled){return}this.applyComposition(this.composing);this.composing.handled=true;this.div.blur();this.div.focus()},applyComposition:function(composing){if(composing.data&&composing.data!=composing.startData){operation(this.cm,applyTextInput)(this.cm,composing.data,0,composing.sel)
-}},setUneditable:function(node){node.setAttribute("contenteditable","false")},onKeyPress:function(e){e.preventDefault();operation(this.cm,applyTextInput)(this.cm,String.fromCharCode(e.charCode==null?e.keyCode:e.charCode),0)},onContextMenu:nothing,resetPosition:nothing,needsContentAttribute:true},ContentEditableInput.prototype);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),side="left";if(order){var partPos=getBidiPartAt(order,pos.ch);side=partPos%2?"right":"left"}var result=nodeAndOffsetInLineMap(info.map,pos.ch,"left");result.offset=result.collapse=="right"?result.end:result.start;return result}function badPos(pos,bad){if(bad){pos.bad=true}return pos}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=0&&cmp(pos,range.to())<=0){return i}}return -1}};function Range(anchor,head){this.anchor=anchor;this.head=head
-}Range.prototype={from:function(){return minPos(this.anchor,this.head)},to:function(){return maxPos(this.anchor,this.head)},empty:function(){return this.head.line==this.anchor.line&&this.head.ch==this.anchor.ch}};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=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)}function clipLine(doc,n){return Math.max(doc.first,Math.min(n,doc.first+doc.size-1))}function clipPos(doc,pos){if(pos.linelast){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 isLine(doc,l){return l>=doc.first&&l=curPos.ch:sp.to>curPos.ch))){if(mayClear){signal(m,"beforeCursorEnter");if(m.explicitlyCleared){if(!line.markedSpans){break}else{--i;continue}}}if(!m.atomic){continue}var newPos=m.find(dir<0?-1:1);if(cmp(newPos,curPos)==0){newPos.ch+=dir;
-if(newPos.ch<0){if(newPos.line>doc.first){newPos=clipPos(doc,Pos(newPos.line-1))}else{newPos=null}}else{if(newPos.ch>line.text.length){if(newPos.line3){add(left,leftPos.top,null,leftPos.bottom);left=leftSide;if(leftPos.bottomend.bottom||rightPos.bottom==end.bottom&&rightPos.right>end.right){end=rightPos}if(left0){display.blinker=setInterval(function(){display.cursorDiv.style.visibility=(on=!on)?"":"hidden"},cm.options.cursorBlinkRate)}else{if(cm.options.cursorBlinkRate<0){display.cursorDiv.style.visibility="hidden"}}}function startWorker(cm,time){if(cm.doc.mode.startState&&cm.doc.frontier=cm.display.viewTo){return}var end=+new Date+cm.options.workTime;var state=copyState(doc.mode,getStateBefore(cm,doc.frontier));var changedLines=[];doc.iter(doc.frontier,Math.min(doc.first+doc.size,cm.display.viewTo+500),function(line){if(doc.frontier>=cm.display.viewFrom){var oldStyles=line.styles;
-var highlighted=highlightLine(cm,line,state,true);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&&iend){startWorker(cm,cm.options.workDelay);return true}});if(changedLines.length){runInOp(cm,function(){for(var i=0;ilim;--search){if(search<=doc.first){return doc.first}var line=getLine(doc,search-1);if(line.stateAfter&&(!precise||search<=doc.frontier)){return search}var indented=countColumn(line.text,null,cm.options.tabSize);if(minline==null||minindent>indented){minline=search-1;minindent=indented}}return minline}function getStateBefore(cm,n,precise){var doc=cm.doc,display=cm.display;if(!doc.mode.startState){return true}var pos=findStartLine(cm,n,precise),state=pos>doc.first&&getLine(doc,pos-1).stateAfter;if(!state){state=startState(doc.mode)}else{state=copyState(doc.mode,state)}doc.iter(pos,n,function(line){processLine(cm,line.text,state);var save=pos==n-1||pos%5==0||pos>=display.viewFrom&&pos2){heights.push((cur.bottom+next.top)/2-rect.top)}}}heights.push(rect.bottom-rect.top)}}function mapFromLineView(lineView,line,lineN){if(lineView.line==line){return{map:lineView.measure.map,cache:lineView.measure.cache}}for(var i=0;ilineN){return{map:lineView.measure.maps[i],cache:lineView.measure.caches[i],before:true}}}}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}function measureChar(cm,line,ch,bias){return measureCharPrepared(cm,prepareMeasureForLine(cm,line),ch,bias)}function findViewForLine(cm,lineN){if(lineN>=cm.display.viewFrom&&lineN=ext.lineN&&lineNch){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(i0){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;for(var i=0;ipart.from){return get(ch-1)}return get(ch,right)}var order=getOrder(lineObj),ch=pos.ch;if(!order){return get(ch)}var partPos=getBidiPartAt(order,ch);var val=getBidi(ch,partPos);if(bidiOther!=null){val.other=getBidi(ch,bidiOther)}return val}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}}function PosWithInfo(line,ch,outside,xRel){var pos=Pos(line,ch);pos.xRel=xRel;if(outside){pos.outside=true}return pos}function coordsChar(cm,x,y){var doc=cm.doc;y+=cm.display.viewOffset;if(y<0){return PosWithInfo(doc.first,0,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,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 coordsCharInner(cm,lineObj,lineNo,x,y){var innerOff=y-heightAtLine(lineObj);var wrongLine=false,adjust=2*cm.display.wrapper.clientWidth;var preparedMeasure=prepareMeasureForLine(cm,lineObj);function getX(ch){var sp=cursorCoords(cm,Pos(lineNo,ch),"line",lineObj,preparedMeasure);wrongLine=true;if(innerOff>sp.bottom){return sp.left-adjust}else{if(innerOfftoX){return PosWithInfo(lineNo,to,toOutside,1)}for(;;){if(bidi?to==from||to==moveVisually(lineObj,from,1):to-from<=1){var ch=x1?1:0);return pos}var step=Math.ceil(dist/2),middle=from+step;if(bidi){middle=from;for(var i=0;ix){to=middle;toX=middleX;if(toOutside=wrongLine){toX+=1000}dist=step}else{from=middle;fromX=middleX;fromOutside=wrongLine;dist-=step}}}var measureText;function textHeight(display){if(display.cachedTextHeight!=null){return display.cachedTextHeight}if(measureText==null){measureText=elt("pre");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}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}var operationGroup=null;var nextOpId=0;function startOperation(cm){cm.curOp={cm:cm,viewChanged:false,startHeight:cm.doc.height,forceUpdate:false,updateInput:null,typing:false,changeObjs:null,cursorActivityHandlers:null,cursorActivityCalled:0,selectionChanged:false,updateMaxLine:false,scrollLeft:null,scrollTop:null,scrollToPos:null,id:++nextOpId};if(operationGroup){operationGroup.ops.push(cm.curOp)}else{cm.curOp.ownsGroup=operationGroup={ops:[cm.curOp],delayedCallbacks:[]}}}function fireCallbacksForOps(group){var callbacks=group.delayedCallbacks,i=0;
-do{for(;i=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(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()}}function endOperation_W2(op){var cm=op.cm;if(op.adjustWidthTo!=null){cm.display.sizer.style.minWidth=op.adjustWidthTo+"px";if(op.maxScrollLeftfrom)){display.updateLineNumbers=from}cm.curOp.viewChanged=true;if(from>=display.viewTo){if(sawCollapsedSpans&&visualLineNo(cm.doc,from)display.viewFrom){resetView(cm)}else{display.viewFrom+=lendiff;display.viewTo+=lendiff}}else{if(from<=display.viewFrom&&to>=display.viewTo){resetView(cm)}else{if(from<=display.viewFrom){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){var cut=viewCuttingPoint(cm,from,from,-1);if(cut){display.view=display.view.slice(0,cut.index);display.viewTo=cut.lineN}else{resetView(cm)}}else{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&&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)}}function resetView(cm){cm.display.viewFrom=cm.display.viewTo=cm.doc.first;cm.display.view=[];cm.display.viewOffset=0}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;i0){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}}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.viewFromto){display.view=display.view.slice(0,findViewIndex(cm,to))}}}display.viewTo=to}function countDirtyView(cm){var view=cm.display.view,dirty=0;for(var i=0;i20*20}on(d.scroller,"touchstart",function(e){if(!isMouseLikeTouchEvent(e)){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)){range=new Range(pos,pos)}else{if(!touch.prev.prev||farAway(touch,touch.prev.prev)){range=cm.findWordAt(pos)}else{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);on(d.scroller,"scroll",function(){if(d.scroller.clientHeight){setScrollTop(cm,d.scroller.scrollTop);setScrollLeft(cm,d.scroller.scrollLeft,true);signal(cm,"scroll",cm)}});on(d.scroller,"mousewheel",function(e){onScrollWheel(cm,e)});on(d.scroller,"DOMMouseScroll",function(e){onScrollWheel(cm,e)});on(d.wrapper,"scroll",function(){d.wrapper.scrollTop=d.wrapper.scrollLeft=0});function drag_(e){if(!signalDOMEvent(cm,e)){e_stop(e)}}if(cm.options.dragDrop){on(d.scroller,"dragstart",function(e){onDragStart(cm,e)});on(d.scroller,"dragenter",drag_);on(d.scroller,"dragover",drag_);on(d.scroller,"drop",operation(cm,onDrop))}var inp=d.input.getField();on(inp,"keyup",function(e){onKeyUp.call(cm,e)});on(inp,"keydown",operation(cm,onKeyDown));on(inp,"keypress",operation(cm,onKeyPress));on(inp,"focus",bind(onFocus,cm));on(inp,"blur",bind(onBlur,cm))}function onResize(cm){var d=cm.display;if(d.lastWrapHeight==d.wrapper.clientHeight&&d.lastWrapWidth==d.wrapper.clientWidth){return}d.cachedCharWidth=d.cachedTextHeight=d.cachedPaddingH=null;d.scrollbarsClipped=false;cm.setSize()}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}}}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();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}function onMouseDown(e){var cm=this,display=cm.display;if(display.activeTouch&&display.input.supportsTouch()||signalDOMEvent(cm,e)){return}display.shift=e.shiftKey;if(eventInWidget(display,e)){if(!webkit){display.scroller.draggable=false;setTimeout(function(){display.scroller.draggable=true},100)}return}if(clickInGutter(cm,e)){return}var start=posFromMouse(cm,e);window.focus();switch(e_button(e)){case 1:if(start){leftButtonDown(cm,e,start)}else{if(e_target(e)==display.scroller){e_preventDefault(e)}}break;case 2:if(webkit){cm.state.lastMiddleDown=+new Date}if(start){extendSelection(cm.doc,start)}setTimeout(function(){display.input.focus()},20);e_preventDefault(e);break;case 3:if(captureRightClick){onContextMenu(cm,e)}break}}var lastClick,lastDoubleClick;function leftButtonDown(cm,e,start){if(ie){setTimeout(bind(ensureFocus,cm),0)}else{ensureFocus(cm)}var now=+new Date,type;if(lastDoubleClick&&lastDoubleClick.time>now-400&&cmp(lastDoubleClick.pos,start)==0){type="triple"}else{if(lastClick&&lastClick.time>now-400&&cmp(lastClick.pos,start)==0){type="double";lastDoubleClick={time:now,pos:start}}else{type="single";lastClick={time:now,pos:start}}}var sel=cm.doc.sel,modifier=mac?e.metaKey:e.ctrlKey,contained;if(cm.options.dragDrop&&dragAndDrop&&!isReadOnly(cm)&&type=="single"&&(contained=sel.contains(start))>-1&&!sel.ranges[contained].empty()){leftButtonStartDrag(cm,e,start,modifier)}else{leftButtonSelect(cm,e,start,type,modifier)}}function leftButtonStartDrag(cm,e,start,modifier){var display=cm.display;var dragEnd=operation(cm,function(e2){if(webkit){display.scroller.draggable=false}cm.state.draggingText=false;off(document,"mouseup",dragEnd);off(display.scroller,"drop",dragEnd);if(Math.abs(e.clientX-e2.clientX)+Math.abs(e.clientY-e2.clientY)<10){e_preventDefault(e2);if(!modifier){extendSelection(cm.doc,start)}display.input.focus();if(ie&&ie_version==9){setTimeout(function(){document.body.focus();display.input.focus()},20)}}});if(webkit){display.scroller.draggable=true}cm.state.draggingText=dragEnd;if(display.scroller.dragDrop){display.scroller.dragDrop()}on(document,"mouseup",dragEnd);on(display.scroller,"drop",dragEnd)}function leftButtonSelect(cm,e,start,type,addNew){var display=cm.display,doc=cm.doc;e_preventDefault(e);var ourRange,ourIndex,startSel=doc.sel,ranges=startSel.ranges;if(addNew&&!e.shiftKey){ourIndex=doc.sel.contains(start);if(ourIndex>-1){ourRange=ranges[ourIndex]}else{ourRange=new Range(start,start)
-}}else{ourRange=doc.sel.primary()}if(e.altKey){type="rect";if(!addNew){ourRange=new Range(start,start)}start=posFromMouse(cm,e,true,true);ourIndex=-1}else{if(type=="double"){var word=cm.findWordAt(start);if(cm.display.shift||doc.extend){ourRange=extendRange(doc,ourRange,word.anchor,word.head)}else{ourRange=word}}else{if(type=="triple"){var line=new Range(Pos(start.line,0),clipPos(doc,Pos(start.line+1,0)));if(cm.display.shift||doc.extend){ourRange=extendRange(doc,ourRange,line.anchor,line.head)}else{ourRange=line}}else{ourRange=extendRange(doc,ourRange,start)}}}if(!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()&&type=="single"){setSelection(doc,normalizeSelection(ranges.slice(0,ourIndex).concat(ranges.slice(ourIndex+1)),0));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(type=="rect"){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 anchor=oldRange.anchor,head=pos;if(type!="single"){if(type=="double"){var range=cm.findWordAt(pos)}else{var range=new Range(Pos(pos.line,0),clipPos(doc,Pos(pos.line+1,0)))}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=startSel.ranges.slice(0);ranges[ourIndex]=new Range(clipPos(doc,anchor),head);setSelection(doc,normalizeSelection(ranges,ourIndex),sel_mouse)}}var editorSize=display.wrapper.getBoundingClientRect();var counter=0;function extend(e){var curCount=++counter;var cur=posFromMouse(cm,e,true,type=="rect");if(!cur){return}if(cmp(cur,lastPos)!=0){ensureFocus(cm);extendTo(cur);var visible=visibleLines(display,doc);if(cur.line>=visible.to||cur.lineeditorSize.bottom?20:0;if(outside){setTimeout(operation(cm,function(){if(counter!=curCount){return}display.scroller.scrollTop+=outside;extend(e)}),50)}}}function done(e){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);on(document,"mousemove",move);on(document,"mouseup",up)}function gutterEvent(cm,e,type,prevent,signalfn){try{var 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=mX){var line=lineAtHeight(cm.doc,mY);var gutter=cm.options.gutters[i];signalfn(cm,type,cm,line,gutter,e);return e_defaultPrevented(e)}}}function clickInGutter(cm,e){return gutterEvent(cm,e,"gutterClick",true,signalLater)}var lastDrop=0;function onDrop(e){var cm=this;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||isReadOnly(cm)){return}if(files&&files.length&&window.FileReader&&window.File){var n=files.length,text=Array(n),read=0;var loadFile=function(file,i){var reader=new FileReader;reader.onload=operation(cm,function(){text[i]=reader.result;if(++read==n){pos=clipPos(cm.doc,pos);var change={from:pos,to:pos,text:splitLines(text.join("\n")),origin:"paste"};makeChange(cm.doc,change);setSelectionReplaceHistory(cm.doc,simpleSelection(pos,changeEnd(change)))}});reader.readAsText(file)};for(var i=0;i-1){cm.state.draggingText(e);setTimeout(function(){cm.display.input.focus()
-},20);return}try{var text=e.dataTransfer.getData("Text");if(text){if(cm.state.draggingText&&!(mac?e.metaKey:e.ctrlKey)){var selected=cm.listSelections()}setSelectionNoUndo(cm.doc,simpleSelection(pos,pos));if(selected){for(var i=0;iscroll.clientWidth||dy&&scroll.scrollHeight>scroll.clientHeight)){return}if(dy&&mac&&webkit){outer:for(var cur=e.target,view=display.view;cur!=scroll;cur=cur.parentNode){for(var i=0;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))})}function makeChangeFromHistory(doc,type,allowSelectionOnly){if(doc.cm&&doc.cm.state.suppressEdits){return}var hist=doc.history,event,selAfter=doc.sel;var source=type=="undo"?hist.done:hist.undone,dest=type=="undo"?hist.undone:hist.done;for(var i=0;i=0;--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=[];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))})}}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;ldoc.lastLine()){return}if(change.from.linelast){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)}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}}doc.frontier=Math.min(doc.frontier,from.line);startWorker(cm,400);var lendiff=change.text.length-(to.line-from.line)-1;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=splitLines(code)}makeChange(doc,{from:from,to:to,text:code,origin:origin})}function maybeScrollWindow(cm,coords){if(signalDOMEvent(cm,"scrollCursorIntoView")){return}var display=cm.display,box=display.sizer.getBoundingClientRect(),doScroll=null;if(coords.top+box.top<0){doScroll=true}else{if(coords.bottom+box.top>(window.innerHeight||document.documentElement.clientHeight)){doScroll=false}}if(doScroll!=null&&!phantom){var scrollNode=elt("div","\u200b",null,"position: absolute; top: "+(coords.top-display.viewOffset-paddingTop(cm.display))+"px; height: "+(coords.bottom-coords.top+scrollGap(cm)+display.barHeight)+"px; left: "+coords.left+"px; width: 2px;");cm.display.lineSpace.appendChild(scrollNode);scrollNode.scrollIntoView(doScroll);cm.display.lineSpace.removeChild(scrollNode)}}function scrollPosIntoView(cm,pos,end,margin){if(margin==null){margin=0}for(var limit=0;limit<5;limit++){var changed=false,coords=cursorCoords(cm,pos);var endCoords=!end||end==pos?coords:cursorCoords(cm,end);var scrollPos=calculateScrollPos(cm,Math.min(coords.left,endCoords.left),Math.min(coords.top,endCoords.top)-margin,Math.max(coords.left,endCoords.left),Math.max(coords.bottom,endCoords.bottom)+margin);var startTop=cm.doc.scrollTop,startLeft=cm.doc.scrollLeft;if(scrollPos.scrollTop!=null){setScrollTop(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 coords}function scrollIntoView(cm,x1,y1,x2,y2){var scrollPos=calculateScrollPos(cm,x1,y1,x2,y2);if(scrollPos.scrollTop!=null){setScrollTop(cm,scrollPos.scrollTop)}if(scrollPos.scrollLeft!=null){setScrollLeft(cm,scrollPos.scrollLeft)}}function calculateScrollPos(cm,x1,y1,x2,y2){var display=cm.display,snapMargin=textHeight(cm.display);if(y1<0){y1=0}var screentop=cm.curOp&&cm.curOp.scrollTop!=null?cm.curOp.scrollTop:display.scroller.scrollTop;var screen=displayHeight(cm),result={};if(y2-y1>screen){y2=y1+screen}var docBottom=cm.doc.height+paddingVert(display);var atTop=y1docBottom-snapMargin;if(y1screentop+screen){var newTop=Math.min(y1,(atBottom?docBottom:y2)-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=x2-x1>screenw;if(tooWide){x2=x1+screenw}if(x1<10){result.scrollLeft=0}else{if(x1screenw+screenleft-3){result.scrollLeft=x2+(tooWide?0:10)-screenw}}}return result}function addToScrollPos(cm,left,top){if(left!=null||top!=null){resolveScrollToPos(cm)}if(left!=null){cm.curOp.scrollLeft=(cm.curOp.scrollLeft==null?cm.doc.scrollLeft:cm.curOp.scrollLeft)+left}if(top!=null){cm.curOp.scrollTop=(cm.curOp.scrollTop==null?cm.doc.scrollTop:cm.curOp.scrollTop)+top}}function ensureCursorVisible(cm){resolveScrollToPos(cm);var cur=cm.getCursor(),from=cur,to=cur;if(!cm.options.lineWrapping){from=cur.ch?Pos(cur.line,cur.ch-1):cur;to=Pos(cur.line,cur.ch+1)}cm.curOp.scrollToPos={from:from,to:to,margin:cm.options.cursorScrollMargin,isCursor:true}}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);var sPos=calculateScrollPos(cm,Math.min(from.left,to.left),Math.min(from.top,to.top)-range.margin,Math.max(from.right,to.right),Math.max(from.bottom,to.bottom)+range.margin);cm.scrollTo(sPos.scrollLeft,sPos.scrollTop)}}function indentLine(cm,n,how,aggressive){var doc=cm.doc,state;if(how==null){how="add"}if(how=="smart"){if(!doc.mode.indent){how="prev"}else{state=getStateBefore(cm,n)}}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=0;i--){replaceRange(cm.doc,"",kill[i].from,kill[i].to,"+delete")}ensureCursorVisible(cm)})}function findPosH(doc,pos,dir,unit,visually){var line=pos.line,ch=pos.ch,origDir=dir;var lineObj=getLine(doc,line);var possible=true;function findNextLine(){var l=line+dir;if(l=doc.first+doc.size){return(possible=false)}line=l;return lineObj=getLine(doc,l)}function moveOnce(boundToLine){var next=(visually?moveVisually:moveLogically)(lineObj,ch,dir,true);if(next==null){if(!boundToLine&&findNextLine()){if(visually){ch=(dir<0?lineRight:lineLeft)(lineObj)}else{ch=dir<0?lineObj.text.length:0}}else{return(possible=false)}}else{ch=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(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()}break}if(type){sawType=type}if(dir>0&&!moveOnce(!first)){break}}}}}var result=skipAtomic(doc,Pos(line,ch),origDir,true);if(!possible){result.hitSide=true}return result}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);y=pos.top+dir*(pageSize-(dir<0?1.5:0.5)*textHeight(cm.display))}else{if(unit=="line"){y=dir>0?pos.bottom+3:pos.top-3}}for(;;){var 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}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)}},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;i0){replaceOneSelection(this.doc,i,new Range(from,newRanges[i].to()),sel_dontScroll)}}else{if(range.head.line>end){indentLine(this,range.head.line,how,true);end=range.head.line;if(i==this.doc.sel.primIndex){ensureCursorVisible(this)}}}}}),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]last){line=last;end=true}}var lineObj=getLine(this.doc,line);return intoCoordSystem(this,lineObj,{top:0,left:0},mode||"page").top+(end?this.doc.height-heightAtLine(lineObj):0)},defaultTextHeight:function(){return textHeight(this.display)},defaultCharWidth:function(){return charWidth(this.display)},setGutterMarker:methodOp(function(line,gutterID,value){return changeLine(this.doc,line,"gutter",function(line){var markers=line.gutterMarkers||(line.gutterMarkers={});markers[gutterID]=value;if(!value&&isEmpty(markers)){line.gutterMarkers=null}return true})}),clearGutter:methodOp(function(gutterID){var cm=this,doc=cm.doc,i=doc.first;doc.iter(function(line){if(line.gutterMarkers&&line.gutterMarkers[gutterID]){line.gutterMarkers[gutterID]=null;regLineChange(cm,i,"gutter");if(isEmpty(line.gutterMarkers)){line.gutterMarkers=null}}++i})}),addLineWidget:methodOp(function(handle,node,options){return addLineWidget(this,handle,node,options)}),removeLineWidget:function(widget){widget.clear()},lineInfo:function(line){if(typeof line=="number"){if(!isLine(this.doc,line)){return null}var n=line;line=getLine(this.doc,line);if(!line){return null}}else{var 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}},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);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,top,left+node.offsetWidth,top+node.offsetHeight)}},triggerOnKeyDown:methodOp(onKeyDown),triggerOnKeyPress:methodOp(onKeyPress),triggerOnKeyUp:onKeyUp,execCommand:function(cmd){if(commands.hasOwnProperty(cmd)){return commands[cmd](this)
-}},findPosH:function(from,amount,unit,visually){var dir=1;if(amount<0){dir=-1;amount=-amount}for(var i=0,cur=clipPos(this.doc,from);i0&&check(line.charAt(start-1))){--start}while(end0.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();
-this.scrollTo(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);var defaults=CodeMirror.defaults={};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}}var Init=CodeMirror.Init={toString:function(){return"CodeMirror.Init"}};option("value","",function(cm,val){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("specialChars",/[\t\u0000-\u0019\u00ad\u200b-\u200f\u2028\u2029\ufeff]/g,function(cm,val){cm.options.specialChars=new RegExp(val.source+(val.test("\t")?"":"|\t"),"g");cm.refresh()},true);option("specialCharPlaceholder",defaultSpecialCharPlaceholder,function(cm){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")},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!=CodeMirror.Init&&getKeyMap(old);if(prev&&prev.detach){prev.detach(cm,next)}if(next.attach){next.attach(cm,prev||null)}});option("extraKeys",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){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("readOnly",false,function(cm,val){if(val=="nocursor"){onBlur(cm);cm.display.input.blur();cm.display.disabled=true}else{cm.display.disabled=false;if(!val){cm.display.input.reset()}}});option("disableInput",false,function(cm,val){if(!val){cm.display.input.reset()}},true);option("dragDrop",true);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){cm.doc.history.undoDepth=val});option("historyEventDelay",1250);option("viewportMargin",10,function(cm){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){cm.display.input.getField().tabIndex=val||""});option("autofocus",null);var modes=CodeMirror.modes={},mimeModes=CodeMirror.mimeModes={};CodeMirror.defineMode=function(name,mode){if(!CodeMirror.defaults.mode&&name!="null"){CodeMirror.defaults.mode=name}if(arguments.length>2){mode.dependencies=Array.prototype.slice.call(arguments,2)}modes[name]=mode};CodeMirror.defineMIME=function(mime,spec){mimeModes[mime]=spec};CodeMirror.resolveMode=function(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 CodeMirror.resolveMode("application/xml")}}}if(typeof spec=="string"){return{name:spec}}else{return spec||{name:"null"}}};CodeMirror.getMode=function(options,spec){var spec=CodeMirror.resolveMode(spec);var mfactory=modes[spec.name];if(!mfactory){return CodeMirror.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 in spec.modeProps){modeObj[prop]=spec.modeProps[prop]}}return modeObj};CodeMirror.defineMode("null",function(){return{token:function(stream){stream.skipToEnd()}}});CodeMirror.defineMIME("text/plain","null");var modeExtensions=CodeMirror.modeExtensions={};CodeMirror.extendMode=function(mode,properties){var exts=modeExtensions.hasOwnProperty(mode)?modeExtensions[mode]:(modeExtensions[mode]={});copyObj(properties,exts)};CodeMirror.defineExtension=function(name,func){CodeMirror.prototype[name]=func};CodeMirror.defineDocExtension=function(name,func){Doc.prototype[name]=func};CodeMirror.defineOption=option;var initHooks=[];CodeMirror.defineInitHook=function(f){initHooks.push(f)};var helpers=CodeMirror.helpers={};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})};var copyState=CodeMirror.copyState=function(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};var startState=CodeMirror.startState=function(mode,a1,a2){return mode.startState?mode.startState(a1,a2):true};CodeMirror.innerMode=function(mode,state){while(mode.innerMode){var info=mode.innerMode(state);if(!info||info.mode==mode){break}state=info.state;mode=info.mode}return info||{mode:mode,state:state}};var commands=CodeMirror.commands={selectAll:function(cm){cm.setSelection(Pos(cm.firstLine(),0),Pos(cm.lastLine()),sel_dontScroll)},singleSelection:function(cm){cm.setSelection(cm.getCursor("anchor"),cm.getCursor("head"),sel_dontScroll)},killLine:function(cm){deleteNearSelection(cm,function(range){if(range.empty()){var len=getLine(cm.doc,range.head.line).text.length;if(range.head.ch==len&&range.head.line0){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){cm.replaceRange(line.charAt(0)+"\n"+prev.charAt(prev.length-1),Pos(cur.line-1,prev.length-1),Pos(cur.line,1),"+transpose")}}}}newSel.push(new Range(cur,cur))}cm.setSelections(newSel)})},newlineAndIndent:function(cm){runInOp(cm,function(){var len=cm.listSelections().length;for(var i=0;i=this.string.length},sol:function(){return this.pos==this.lineStart},peek:function(){return this.string.charAt(this.pos)||undefined},next:function(){if(this.posstart},eatSpace:function(){var start=this.pos;while(/[\s\u00a0]/.test(this.string.charAt(this.pos))){++this.pos}return this.pos>start},skipToEnd:function(){this.pos=this.string.length},skipTo:function(ch){var found=this.string.indexOf(ch,this.pos);if(found>-1){this.pos=found;return true}},backUp:function(n){this.pos-=n},column:function(){if(this.lastColumnPos0){return null}if(match&&consume!==false){this.pos+=match[0].length}return match}},current:function(){return this.string.slice(this.start,this.pos)},hideFirstChars:function(n,inner){this.lineStart+=n;try{return inner()}finally{this.lineStart-=n}}};var nextMarkerId=0;var TextMarker=CodeMirror.TextMarker=function(doc,type){this.lines=[];
-this.type=type;this.doc=doc;this.id=++nextMarkerId};eventMixin(TextMarker);TextMarker.prototype.clear=function(){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;icm.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)}if(withOp){endOperation(cm)}if(this.parent){this.parent.clear()}};TextMarker.prototype.find=function(side,lineObj){if(side==null&&this.type=="bookmark"){side=1}var from,to;for(var i=0;i0||diff==0&&marker.clearWhenEmpty!==false){return marker}if(marker.replacedWith){marker.collapsed=true;marker.widgetNode=elt("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")}sawCollapsedSpans=true}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});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(){marker.clear()})}if(marker.readOnly){sawReadOnlySpans=true;if(doc.history.done.length||doc.history.undone.length){doc.clearHistory()}}if(marker.collapsed){marker.id=++nextMarkerId;marker.atomic=true}if(cm){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}var SharedTextMarker=CodeMirror.SharedTextMarker=function(markers,primary){this.markers=markers;
-this.primary=primary;for(var i=0;i=startCh:span.to>startCh);(nw||(nw=[])).push(new MarkedSpan(marker,span.from,endsAfter?null:span.to))}}}return nw}function markedSpansAfter(old,endCh,isInsert){if(old){for(var i=0,nw;i=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.from0&&first){for(var i=0;i0){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-1}}return parts}function detachMarkedSpans(line){var spans=line.markedSpans;if(!spans){return}for(var i=0;i=0&&toCmp<=0||fromCmp<=0&&toCmp>=0){continue}if(fromCmp<=0&&(cmp(found.to,from)>0||(sp.marker.inclusiveRight&&marker.inclusiveLeft))||fromCmp>=0&&(cmp(found.from,to)<0||(sp.marker.inclusiveLeft&&marker.inclusiveRight))){return true}}}}function visualLine(line){var merged;while(merged=collapsedSpanAtStart(line)){line=merged.find(-1,true).line}return line}function visualLineContinued(line){var merged,lines;while(merged=collapsedSpanAtEnd(line)){line=merged.find(1,true).line;(lines||(lines=[])).push(line)}return lines}function visualLineNo(doc,lineN){var line=getLine(doc,lineN),vis=visualLine(line);if(line==vis){return lineN}return lineNo(vis)}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}function lineIsHidden(doc,line){var sps=sawCollapsedSpans&&line.markedSpans;if(sps){for(var sp,i=0;istream.start){return style}}throw new Error("Mode "+mode.name+" failed to advance stream.")}function takeToken(cm,pos,precise,asArray){function getObj(copy){return{start:stream.start,end:stream.pos,string:stream.current(),type:style||null,state:copy?copyState(doc.mode,state):state}}var doc=cm.doc,mode=doc.mode,style;pos=clipPos(doc,pos);var line=getLine(doc,pos.line),state=getStateBefore(cm,pos.line,precise);var stream=new StringStream(line.text,cm.options.tabSize),tokens;if(asArray){tokens=[]}while((asArray||stream.poscm.options.maxHighlightLength){flattenSpans=false;if(forceToEnd){processLine(cm,text,state,stream.pos)}stream.pos=text.length;style=null}else{style=extractLineClasses(readToken(mode,stream,state,inner),lineClasses)}if(inner){var mName=inner[0].name;if(mName){style="m-"+(style?mName+" "+style:mName)}}if(!flattenSpans||curStyle!=style){while(curStartend){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,"cm-overlay "+style);i=start+2}else{for(;startstart&&part.from<=start){break}}if(part.to>=end){return inner(builder,text,style,startStyle,endStyle,title)}inner(builder,text.slice(0,part.to-start),style,startStyle,null,title);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}function insertLineContent(line,builder,styles){var spans=line.markedSpans,allText=line.text,at=0;if(!spans){for(var i=1;ipos)){if(sp.to!=null&&nextChange>sp.to){nextChange=sp.to;spanEndStyle=""}if(m.className){spanStyle+=" "+m.className}if(m.css){css=m.css}if(m.startStyle&&sp.from==pos){spanStartStyle+=" "+m.startStyle}if(m.endStyle&&sp.to==nextChange){spanEndStyle+=" "+m.endStyle}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(m.type=="bookmark"&&sp.from==pos&&m.widgetNode){foundBookmarks.push(m)}}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&&foundBookmarks.length){for(var j=0;j=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)}}}function isWholeLineUpdate(doc,change){return change.from.ch==0&&change.to.ch==0&&lst(change.text)==""&&(!doc.cm||doc.cm.options.wholeLineUpdateBefore)}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){for(var i=start,result=[];i1){doc.remove(from.line+1,nlines-1)}doc.insert(from.line+1,added)}}}}signalLater(doc,"change",doc,change)}function LeafChunk(lines){this.lines=lines;this.parent=null;for(var i=0,height=0;i1||!(this.children[0] instanceof LeafChunk))){var lines=[];this.collapse(lines);this.children=[new LeafChunk(lines)];this.children[0].parent=this}},collapse:function(lines){for(var i=0;i50){while(child.lines.length>50){var spilled=child.lines.splice(child.lines.length-25,25);var newleaf=new LeafChunk(spilled);child.height-=newleaf.height;this.children.splice(i+1,0,newleaf);newleaf.parent=this}this.maybeSpill()}break}at-=sz}},maybeSpill:function(){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){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(at,n,op){for(var i=0;i=0;i--){makeChange(this,changes[i])}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=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;ispan.to||span.from==null&&lineNo!=from.line||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;ioff){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.linefrom){from=options.from}if(options.to!=null&&options.to=doc.size){throw new Error("There is no line "+(n+doc.first)+" in the document.")}for(var chunk=doc;!chunk.lines;){for(var i=0;;++i){var child=chunk.children[i],sz=child.chunkSize();if(n1&&!hist.done[hist.done.length-2].ranges){hist.done.pop();return lst(hist.done)}}}}function addChangeToHistory(doc,change,selAfter,opId){var hist=doc.history;hist.undone.length=0;var time=+new Date,cur;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))){var last=lst(cur.changes);if(cmp(change.from,change.to)==0&&cmp(change.from,last.to)==0){last.to=changeEnd(change)}else{cur.changes.push(historyChangeFromChange(doc,change))}}else{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)}function addSelectionToHistory(doc,sel,opId,options){var hist=doc.history,origin=options&&options.origin;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)}}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})}function removeClearedSpans(spans){if(!spans){return null}for(var i=0,out;i-1){lst(newChanges)[prop]=change[prop];delete change[prop]}}}}}}return copy}function rebaseHistSelSingle(pos,from,to,diff){if(to0}function eventMixin(ctor){ctor.prototype.on=function(type,f){on(this,type,f)};ctor.prototype.off=function(type,f){off(this,type,f)}}var scrollerGap=30;var Pass=CodeMirror.Pass={toString:function(){return"CodeMirror.Pass"}};var sel_dontScroll={scroll:false},sel_mouse={origin:"*mouse"},sel_move={origin:"+move"};function Delayed(){this.id=null}Delayed.prototype.set=function(ms,f){clearTimeout(this.id);this.id=setTimeout(f,ms)};var countColumn=CodeMirror.countColumn=function(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}};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]}var selectInput=function(node){node.select()};if(ios){selectInput=function(node){node.selectionStart=0;node.selectionEnd=node.value.length}}else{if(ie){selectInput=function(node){try{node.select()}catch(_e){}}}}function indexOf(array,elt){for(var i=0;
-i"\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}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)}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;i0;--count){e.removeChild(e.firstChild)}return e}function removeChildrenAndAdd(parent,e){return removeChildren(parent).appendChild(e)}var contains=CodeMirror.contains=function(parent,child){if(child.nodeType==3){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(){return document.activeElement}if(ie&&ie_version<11){activeElt=function(){try{return document.activeElement}catch(e){return document.body}}}function classTest(cls){return new RegExp("(^|\\s)"+cls+"(?:$|\\s)\\s*")}var rmClass=CodeMirror.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:"")}};var addClass=CodeMirror.addClass=function(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;i2&&!(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}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();if(!r0||r0.left==r0.right){return false}var r1=range(txt,1,2).getBoundingClientRect();return badBidiRects=(r1.right-r0.right<3)}var splitLines=CodeMirror.splitLines="\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){try{var 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 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",107:"=",109:"-",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"};CodeMirror.keyNames=keyNames;(function(){for(var i=0;i<10;i++){keyNames[i+48]=keyNames[i+96]=String(i)}for(var i=65;i<=90;i++){keyNames[i]=String.fromCharCode(i)}for(var i=1;i<=12;i++){keyNames[i+111]=keyNames[i+63235]="F"+i}})();function iterateBidiSections(order,from,to,f){if(!order){return f(from,to,"ltr")}var found=false;for(var i=0;ifrom||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")}}function bidiLeft(part){return part.level%2?part.to:part.from}function bidiRight(part){return part.level%2?part.from:part.to}function lineLeft(line){var order=getOrder(line);return order?bidiLeft(order[0]):0}function lineRight(line){var order=getOrder(line);if(!order){return line.text.length}return bidiRight(lst(order))}function lineStart(cm,lineN){var line=getLine(cm.doc,lineN);var visual=visualLine(line);if(visual!=line){lineN=lineNo(visual)}var order=getOrder(visual);var ch=!order?0:order[0].level%2?lineRight(visual):lineLeft(visual);return Pos(lineN,ch)}function lineEnd(cm,lineN){var merged,line=getLine(cm.doc,lineN);while(merged=collapsedSpanAtEnd(line)){line=merged.find(1,true).line;lineN=null}var order=getOrder(line);var ch=!order?line.text.length:order[0].level%2?lineLeft(line):lineRight(line);return Pos(lineN==null?lineNo(line):lineN,ch)}function lineStartSmart(cm,pos){var start=lineStart(cm,pos.line);var line=getLine(cm.doc,start.line);var order=getOrder(line);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)}return start}function compareBidiLevel(order,a,b){var linedir=order[0].level;if(a==linedir){return true}if(b==linedir){return false}return apos){return i}if((cur.from==pos||cur.to==pos)){if(found==null){found=i}else{if(compareBidiLevel(order,cur.level,order[found].level)){if(cur.from!=cur.to){bidiOther=found}return i}else{if(cur.from!=cur.to){bidiOther=i}return found}}}}return found}function moveInLine(line,pos,dir,byUnit){if(!byUnit){return pos+dir}do{pos+=dir}while(pos>0&&isExtendingChar(line.text.charAt(pos)));return pos}function moveVisually(line,start,dir,byUnit){var bidi=getOrder(line);if(!bidi){return moveLogically(line,start,dir,byUnit)}var pos=getBidiPartAt(bidi,start),part=bidi[pos];var target=moveInLine(line,start,part.level%2?-dir:dir,byUnit);for(;;){if(target>part.from&&target0)==part.level%2?part.to:part.from}else{part=bidi[pos+=dir];if(!part){return null}if((dir>0)==part.level%2){target=moveInLine(line,part.to,-1,byUnit)}else{target=moveInLine(line,part.from,1,byUnit)}}}}function moveLogically(line,start,dir,byUnit){var target=start+dir;if(byUnit){while(target>0&&isExtendingChar(line.text.charAt(target))){target+=dir}}return target<0||target>line.text.length?null:target}var bidiOrdering=(function(){var lowTypes="bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLN";var arabicTypes="rrrrrrrrrrrr,rNNmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmrrrrrrrnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmNmmmm";function charType(code){if(code<=247){return lowTypes.charAt(code)}else{if(1424<=code&&code<=1524){return"R"}else{if(1536<=code&&code<=1773){return arabicTypes.charAt(code-1536)}else{if(1774<=code&&code<=2220){return"r"}else{if(8192<=code&&code<=8203){return"w"}else{if(code==8204){return"b"}else{return"L"}}}}}}}var bidiRE=/[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/;var isNeutral=/[stwN]/,isStrong=/[LRr]/,countsAsLeft=/[Lb1n]/,countsAsNum=/[1n]/;var outerType="L";function BidiSpan(level,from,to){this.level=level;this.from=from;this.to=to}return function(str){if(!bidiRE.test(str)){return false}var len=str.length,types=[];for(var i=0,type;it)return i;o.to==t&&(o.from!=o.to&&"before"==n?r=i:Oe=i),o.from==t&&(o.from!=o.to&&"before"!=n?r=i:Oe=i)}return null!=r?r:Oe}De=/[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/,We=/[stwN]/,He=/[LRr]/,Fe=/[Lb1n]/,Pe=/[1n]/;var De,We,He,Fe,Pe,Ee=function(e,t){var n="ltr"==t?"L":"R";if(0==e.length||"ltr"==t&&!De.test(e))return!1;for(var r,i=e.length,o=[],l=0;l=e.size)throw new Error("There is no line "+(t+e.first)+" in the document.");for(var n=e;!n.lines;)for(var r=0;;++r){var i=n.children[r],o=i.chunkSize();if(t=e.first&&tn?H(n,D(e,n).text.length):(e=D(e,(n=t).line).text.length,null==(t=n.ch)||e=this.string.length},g.prototype.sol=function(){return this.pos==this.lineStart},g.prototype.peek=function(){return this.string.charAt(this.pos)||void 0},g.prototype.next=function(){if(this.post},g.prototype.eatSpace=function(){for(var e=this.pos;/[\s\u00a0]/.test(this.string.charAt(this.pos));)++this.pos;return this.pos>e},g.prototype.skipToEnd=function(){this.pos=this.string.length},g.prototype.skipTo=function(e){e=this.string.indexOf(e,this.pos);if(-1e.options.maxHighlightLength&&at(e.doc.mode,r.state),o=kt(e,t,r),i&&(r.state=i),t.stateAfter=r.save(!i),t.styles=o.styles,o.classes?t.styleClasses=o.classes:t.styleClasses&&(t.styleClasses=null),n===e.doc.highlightFrontier&&(e.doc.modeFrontier=Math.max(e.doc.modeFrontier,++e.doc.highlightFrontier))),t.styles}function Mt(n,r,e){var t,i,o,l=n.doc,s=n.display;return l.mode.startState?(i=(t=function(e,t,n){for(var r,i,o=e.doc,l=n?-1:t-(e.doc.mode.innerMode?1e3:100),s=t;ll.first&&D(l,t-1).stateAfter,o=i?Lt.fromSaved(l,i,t):new Lt(l,ct(l.mode),t),l.iter(t,r,function(e){Nt(n,e.text,o);var t=o.line;e.stateAfter=t==r-1||t%5==0||t>=s.viewFrom&&tt.start)return o}throw new Error("Mode "+e.name+" failed to advance stream.")}Lt.prototype.lookAhead=function(e){var t=this.doc.getLine(this.line+e);return null!=t&&e>this.maxLookAhead&&(this.maxLookAhead=e),t},Lt.prototype.baseToken=function(e){if(!this.baseTokens)return null;for(;this.baseTokens[this.baseTokenPos]<=e;)this.baseTokenPos+=2;var t=this.baseTokens[this.baseTokenPos+1];return{type:t&&t.replace(/( |^)overlay .*/,""),size:this.baseTokens[this.baseTokenPos]-e}},Lt.prototype.nextLine=function(){this.line++,0e.options.maxHighlightLength?(s=!1,l&&Nt(e,t,r,c.pos),c.pos=t.length,null):Ht(At(n,c,r.state,h),o);if(h&&(d=h[0].name)&&(f="m-"+(f?d+" "+f:d)),!s||u!=f){for(;a=t:l.to>t),(r=r||[]).push(new It(s,l.from,o?null:l.to)))}return r}(n,r,o),s=function(e,t,n){var r;if(e)for(var i=0;i=t:l.to>t))&&(l.from!=t||"bookmark"!=s.type||n&&!l.marker.insertLeft)||(o=null==l.from||(s.inclusiveLeft?l.from<=t:l.frome.lastLine())return t;var n,r=D(e,t);if(!Jt(e,r))return t;for(;n=$t(r);)r=n.find(1,!0).line;return W(r)+1}function Jt(e,t){var n=Et&&t.markedSpans;if(n)for(var r,i=0;in.maxLineLength&&(n.maxLineLength=t,n.maxLine=e)})}var rn=function(e,t,n){this.text=e,Ut(this,t),this.height=n?n(this):1};rn.prototype.lineNo=function(){return W(this)},Ve(rn);var on={},ln={};function sn(e,t){return!e||/^\s*$/.test(e)?null:(t=t.addModeClass?ln:on)[e]||(t[e]=e.replace(/\S+/g,"cm-$&"))}function an(e,t){var n=ee("span",null,null,x?"padding-right: .1px":null),r={pre:ee("pre",[n],"CodeMirror-line"),content:n,col:0,pos:0,cm:e,trailingSpace:!1,splitSpaces:e.getOption("lineWrapping")};t.measure={};for(var i,o,l=0;l<=(t.rest?t.rest.length:0);l++){var s=l?t.rest[l-1]:t.line,a=void 0,u=(r.pos=0,r.addToken=cn,u=e.display.measure,o=i=void 0,(null!=qe?qe:(o=y(u,document.createTextNode("AخA")),i=ie(o,0,1).getBoundingClientRect(),o=ie(o,1,2).getBoundingClientRect(),J(u),i&&i.left!=i.right&&(qe=o.right-i.right<3)))&&(a=ze(s,e.doc.direction))&&(r.addToken=function(h,d){return function(e,t,n,r,i,o,l){n=n?n+" cm-force-border":"cm-force-border";for(var s=e.pos,a=s+t.length;;){for(var u=void 0,c=0;cs&&u.from<=s);c++);if(u.to>=a)return h(e,t,n,r,i,o,l);h(e,t.slice(0,u.to-s),n,r,null,o,l),r=null,t=t.slice(u.to-s),s=u.to}}}(r.addToken,a)),r.map=[],t!=e.display.externalMeasured&&W(s));!function(e,t,n){var r=e.markedSpans,i=e.text,o=0;if(r)for(var l,s,a,u,c,h,d,f=i.length,p=0,g=1,m="",v=0;;){if(v==p){a=u=c=s="",h=d=null,v=1/0;for(var y=[],b=void 0,w=0;wp||C.collapsed&&x.to==p&&x.from==p)){if(null!=x.to&&x.to!=p&&v>x.to&&(v=x.to,u=""),C.className&&(a+=" "+C.className),C.css&&(s=(s?s+";":"")+C.css),C.startStyle&&x.from==p&&(c+=" "+C.startStyle),C.endStyle&&x.to==v&&(b=b||[]).push(C.endStyle,x.to),C.title&&((d=d||{}).title=C.title),C.attributes)for(var S in C.attributes)(d=d||{})[S]=C.attributes[S];C.collapsed&&(!h||jt(h.marker,C)<0)&&(h=x)}else x.from>p&&v>x.from&&(v=x.from)}if(b)for(var L=0;Ln)return{map:e.measure.maps[i],cache:e.measure.caches[i],before:!0}}}function Hn(e,t,n,r){return En(e,Pn(e,t),n,r)}function Fn(e,t){return t>=e.display.viewFrom&&t=e.lineN&&tt)&&(i=(o=a-s)-1,a<=t)&&(l="right"),null!=i){if(r=e[u+2],s==a&&n==(r.insertLeft?"left":"right")&&(l=n),"left"==n&&0==i)for(;u&&e[u-2]==e[u-3]&&e[u-1].insertLeft;)r=e[2+(u-=3)],l="left";if("right"==n&&i==a-s)for(;u=i.text.length?(c=i.text.length,e="before"):c<=0&&(c=0,e="after"),u?(a=Ae(u,c,e),t=Oe,a=h(c,a,"before"==e),null!=t&&(a.other=h(c,t,"before"!=e)),a):s("before"==e?c-1:c,"before"==e);function h(e,t,n){return s(n?e-1:e,1==u[t].level!=n)}}function qn(e,t){var n=0,t=(t=P(e.doc,t),e.options.lineWrapping||(n=or(e.display)*t.ch),D(e.doc,t.line)),e=en(t)+Tn(e.display);return{left:n,right:n,top:e,bottom:e+t.height}}function Zn(e,t,n,r,i){e=H(e,t,n);return e.xRel=i,r&&(e.outside=r),e}function Qn(e,t,n){var r=e.doc;if((n+=e.display.viewOffset)<0)return Zn(r.first,0,null,-1,-1);var i=pt(r,n),o=r.first+r.size-1;if(o=a.bottom?1:0)}return c=Me(e.text,c,1),Zn(t,c,g,f,r-p)}(e,l,i,t,n),a=function(e,t){var n,r=Et&&e.markedSpans;if(r)for(var i=0;it)&&(!n||jt(n,o.marker)<0)&&(n=o.marker)}return n}(l,s.ch+(0r},i,e)}}function er(e,t,n,r){return Jn(e,t,n=n||Pn(e,t),Xn(e,t,En(e,n,r),"line").top)}function tr(e,t,n,r){return!(e.bottom<=n)&&(e.top>n||(r?e.left:e.right)>t)}function nr(n,r,i,o,l,s,a){var e,t=Ne(function(e){var e=l[e],t=1!=e.level;return tr(_n(n,H(i,t?e.to:e.from,t?"before":"after"),"line",r,o),s,a,!0)},0,l.length-1),u=l[t];return u=0a?l[t-1]:u}function rr(e,t,n,r,i,o,l){for(var l=Jn(e,t,r,l),s=l.begin,a=l.end,u=(/\s/.test(t.text.charAt(a-1))&&a--,null),c=null,h=0;h=a||f.to<=s||(d=(d=En(e,r,1!=f.level?Math.min(a,f.to)-1:Math.max(s,f.from)).right)a?{from:u.from,to:a,level:u.level}:u}function ir(e){if(null!=e.cachedTextHeight)return e.cachedTextHeight;if(null==In){In=M("pre",null,"CodeMirror-line-like");for(var t=0;t<49;++t)In.appendChild(document.createTextNode("x")),In.appendChild(M("br"));In.appendChild(document.createTextNode("x"))}y(e.measure,In);var n=In.offsetHeight/50;return 3=e.display.viewTo)return null;if((t-=e.display.viewFrom)<0)return null;for(var n=e.display.view,r=0;rt)&&(o.updateLineNumbers=t),e.curOp.viewChanged=!0,t>=o.viewTo?Et&&Zt(e.doc,t)o.viewFrom?fr(e):(o.viewFrom+=r,o.viewTo+=r):t<=o.viewFrom&&n>=o.viewTo?fr(e):t<=o.viewFrom?(l=pr(e,n,n+r,1))?(o.view=o.view.slice(l.index),o.viewFrom=l.lineN,o.viewTo+=r):fr(e):n>=o.viewTo?(l=pr(e,t,t,-1))?(o.view=o.view.slice(0,l.index),o.viewTo=l.lineN):fr(e):(l=pr(e,t,t,-1),i=pr(e,n,n+r,1),l&&i?(o.view=o.view.slice(0,l.index).concat(fn(e,l.lineN,i.lineN)).concat(o.view.slice(i.index)),o.viewTo+=r):fr(e)),o.externalMeasured);l&&(n=i.lineN&&t=r.viewTo||null!=(i=r.view[hr(e,t)]).node&&-1==S(r=i.changes||(i.changes=[]),n)&&r.push(n)}function fr(e){e.display.viewFrom=e.display.viewTo=e.doc.first,e.display.view=[],e.display.viewOffset=0}function pr(e,t,n,r){var i,o=hr(e,t),l=e.display.view;if(Et&&n!=e.doc.first+e.doc.size){for(var s=e.display.viewFrom,a=0;a=e.display.viewTo||s.to().linet||t==n&&l.to==t)&&(r(Math.max(l.from,t),Math.min(l.to,n),1==l.level?"rtl":"ltr",o),i=!0)}i||r(t,n,"ltr")}(C,g||0,null==m?b:m,function(e,t,n,r){var i,o,l,s,a,u="ltr"==n,c=w(e,u?"left":"right"),h=w(t-1,u?"right":"left"),d=null==g&&0==e,f=null==m&&t==b,p=0==r,r=!C||r==C.length-1;h.top-c.top<=3?(i=(k?d:f)&&p?S:(u?c:h).left,a=(k?f:d)&&r?L:(u?h:c).right,T(i,c.top,a-i,c.bottom)):(a=u?(o=k&&d&&p?S:c.left,l=k?L:x(e,n,"before"),s=k?S:x(t,n,"after"),k&&f&&r?L:h.right):(o=k?x(e,n,"before"):S,l=!k&&d&&p?L:c.right,s=!k&&f&&r?S:h.left,k?x(t,n,"after"):L),T(o,c.top,l-o,c.bottom),c.bottome.display.sizerWidth&&(a=Math.ceil(c/or(e.display)))>e.display.maxLineLength&&(e.display.maxLineLength=a,e.display.maxLine=s.line,e.display.maxLineChanged=!0)}}2=o&&(i=pt(t,en(D(t,n))-e.wrapper.clientHeight),o=n)),{from:i,to:Math.max(o,i+1)}}function Nr(e,t){var n=e.display,r=ir(e.display),i=(t.top<0&&(t.top=0),(e.curOp&&null!=e.curOp.scrollTop?e.curOp:n.scroller).scrollTop),o=Dn(e),l={},s=(t.bottom-t.top>o&&(t.bottom=t.top+o),e.doc.height+Mn(n)),a=t.tops-r,r=(t.topi+o&&(a=Math.min(t.top,(r?s:t.bottom)-o))!=i&&(l.scrollTop=a),e.options.fixedGutter?0:n.gutters.offsetWidth),s=e.curOp&&null!=e.curOp.scrollLeft?e.curOp.scrollLeft:n.scroller.scrollLeft-r,o=An(e)-n.gutters.offsetWidth,i=t.right-t.left>o;return i&&(t.right=t.left+o),t.left<10?l.scrollLeft=0:t.lefto+s-3&&(l.scrollLeft=t.right+(i?0:10)-o),l}function Or(e,t){null!=t&&(Wr(e),e.curOp.scrollTop=(null==e.curOp.scrollTop?e.doc:e.curOp).scrollTop+t)}function Ar(e){Wr(e);var t=e.getCursor();e.curOp.scrollToPos={from:t,to:t,margin:e.options.cursorScrollMargin}}function Dr(e,t,n){null==t&&null==n||Wr(e),null!=t&&(e.curOp.scrollLeft=t),null!=n&&(e.curOp.scrollTop=n)}function Wr(e){var t=e.curOp.scrollToPos;t&&(e.curOp.scrollToPos=null,Hr(e,qn(e,t.from),qn(e,t.to),t.margin))}function Hr(e,t,n,r){t=Nr(e,{left:Math.min(t.left,n.left),top:Math.min(t.top,n.top)-r,right:Math.max(t.right,n.right),bottom:Math.max(t.bottom,n.bottom)+r});Dr(e,t.scrollLeft,t.scrollTop)}function Fr(e,t){Math.abs(e.doc.scrollTop-t)<2||(m||Qr(e,{top:t}),Pr(e,t,!0),m&&Qr(e),Yr(e,100))}function Pr(e,t,n){t=Math.max(0,Math.min(e.display.scroller.scrollHeight-e.display.scroller.clientHeight,t)),e.display.scroller.scrollTop==t&&!n||(e.doc.scrollTop=t,e.display.scrollbars.setScrollTop(t),e.display.scroller.scrollTop==t)||(e.display.scroller.scrollTop=t)}function Er(e,t,n,r){t=Math.max(0,Math.min(t,e.display.scroller.scrollWidth-e.display.scroller.clientWidth)),(n?t==e.doc.scrollLeft:Math.abs(e.doc.scrollLeft-t)<2)&&!r||(e.doc.scrollLeft=t,ti(e),e.display.scroller.scrollLeft!=t&&(e.display.scroller.scrollLeft=t),e.display.scrollbars.setScrollLeft(t))}function Ir(e){var t=e.display,n=t.gutters.offsetWidth,r=Math.round(e.doc.height+Mn(e.display));return{clientHeight:t.scroller.clientHeight,viewHeight:t.wrapper.clientHeight,scrollWidth:t.scroller.scrollWidth,clientWidth:t.scroller.clientWidth,viewWidth:t.wrapper.clientWidth,barLeft:e.options.fixedGutter?n:0,docHeight:r,scrollHeight:r+On(e)+t.barHeight,nativeBarWidth:t.nativeBarWidth,gutterWidth:n}}function zr(e,t,n){this.cm=n;var r=this.vert=M("div",[M("div",null,null,"min-width: 1px")],"CodeMirror-vscrollbar"),i=this.horiz=M("div",[M("div",null,null,"height: 100%; min-height: 1px")],"CodeMirror-hscrollbar");r.tabIndex=i.tabIndex=-1,e(r),e(i),L(r,"scroll",function(){r.clientHeight&&t(r.scrollTop,"vertical")}),L(i,"scroll",function(){i.clientWidth&&t(i.scrollLeft,"horizontal")}),this.checkedZeroWidth=!1,w&&v<8&&(this.horiz.style.minHeight=this.vert.style.minWidth="18px")}function Rr(){}zr.prototype.update=function(e){var t,n=e.scrollWidth>e.clientWidth+1,r=e.scrollHeight>e.clientHeight+1,i=e.nativeBarWidth;return r?(this.vert.style.display="block",this.vert.style.bottom=n?i+"px":"0",t=e.viewHeight-(n?i:0),this.vert.firstChild.style.height=Math.max(0,e.scrollHeight-e.clientHeight+t)+"px"):(this.vert.scrollTop=0,this.vert.style.display="",this.vert.firstChild.style.height="0"),n?(this.horiz.style.display="block",this.horiz.style.right=r?i+"px":"0",this.horiz.style.left=e.barLeft+"px",t=e.viewWidth-e.barLeft-(r?i:0),this.horiz.firstChild.style.width=Math.max(0,e.scrollWidth-e.clientWidth+t)+"px"):(this.horiz.style.display="",this.horiz.firstChild.style.width="0"),!this.checkedZeroWidth&&0=l.viewTo)||l.maxLineChanged&&o.options.lineWrapping,i.update=i.mustUpdate&&new _r(o,i.mustUpdate&&{top:i.scrollTop,ensure:i.scrollToPos},i.forceUpdate)}for(var s=0;s(window.innerHeight||document.documentElement.clientHeight)&&(r=!1),null==r)||K||(i=M("div","",null,"position: absolute;\n top: "+(t.top-n.viewOffset-Tn(e.display))+"px;\n height: "+(t.bottom-t.top+On(e)+n.barHeight)+"px;\n left: "+t.left+"px; width: "+Math.max(2,t.right-t.left)+"px;"),e.display.lineSpace.appendChild(i),i.scrollIntoView(r),e.display.lineSpace.removeChild(i))}(w,v));var S=b.maybeHiddenMarkers,L=b.maybeUnhiddenMarkers;if(S)for(var k=0;k=l.display.viewTo||(s=+new Date+l.options.workTime,a=Mt(l,c.highlightFrontier),u=[],c.iter(a.line,Math.min(c.first+c.size,l.display.viewTo+500),function(e){if(a.line>=l.display.viewFrom){for(var t=e.styles,n=e.text.length>l.options.maxHighlightLength?at(c.mode,a.state):null,r=kt(l,e,a,!0),n=(n&&(a.state=n),e.styles=r.styles,e.styleClasses),r=r.classes,i=(r?e.styleClasses=r:n&&(e.styleClasses=null),!t||t.length!=e.styles.length||n!=r&&(!n||!r||n.bgClass!=r.bgClass||n.textClass!=r.textClass)),o=0;!i&&os)return Yr(l,l.options.workDelay),!0}),c.highlightFrontier=a.line,c.modeFrontier=Math.max(c.modeFrontier,a.line),u.length&&h(l,function(){for(var e=0;e=n.viewFrom&&t.visible.to<=n.viewTo&&(null==n.updateLineNumbers||n.updateLineNumbers>=n.viewTo)&&n.renderedView==n.view&&0==gr(e))return!1;ni(e)&&(fr(e),t.dims=lr(e));var i=r.first+r.size,o=Math.max(t.visible.from-e.options.viewportMargin,r.first),l=Math.min(i,t.visible.to+e.options.viewportMargin),r=(n.viewFroml&&n.viewTo-l<20&&(l=Math.min(i,n.viewTo)),Et&&(o=Zt(e.doc,o),l=Qt(e.doc,l)),o!=n.viewFrom||l!=n.viewTo||n.lastWrapHeight!=t.wrapperHeight||n.lastWrapWidth!=t.wrapperWidth),i=(i=o,o=l,0==(s=(l=e).display).view.length||i>=s.viewTo||o<=s.viewFrom?(s.view=fn(l,i,o),s.viewFrom=i):(s.viewFrom>i?s.view=fn(l,i,s.viewFrom).concat(s.view):s.viewFromo&&(s.view=s.view.slice(0,hr(l,o)))),s.viewTo=o,n.viewOffset=en(D(e.doc,n.viewFrom)),e.display.mover.style.top=n.viewOffset+"px",gr(e));if(!r&&0==i&&!t.force&&n.renderedView==n.view&&(null==n.updateLineNumbers||n.updateLineNumbers>=n.viewTo))return!1;var s,l=!(l=e).hasFocus()&&(s=N())&&te(l.display.lineDiv,s)?(s={activeElt:s},window.getSelection&&(o=window.getSelection()).anchorNode&&o.extend&&te(l.display.lineDiv,o.anchorNode)&&(s.anchorNode=o.anchorNode,s.anchorOffset=o.anchorOffset,s.focusNode=o.focusNode,s.focusOffset=o.focusOffset),s):null,a=(4=e.display.viewFrom&&t.visible.to<=e.display.viewTo)break;if(!qr(e,t))break;kr(e);var i=Ir(e);mr(e),Br(e,i),ei(e,i),t.force=!1}t.signal(e,"update",e),e.display.viewFrom==e.display.reportedViewFrom&&e.display.viewTo==e.display.reportedViewTo||(t.signal(e,"viewportChange",e,e.display.viewFrom,e.display.viewTo),e.display.reportedViewFrom=e.display.viewFrom,e.display.reportedViewTo=e.display.viewTo)}function Qr(e,t){var n,t=new _r(e,t);qr(e,t)&&(kr(e),Zr(e,t),n=Ir(e),mr(e),Br(e,n),ei(e,n),t.finish())}function Jr(e){var t=e.gutters.offsetWidth;e.sizer.style.marginLeft=t+"px",b(e,"gutterChanged",e)}function ei(e,t){e.display.sizer.style.minHeight=t.docHeight+"px",e.display.heightForcer.style.top=t.docHeight+"px",e.display.gutters.style.height=t.docHeight+e.display.barHeight+On(e)+"px"}function ti(e){var t=e.display,n=t.view;if(t.alignWidgets||t.gutters.firstChild&&e.options.fixedGutter){for(var r=sr(t)-t.scroller.scrollLeft+e.doc.scrollLeft,i=t.gutters.offsetWidth,o=r+"px",l=0;la.clientWidth,c=a.scrollHeight>a.clientHeight;if(o&&u||i&&c){if(i&&C&&x)e:for(var h=t.target,d=s.view;h!=a;h=h.parentNode)for(var f=0;fs-(e.cm?e.cm.options.historyEventDelay:500)||"*"==t.origin.charAt(0)))&&(o=(o=l).lastOp==r?(Mi(o.done),z(o.done)):o.done.length&&!z(o.done).ranges?z(o.done):1l.undoDepth;)l.done.shift(),l.done[0].ranges||l.done.shift()}l.done.push(n),l.generation=++l.maxGeneration,l.lastModTime=l.lastSelTime=s,l.lastOp=l.lastSelOp=r,l.lastOrigin=l.lastSelOrigin=t.origin,i||O(e,"historyAdded")}function Oi(e,t,n,r){var i,o,l,s=e.history,a=r&&r.origin;n==s.lastSelOp||a&&s.lastSelOrigin==a&&(s.lastModTime==s.lastSelTime&&s.lastOrigin==a||(e=e,i=a,o=z(s.done),l=t,"*"==(i=i.charAt(0)))||"+"==i&&o.ranges.length==l.ranges.length&&o.somethingSelected()==l.somethingSelected()&&new Date-e.history.lastSelTime<=(e.cm?e.cm.options.historyEventDelay:500))?s.done[s.done.length-1]=t:Ai(t,s.done),s.lastSelTime=+new Date,s.lastSelOrigin=a,s.lastSelOp=n,r&&!1!==r.clearRedo&&Mi(s.undone)}function Ai(e,t){var n=z(t);n&&n.ranges&&n.equals(e)||t.push(e)}function Di(t,n,e,r){var i=n["spans_"+t.id],o=0;t.iter(Math.max(t.first,e),Math.min(t.first+t.size,r),function(e){e.markedSpans&&((i=i||(n["spans_"+t.id]={}))[o]=e.markedSpans),++o})}function Wi(e,t){var n=t["spans_"+e.id];if(!n)return null;for(var r=[],i=0;i=t.ch:s.to>t.ch))){if(i&&(O(a,"beforeCursorEnter"),a.explicitlyCleared)){if(o.markedSpans){--l;continue}break}if(a.atomic){if(n){var s=a.find(r<0?1:-1),h=void 0;if((s=(r<0?c:u)?Yi(e,s,-r,s&&s.line==t.line?o:null):s)&&s.line==t.line&&(h=F(s,n))&&(r<0?h<0:0e.first?P(e,H(t.line-1)):null:0e.lastLine())){t.from.linei?{from:t.from,to:H(i,D(e,i).text.length),text:[t.text[0]],origin:t.origin}:t).removed=ht(e,t.from,t.to),n=n||mi(e,t),e.cm){var i=e.cm,o=t,l=r,s=i.doc,a=i.display,u=o.from,c=o.to,h=!1,d=u.line,f=(i.options.lineWrapping||(d=W(qt(D(s,u.line))),s.iter(d,c.line+1,function(e){if(e==a.maxLine)return h=!0})),-1a.maxLineLength&&(a.maxLine=e,a.maxLineLength=t,a.maxLineChanged=!0,h=!1)}),h&&(i.curOp.updateMaxLine=!0)),s),p=u.line;if(f.modeFrontier=Math.min(f.modeFrontier,p),!(f.highlightFrontiert.display.maxLineLength&&(t.display.maxLine=u,t.display.maxLineLength=c,t.display.maxLineChanged=!0)}null!=r&&t&&this.collapsed&&E(t,r,i+1),this.lines.length=0,this.explicitlyCleared=!0,this.atomic&&this.doc.cantEdit&&(this.doc.cantEdit=!1,t)&&Vi(t.doc),t&&b(t,"markerCleared",t,this,r,i),n&&Xr(t),this.parent&&this.parent.clear()}},co.prototype.find=function(e,t){var n,r;null==e&&"bookmark"==this.type&&(e=1);for(var i=0;i=e.ch)&&t.push(i.marker.parent||i.marker)}return t},findMarks:function(i,o,l){i=P(this,i),o=P(this,o);var s=[],a=i.line;return this.iter(i.line,o.line+1,function(e){var t=e.markedSpans;if(t)for(var n=0;n=r.to||null==r.from&&a!=i.line||null!=r.from&&a==o.line&&r.from>=o.ch||l&&!l(r.marker)||s.push(r.marker.parent||r.marker)}++a}),s},getAllMarks:function(){var r=[];return this.iter(function(e){var t=e.markedSpans;if(t)for(var n=0;nt&&(t=e.from),null!=e.to&&e.toe.text.length?null:t}function Ro(e,t,n){e=zo(e,t.ch,n);return null==e?null:new H(t.line,e,n<0?"after":"before")}function Bo(e,t,n,r,i){if(e){"rtl"==t.doc.direction&&(i=-i);var o,l,s,a,e=ze(n,t.doc.direction);if(e)return o=i<0==(1==(e=i<0?z(e):e[0]).level)?"after":"before",0=n.text.length?(s.ch=n.text.length,s.sticky="before"):s.ch<=0&&(s.ch=0,s.sticky="after");var r=Ae(a,s.ch,s.sticky),i=a[r];if("ltr"==t.doc.direction&&i.level%2==0&&(0s.ch:i.from=i.from&&d>=c.begin))return new H(s.line,d,h?"before":"after")}function f(e,t,n){for(var r=function(e,t){return t?new H(s.line,u(e,1),"before"):new H(s.line,e,"after")};0<=e&&el.doc.first&&(n=D(l.doc,e.line-1).text)&&(e=new H(e.line,1),l.replaceRange(t.charAt(0)+l.doc.lineSeparator()+n.charAt(n.length-1),H(e.line-1,n.length-1),e,"+transpose"))),i.push(new B(e,e)));l.setSelections(i)})},newlineAndIndent:function(r){return h(r,function(){for(var e=(t=r.listSelections()).length-1;0<=e;e--)r.replaceRange(r.doc.lineSeparator(),t[e].anchor,t[e].head,"+input");for(var t=r.listSelections(),n=0;nc&&t.push(new B(H(s,c),H(s,ge(u,l,n))))}t.length||t.push(new B(f,f)),G(g,di(d,y.ranges.slice(0,v).concat(t),v),{origin:"*mouse",scroll:!1}),d.scrollIntoView(e)}else{var h,r=m,i=il(d,e,p.unit),e=r.anchor,e=0=n.to||o.linea.bottom?20:0)&&setTimeout(I(d,function(){u==i&&(l.scroller.scrollTop+=r,e(t))}),50))}:n)(e)}),i=I(d,n);d.state.selectingText=i,L(l.wrapper.ownerDocument,"mousemove",r),L(l.wrapper.ownerDocument,"mouseup",i)})(i,s,o,a)):Ye(e)==h.scroller&&T(e):2==n?(t&&Ei(c.doc,t),setTimeout(function(){return h.input.focus()},20)):3==n&&(q?c.display.input.onContextMenu(e):Cr(c))))}function il(e,t,n){return"char"==n?new B(t,t):"word"==n?e.findWordAt(t):"line"==n?new B(H(t.line,0),P(e.doc,H(t.line+1,0))):(n=n(e,t),new B(n.from,n.to))}function ol(e,t,n,r){var i,o;if(t.touches)i=t.touches[0].clientX,o=t.touches[0].clientY;else try{i=t.clientX,o=t.clientY}catch(e){return!1}if(i>=Math.floor(e.display.gutters.getBoundingClientRect().right))return!1;r&&T(t);var l=e.display,r=l.lineDiv.getBoundingClientRect();if(o>r.bottom||!Ue(e,n))return je(t);o-=r.top-l.viewOffset;for(var s=0;s=i)return O(e,n,e,pt(e.doc,o),e.display.gutterSpecs[s].className,t),je(t)}}function ll(e,t){return ol(e,t,"gutterClick",!0)}function sl(e,t){var n,r;kn(e.display,t)||(r=t,Ue(n=e,"gutterContextMenu")&&ol(n,r,"gutterContextMenu",!1))||(A(e,t,"contextmenu")||q||e.display.input.onContextMenu(t))}function al(e){e.display.wrapper.className=e.display.wrapper.className.replace(/\s*cm-s-\S+/g,"")+e.options.theme.replace(/(^|\s)\s*/g," cm-s-"),Un(e)}el.prototype.compare=function(e,t,n){return this.time+400>e&&0==F(t,this.pos)&&n==this.button};var ul={toString:function(){return"CodeMirror.Init"}},cl={},hl={};function dl(e,t,n){!t!=!(n&&n!=ul)&&(n=e.display.dragFunctions,(t=t?L:k)(e.display.scroller,"dragstart",n.start),t(e.display.scroller,"dragenter",n.enter),t(e.display.scroller,"dragover",n.over),t(e.display.scroller,"dragleave",n.leave),t(e.display.scroller,"drop",n.drop))}function fl(e){e.options.lineWrapping?(ne(e.display.wrapper,"CodeMirror-wrap"),e.display.sizer.style.minWidth="",e.display.sizerWidth=null):(Q(e.display.wrapper,"CodeMirror-wrap"),nn(e)),ur(e),E(e),Un(e),setTimeout(function(){return Br(e)},100)}function p(e,t){var n=this;if(!(this instanceof p))return new p(e,t);this.options=t=t?se(t):{},se(cl,t,!1);var r,i=t.value,o=("string"==typeof i?i=new f(i,t.mode,null,t.lineSeparator,t.direction):t.mode&&(i.modeOption=t.mode),this.doc=i,new p.inputStyles[t.inputStyle](this)),e=this.display=new li(e,i,o,t),l=(al(e.wrapper.CodeMirror=this),t.lineWrapping&&(this.display.wrapper.className+=" CodeMirror-wrap"),Vr(this),this.state={keyMaps:[],overlays:[],modeGen:0,overwrite:!1,delayingBlurEvent:!1,focused:!1,suppressEdits:!1,pasteIncoming:-1,cutIncoming:-1,selectingText:!1,draggingText:!1,highlight:new ue,keySeq:null,specialChars:null},t.autofocus&&!Y&&e.input.focus(),w&&v<11&&setTimeout(function(){return n.display.input.reset(!0)},20),this),s=l.display;L(s.scroller,"mousedown",I(l,rl)),L(s.scroller,"dblclick",w&&v<11?I(l,function(e){var t;A(l,e)||!(t=cr(l,e))||ll(l,e)||kn(l.display,e)||(T(e),e=l.findWordAt(t),Ei(l.doc,e.anchor,e.head))}):function(e){return A(l,e)||T(e)}),L(s.scroller,"contextmenu",function(e){return sl(l,e)}),L(s.input.getField(),"contextmenu",function(e){s.scroller.contains(e.target)||sl(l,e)});var a,u={end:0};function c(){s.activeTouch&&(a=setTimeout(function(){return s.activeTouch=null},1e3),(u=s.activeTouch).end=+new Date)}function h(e,t){var n;return null==t.left||400<(n=t.left-e.left)*n+(n=t.top-e.top)*n}L(s.scroller,"touchstart",function(e){var t;A(l,e)||function(e){if(1==e.touches.length)return(e=e.touches[0]).radiusX<=1&&e.radiusY<=1}(e)||ll(l,e)||(s.input.ensurePolled(),clearTimeout(a),t=+new Date,s.activeTouch={start:t,moved:!1,prev:t-u.end<=300?u:null},1==e.touches.length&&(s.activeTouch.left=e.touches[0].pageX,s.activeTouch.top=e.touches[0].pageY))}),L(s.scroller,"touchmove",function(){s.activeTouch&&(s.activeTouch.moved=!0)}),L(s.scroller,"touchend",function(e){var t,n=s.activeTouch;n&&!kn(s,e)&&null!=n.left&&!n.moved&&new Date-n.start<300&&(t=l.coordsChar(s.activeTouch,"page"),n=!n.prev||h(n,n.prev)?new B(t,t):!n.prev.prev||h(n,n.prev.prev)?l.findWordAt(t):new B(H(t.line,0),P(l.doc,H(t.line+1,0))),l.setSelection(n.anchor,n.head),l.focus(),T(e)),c()}),L(s.scroller,"touchcancel",c),L(s.scroller,"scroll",function(){s.scroller.clientHeight&&(Fr(l,s.scroller.scrollTop),Er(l,s.scroller.scrollLeft,!0),O(l,"scroll",l))}),L(s.scroller,"mousewheel",function(e){return hi(l,e)}),L(s.scroller,"DOMMouseScroll",function(e){return hi(l,e)}),L(s.wrapper,"scroll",function(){return s.wrapper.scrollTop=s.wrapper.scrollLeft=0}),s.dragFunctions={enter:function(e){A(l,e)||Xe(e)},over:function(e){var t,n;A(l,e)||((n=cr(t=l,n=e))&&(yr(t,n,n=document.createDocumentFragment()),t.display.dragCursor||(t.display.dragCursor=M("div",null,"CodeMirror-cursors CodeMirror-dragcursors"),t.display.lineSpace.insertBefore(t.display.dragCursor,t.display.cursorDiv)),y(t.display.dragCursor,n)),Xe(e))},start:function(e){var t,n;t=l,e=e,w&&(!t.state.draggingText||+new Date-yo<100)?Xe(e):A(t,e)||kn(t.display,e)||(e.dataTransfer.setData("Text",t.getSelection()),e.dataTransfer.effectAllowed="copyMove",e.dataTransfer.setDragImage&&!V&&((n=M("img",null,null,"position: fixed; left: 0; top: 0;")).src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==",U&&(n.width=n.height=1,t.display.wrapper.appendChild(n),n._top=n.offsetTop),e.dataTransfer.setDragImage(n,0,0),U)&&n.parentNode.removeChild(n))},drop:I(l,bo),leave:function(e){A(l,e)||wo(l)}};o=s.input.getField();for(r in L(o,"keyup",function(e){return Qo.call(l,e)}),L(o,"keydown",I(l,Zo)),L(o,"keypress",I(l,Jo)),L(o,"focus",function(e){return Sr(l,e)}),L(o,"blur",function(e){return Lr(l,e)}),So(),jr(this),this.curOp.forceUpdate=!0,Si(this,i),t.autofocus&&!Y||this.hasFocus()?setTimeout(function(){n.hasFocus()&&!n.state.focused&&Sr(n)},20):Lr(this),hl)hl.hasOwnProperty(r)&&hl[r](this,t[r],ul);ni(this),t.finishInit&&t.finishInit(this);for(var d=0;do.first?ae(D(o,t-1).text,null,l):0:"add"==n?c=a+e.options.indentUnit:"subtract"==n?c=a-e.options.indentUnit:"number"==typeof n&&(c=a+n);var c=Math.max(0,c),h="",d=0;if(e.options.indentWithTabs)for(var f=Math.floor(c/l);f;--f)d+=l,h+="\t";if(dl,a=Qe(t),u=null;if(s&&1l?"cut":"+input")});qi(e.doc,f),b(e,"inputRead",e,f)}t&&!s&&wl(e,t),Ar(e),e.curOp.updateInput<2&&(e.curOp.updateInput=h),e.curOp.typing=!0,e.state.pasteIncoming=e.state.cutIncoming=-1}function bl(e,t){var n=e.clipboardData&&e.clipboardData.getData("Text");return n&&(e.preventDefault(),t.isReadOnly()||t.options.disableInput||h(t,function(){return yl(t,n,0,null,"paste")}),1)}function wl(e,t){if(e.options.electricChars&&e.options.smartIndent)for(var n=e.doc.sel,r=n.ranges.length-1;0<=r;r--){var i=n.ranges[r];if(!(100=n.first+n.size||(r=new H(e,r.ch,r.sticky),!(s=D(n,e))))return;r=Bo(l,n.cm,s,r.line,a)}else r=t;return 1}if("char"==o||"codepoint"==o)u();else if("column"==o)u(!0);else if("word"==o||"group"==o)for(var c=null,h="group"==o,d=n.cm&&n.cm.getHelper(r,"wordChars"),f=!0;!(i<0)||u(!f);f=!1){var p=s.text.charAt(r.ch)||"\n",p=Se(p,d)?"w":h&&"\n"==p?"n":!h||/\s/.test(p)?null:"p";if(h&&!f&&(p=p||"s"),c&&c!=p){i<0&&(i=1,u(),r.sticky="after");break}if(p&&(c=p),0=s.height){l.hitSide=!0;break}o+=5*n}return l}function i(e){this.cm=e,this.lastAnchorNode=this.lastAnchorOffset=this.lastFocusNode=this.lastFocusOffset=null,this.polling=new ue,this.composing=null,this.gracePeriod=!1,this.readDOMTimeout=null}function Tl(e,t){var n,r,i=Fn(e,t.line);return!i||i.hidden?null:(n=Wn(i,i=D(e.doc,t.line),t.line),r="left",(i=ze(i,e.doc.direction))&&(r=Ae(i,t.ch)%2?"right":"left"),(e=Rn(n.map,t.ch,r)).offset="right"==e.collapse?e.end:e.start,e)}function Ml(e,t){return t&&(e.bad=!0),e}function Nl(e,t,n){var r;if(t==e.display.lineDiv){if(!(r=e.display.lineDiv.childNodes[n]))return Ml(e.clipPos(H(e.display.viewTo-1)),!0);t=null,n=0}else for(r=t;;r=r.parentNode){if(!r||r==e.display.lineDiv)return null;if(r.parentNode&&r.parentNode==e.display.lineDiv)break}for(var i=0;i=t.display.viewTo||n.line=t.display.viewFrom&&Tl(t,r)||{node:i[0].measure.map[2],offset:0},r=n.linet.firstLine()&&(i=H(i.line-1,D(t.doc,i.line-1).length)),r.ch==D(t.doc,r.line).text.length&&r.linen.viewTo-1)return!1;var o,l=i.line==n.viewFrom||0==(l=hr(t,i.line))?(e=W(n.view[0].line),n.view[0].node):(e=W(n.view[l].line),n.view[l-1].node.nextSibling),r=hr(t,r.line),n=r==n.view.length-1?(o=n.viewTo-1,n.lineDiv.lastChild):(o=W(n.view[r+1].line)-1,n.view[r+1].node.previousSibling);if(!l)return!1;for(var s=t.doc.splitLines(function(o,e,t,l,s){var n="",a=!1,u=o.doc.lineSeparator(),c=!1;function h(){a&&(n+=u,c&&(n+=u),a=c=!1)}function d(e){e&&(h(),n+=e)}for(;!function e(t){if(1==t.nodeType){var n=t.getAttribute("cm-text");if(n)d(n);else if(n=t.getAttribute("cm-marker"))(n=o.findMarks(H(l,0),H(s+1,0),(i=+n,function(e){return e.id==i}))).length&&(n=n[0].find(0))&&d(ht(o.doc,n.from,n.to).join(u));else if("false"!=t.getAttribute("contenteditable")&&(n=/^(pre|div|p|li|table|br)$/i.test(t.nodeName),/^br$/i.test(t.nodeName)||0!=t.textContent.length)){n&&h();for(var r=0;ri.ch&&p.charCodeAt(p.length-c-1)==g.charCodeAt(g.length-c-1);)u--,c++;s[s.length-1]=p.slice(0,p.length-c).replace(/^\u200b+/,""),s[0]=s[0].slice(u).replace(/\u200b+$/,"");r=H(e,u),l=H(o,a.length?z(a).length-c:0);return 1n&&(gl(this,i.head.line,e,!0),n=i.head.line,r==this.doc.sel.primIndex)&&Ar(this);else{for(var o=i.from(),i=i.to(),l=Math.max(n,o.line),n=Math.min(this.lastLine(),i.line-(i.ch?0:1))+1,s=l;s>1;if((l?n[2*l-1]:0)>=o)i=l;else{if(!(n[2*l+1]l)&&e.top>t.offsetHeight?a=e.top-t.offsetHeight:e.bottom+t.offsetHeight<=l&&(a=e.bottom),u+t.offsetWidth>o&&(u=o-t.offsetWidth)),t.style.top=a+"px",t.style.left=t.style.right="","right"==i?(u=s.sizer.clientWidth-t.offsetWidth,t.style.right="0px"):("left"==i?u=0:"middle"==i&&(u=(s.sizer.clientWidth-t.offsetWidth)/2),t.style.left=u+"px"),n&&(r=this,l={left:u,top:a,right:u+t.offsetWidth,bottom:a+t.offsetHeight},null!=(l=Nr(r,l)).scrollTop&&Fr(r,l.scrollTop),null!=l.scrollLeft)&&Er(r,l.scrollLeft)},triggerOnKeyDown:n(Zo),triggerOnKeyPress:n(Jo),triggerOnKeyUp:Qo,triggerOnMouseDown:n(rl),execCommand:function(e){if(Uo.hasOwnProperty(e))return Uo[e].call(null,this)},triggerElectric:n(function(e){wl(this,e)}),findPosH:function(e,t,n,r){for(var i=1,o=(t<0&&(i=-1,t=-t),P(this.doc,e)),l=0;l span::selection,
+.cm-fat-cursor .CodeMirror-line > span > span::selection { background: transparent; }
+.cm-fat-cursor .CodeMirror-line::-moz-selection,
+.cm-fat-cursor .CodeMirror-line > span::-moz-selection,
+.cm-fat-cursor .CodeMirror-line > span > span::-moz-selection { background: transparent; }
+.cm-fat-cursor { caret-color: transparent; }
@-moz-keyframes blink {
- 0% { background: #7e7; }
- 50% { background: none; }
- 100% { background: #7e7; }
+ 0% {}
+ 50% { background-color: transparent; }
+ 100% {}
}
@-webkit-keyframes blink {
- 0% { background: #7e7; }
- 50% { background: none; }
- 100% { background: #7e7; }
+ 0% {}
+ 50% { background-color: transparent; }
+ 100% {}
}
@keyframes blink {
- 0% { background: #7e7; }
- 50% { background: none; }
- 100% { background: #7e7; }
+ 0% {}
+ 50% { background-color: transparent; }
+ 100% {}
}
/* Can style cursor different in overwrite (non-insert) mode */
-div.CodeMirror-overwrite div.CodeMirror-cursor {}
+.CodeMirror-overwrite .CodeMirror-cursor {}
.cm-tab { display: inline-block; text-decoration: inherit; }
+.CodeMirror-rulers {
+ position: absolute;
+ left: 0; right: 0; top: -50px; bottom: 0;
+ overflow: hidden;
+}
.CodeMirror-ruler {
border-left: 1px solid #ccc;
+ top: 0; bottom: 0;
position: absolute;
}
/* DEFAULT THEME */
+.cm-s-default .cm-header {color: blue;}
+.cm-s-default .cm-quote {color: #090;}
+.cm-negative {color: #d44;}
+.cm-positive {color: #292;}
+.cm-header, .cm-strong {font-weight: bold;}
+.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;}
@@ -113,7 +119,7 @@ div.CodeMirror-overwrite div.CodeMirror-cursor {}
.cm-s-default .cm-property,
.cm-s-default .cm-operator {}
.cm-s-default .cm-variable-2 {color: #05a;}
-.cm-s-default .cm-variable-3 {color: #085;}
+.cm-s-default .cm-variable-3, .cm-s-default .cm-type {color: #085;}
.cm-s-default .cm-comment {color: #a50;}
.cm-s-default .cm-string {color: #a11;}
.cm-s-default .cm-string-2 {color: #f50;}
@@ -123,25 +129,18 @@ div.CodeMirror-overwrite div.CodeMirror-cursor {}
.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-header {color: blue;}
-.cm-s-default .cm-quote {color: #090;}
.cm-s-default .cm-hr {color: #999;}
.cm-s-default .cm-link {color: #00c;}
-.cm-negative {color: #d44;}
-.cm-positive {color: #292;}
-.cm-header, .cm-strong {font-weight: bold;}
-.cm-em {font-style: italic;}
-.cm-link {text-decoration: underline;}
-.cm-strikethrough {text-decoration: line-through;}
-
.cm-s-default .cm-error {color: #f00;}
.cm-invalidchar {color: #f00;}
+.CodeMirror-composing { border-bottom: 2px solid; }
+
/* Default styles for common addons */
-div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
-div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
+div.CodeMirror span.CodeMirror-matchingbracket {color: #0b0;}
+div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #a22;}
.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
.CodeMirror-activeline-background {background: #e8f2ff;}
@@ -158,30 +157,28 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
.CodeMirror-scroll {
overflow: scroll !important; /* Things will break if this is overridden */
- /* 30px is the magic margin used to hide the element's real scrollbars */
+ /* 50px is the magic margin used to hide the element's real scrollbars */
/* See overflow: hidden in .CodeMirror */
- margin-bottom: -30px; margin-right: -30px;
- padding-bottom: 30px;
+ margin-bottom: -50px; margin-right: -50px;
+ padding-bottom: 50px;
height: 100%;
outline: none; /* Prevent dragging from highlighting the element */
position: relative;
- -moz-box-sizing: content-box;
- box-sizing: content-box;
+ z-index: 0;
}
.CodeMirror-sizer {
position: relative;
- border-right: 30px solid transparent;
- -moz-box-sizing: content-box;
- box-sizing: content-box;
+ border-right: 50px solid transparent;
}
/* The fake, visible scrollbars. Used to force redraw during scrolling
- before actuall scrolling happens, thus preventing shaking and
+ before actual scrolling happens, thus preventing shaking and
flickering artifacts. */
.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
position: absolute;
z-index: 6;
display: none;
+ outline: none;
}
.CodeMirror-vscrollbar {
right: 0; top: 0;
@@ -202,40 +199,41 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
.CodeMirror-gutters {
position: absolute; left: 0; top: 0;
+ min-height: 100%;
z-index: 3;
}
.CodeMirror-gutter {
white-space: normal;
height: 100%;
- -moz-box-sizing: content-box;
- box-sizing: content-box;
display: inline-block;
- margin-bottom: -30px;
- /* Hack to make IE7 behave */
- *zoom:1;
- *display:inline;
+ vertical-align: top;
+ margin-bottom: -50px;
}
.CodeMirror-gutter-wrapper {
position: absolute;
z-index: 4;
- height: 100%;
+ background: none !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 {
- -webkit-user-select: none;
- -moz-user-select: none;
- user-select: none;
-}
+.CodeMirror-gutter-wrapper ::selection { background-color: transparent }
+.CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent }
.CodeMirror-lines {
cursor: text;
min-height: 1px; /* prevents collapsing before first draw */
}
-.CodeMirror pre {
+.CodeMirror pre.CodeMirror-line,
+.CodeMirror pre.CodeMirror-line-like {
/* Reset some styles that the rest of the page might have set */
-moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
border-width: 0;
@@ -251,8 +249,11 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
position: relative;
overflow: visible;
-webkit-tap-highlight-color: transparent;
+ -webkit-font-variant-ligatures: contextual;
+ font-variant-ligatures: contextual;
}
-.CodeMirror-wrap pre {
+.CodeMirror-wrap pre.CodeMirror-line,
+.CodeMirror-wrap pre.CodeMirror-line-like {
word-wrap: break-word;
white-space: pre-wrap;
word-break: normal;
@@ -267,15 +268,27 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
.CodeMirror-linewidget {
position: relative;
z-index: 2;
- overflow: auto;
+ padding: 0.1px; /* Force widget margins to stay inside of the container */
}
.CodeMirror-widget {}
+.CodeMirror-rtl pre { direction: rtl; }
+
.CodeMirror-code {
outline: none;
}
+/* Force content-box sizing for the elements where we expect it */
+.CodeMirror-scroll,
+.CodeMirror-sizer,
+.CodeMirror-gutter,
+.CodeMirror-gutters,
+.CodeMirror-linenumber {
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
+}
+
.CodeMirror-measure {
position: absolute;
width: 100%;
@@ -283,19 +296,22 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
overflow: hidden;
visibility: hidden;
}
-.CodeMirror-measure pre { position: static; }
-.CodeMirror div.CodeMirror-cursor {
+.CodeMirror-cursor {
position: absolute;
- border-right: none;
- width: 0;
+ 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;
}
@@ -303,17 +319,14 @@ div.CodeMirror-cursors {
.CodeMirror-selected { background: #d9d9d9; }
.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
.CodeMirror-crosshair { cursor: crosshair; }
-.CodeMirror ::selection { background: #d7d4f0; }
-.CodeMirror ::-moz-selection { background: #d7d4f0; }
+.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);
+ background-color: #ffa;
+ background-color: rgba(255, 255, 0, .4);
}
-/* IE7 hack to prevent it from returning funny offsetTops on the spans */
-.CodeMirror span { *vertical-align: text-bottom; }
-
/* Used to force a border model for a node */
.cm-force-border { padding-right: .1px; }
diff --git a/static/editor.md/lib/codemirror/lib/codemirror.js b/static/editor.md/lib/codemirror/lib/codemirror.js
index 64022c089..8686b788f 100644
--- a/static/editor.md/lib/codemirror/lib/codemirror.js
+++ b/static/editor.md/lib/codemirror/lib/codemirror.js
@@ -1,2429 +1,2326 @@
-// CodeMirror v5.0, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: https://codemirror.net/LICENSE
-// This is CodeMirror (http://codemirror.net), a code editor
+// This is CodeMirror (https://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(mod) {
- if (typeof exports == "object" && typeof module == "object") // CommonJS
- module.exports = mod();
- else if (typeof define == "function" && define.amd) // AMD
- return define([], mod);
- else // Plain browser env
- this.CodeMirror = mod();
-})(function() {
- "use strict";
-
- // BROWSER SNIFFING
+(function (global, factory) {
+ typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
+ typeof define === 'function' && define.amd ? define(factory) :
+ (global = global || self, global.CodeMirror = factory());
+}(this, (function () { 'use strict';
// Kludges for bugs and behavior differences that can't be feature
// detected are enabled based on userAgent etc sniffing.
-
- var gecko = /gecko\/\d/i.test(navigator.userAgent);
- var ie_upto10 = /MSIE \d/.test(navigator.userAgent);
- var ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(navigator.userAgent);
- var ie = ie_upto10 || ie_11up;
- var ie_version = ie && (ie_upto10 ? document.documentMode || 6 : ie_11up[1]);
- var webkit = /WebKit\//.test(navigator.userAgent);
- var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(navigator.userAgent);
- var chrome = /Chrome\//.test(navigator.userAgent);
- var presto = /Opera\//.test(navigator.userAgent);
+ 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(navigator.userAgent);
- var phantom = /PhantomJS/.test(navigator.userAgent);
+ var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(userAgent);
+ var phantom = /PhantomJS/.test(userAgent);
- var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent);
+ var ios = safari && (/Mobile\/\w+/.test(userAgent) || navigator.maxTouchPoints > 2);
+ 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(navigator.userAgent);
- var mac = ios || /Mac/.test(navigator.platform);
- var windows = /win/i.test(navigator.platform);
+ 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 && navigator.userAgent.match(/Version\/(\d*\.\d*)/);
- if (presto_version) presto_version = Number(presto_version[1]);
+ 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);
- // Optimize some code when these features are not used.
- var sawReadOnlySpans = false, sawCollapsedSpans = false;
-
- // EDITOR CONSTRUCTOR
+ function classTest(cls) { return new RegExp("(^|\\s)" + cls + "(?:$|\\s)\\s*") }
- // A CodeMirror instance represents an editor. This is the object
- // that user code is usually dealing with.
+ 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 CodeMirror(place, options) {
- if (!(this instanceof CodeMirror)) return new CodeMirror(place, options);
+ function removeChildren(e) {
+ for (var count = e.childNodes.length; count > 0; --count)
+ { e.removeChild(e.firstChild); }
+ return e
+ }
- this.options = options = options ? copyObj(options) : {};
- // Determine effective options based on given values and defaults.
- copyObj(defaults, options, false);
- setGuttersForLineNumbers(options);
+ function removeChildrenAndAdd(parent, e) {
+ return removeChildren(parent).appendChild(e)
+ }
- var doc = options.value;
- if (typeof doc == "string") doc = new Doc(doc, options.mode);
- this.doc = doc;
+ 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 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";
- if (options.autofocus && !mobile) display.input.focus();
- initScrollbars(this);
+ 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
+ }; }
- 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, 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
- draggingText: false,
- highlight: new Delayed(), // stores highlight worker timeout
- keySeq: null // Unfinished key sequence
- };
+ 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)
+ }
- var cm = this;
+ 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
+ }
- // Override magic textarea content restore that IE sometimes does
- // on our hidden textarea on reload
- if (ie && ie_version < 11) setTimeout(function() { cm.display.input.reset(true); }, 20);
+ 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
+ }
- registerEventHandlers(this);
- ensureGlobalHandlers();
+ 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) {} }; }
- startOperation(this);
- this.curOp.forceUpdate = true;
- attachDoc(this, doc);
+ function bind(f) {
+ var args = Array.prototype.slice.call(arguments, 1);
+ return function(){return f.apply(null, args)}
+ }
- if ((options.autofocus && !mobile) || cm.hasFocus())
- setTimeout(bind(onFocus, this), 20);
- else
- onBlur(this);
+ 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
+ }
- for (var opt in optionHandlers) if (optionHandlers.hasOwnProperty(opt))
- optionHandlers[opt](this, options[opt], Init);
- maybeUpdateLineNumberWidth(this);
- if (options.finishInit) options.finishInit(this);
- for (var i = 0; i < initHooks.length; ++i) initHooks[i](this);
- 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";
+ // 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;
+ }
}
- // DISPLAY CONSTRUCTOR
+ var Delayed = function() {
+ this.id = null;
+ this.f = null;
+ this.time = 0;
+ this.handler = bind(this.onTimeout, this);
+ };
+ Delayed.prototype.onTimeout = function (self) {
+ self.id = 0;
+ if (self.time <= +new Date) {
+ self.f();
+ } else {
+ setTimeout(self.handler, self.time - +new Date);
+ }
+ };
+ Delayed.prototype.set = function (ms, f) {
+ this.f = f;
+ var time = +new Date + ms;
+ if (!this.id || time < this.time) {
+ clearTimeout(this.id);
+ this.id = setTimeout(this.handler, ms);
+ this.time = time;
+ }
+ };
- // The display handles the DOM integration, both for input reading
- // and content drawing. It holds references to DOM nodes and
- // display-related state.
+ function indexOf(array, elt) {
+ for (var i = 0; i < array.length; ++i)
+ { if (array[i] == elt) { return i } }
+ return -1
+ }
- function Display(place, doc, input) {
- var d = this;
- this.input = input;
+ // Number of pixels added to scroller and sizer to hide scrollbar
+ var scrollerGap = 50;
- // 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 = elt("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 = elt("div", [d.measure, d.lineMeasure, d.selectionDiv, d.cursorDiv, d.lineDiv],
- null, "position: relative; outline: none");
- // Moved around its parent to cover visible view.
- d.mover = elt("div", [elt("div", [d.lineSpace], "CodeMirror-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");
+ // Returned or thrown by various protocols to signal 'I'm not
+ // handling this'.
+ var Pass = {toString: function(){return "CodeMirror.Pass"}};
- // 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;
+ // Reused option objects for setSelection & friends
+ var sel_dontScroll = {scroll: false}, sel_mouse = {origin: "*mouse"}, sel_move = {origin: "+move"};
- if (place) {
- if (place.appendChild) place.appendChild(d.wrapper);
- else place(d.wrapper);
+ // 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 }
}
+ }
- // 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;
+ var spaceStrs = [""];
+ function spaceStr(n) {
+ while (spaceStrs.length <= n)
+ { spaceStrs.push(lst(spaceStrs) + " "); }
+ return spaceStrs[n]
+ }
- d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null;
+ function lst(arr) { return arr[arr.length-1] }
- // 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;
+ function map(array, f) {
+ var out = [];
+ for (var i = 0; i < array.length; i++) { out[i] = f(array[i], i); }
+ return out
+ }
- // Used for measuring wheel scrolling granularity
- d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null;
+ 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);
+ }
- // True when shift is held down.
- d.shift = false;
+ function nothing() {}
- // Used to track whether anything happened since the context menu
- // was opened.
- d.selForContextMenu = null;
+ 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
+ }
- d.activeTouch = null;
+ 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)
+ }
- input.init(d);
+ function isEmpty(obj) {
+ for (var n in obj) { if (obj.hasOwnProperty(n) && obj[n]) { return false } }
+ return true
}
- // STATE UPDATES
+ // 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) }
- // Used to get the editor into a consistent state again when options change.
+ // 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
+ }
- function loadMode(cm) {
- cm.doc.mode = CodeMirror.getMode(cm.options, cm.doc.modeOption);
- resetModeState(cm);
+ // Returns the value from the range [`from`; `to`] that satisfies
+ // `pred` and is closest to `from`. Assumes that at least `to`
+ // satisfies `pred`. Supports `from` being greater than `to`.
+ function findFirst(pred, from, to) {
+ // At any point we are certain `to` satisfies `pred`, don't know
+ // whether `from` does.
+ var dir = from > to ? -1 : 1;
+ for (;;) {
+ if (from == to) { return from }
+ var midF = (from + to) / 2, mid = dir < 0 ? Math.ceil(midF) : Math.floor(midF);
+ if (mid == from) { return pred(mid) ? from : to }
+ if (pred(mid)) { to = mid; }
+ else { from = mid + dir; }
+ }
}
- function resetModeState(cm) {
- cm.doc.iter(function(line) {
- if (line.stateAfter) line.stateAfter = null;
- if (line.styles) line.styles = null;
- });
- cm.doc.frontier = cm.doc.first;
- startWorker(cm, 100);
- cm.state.modeGen++;
- if (cm.curOp) regChange(cm);
+ // BIDI HELPERS
+
+ function iterateBidiSections(order, from, to, f) {
+ if (!order) { return f(from, to, "ltr", 0) }
+ 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", i);
+ found = true;
+ }
+ }
+ if (!found) { f(from, to, "ltr"); }
}
- 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(){updateScrollbars(cm);}, 100);
- }
-
- // 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;
+ 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 (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);
- });
+ if (cur.from == ch) {
+ if (cur.from != cur.to && sticky != "before") { found = i; }
+ else { bidiOther = i; }
+ }
+ }
+ return found != null ? found : bidiOther
}
- 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);
- }
+ // Bidirectional ordering algorithm
+ // See http://unicode.org/reports/tr9/tr9-13.html for the algorithm
+ // that this (partially) implements.
- function guttersChanged(cm) {
- updateGutters(cm);
- regChange(cm);
- setTimeout(function(){alignHorizontally(cm);}, 20);
- }
+ // 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
- // 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);
- for (var i = 0; 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";
- }
+ // 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" }
}
- gutters.style.display = i ? "" : "none";
- updateGutterSpace(cm);
- }
- function updateGutterSpace(cm) {
- var width = cm.display.gutters.offsetWidth;
- cm.display.sizer.style.marginLeft = width + "px";
- }
+ var bidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/;
+ var isNeutral = /[stwN]/, isStrong = /[LRr]/, countsAsLeft = /[Lb1n]/, countsAsNum = /[1n]/;
- // 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 = merged.find(0, true);
- len -= cur.text.length - found.from.ch;
- cur = found.to.line;
- len += cur.text.length - found.to.ch;
+ function BidiSpan(level, from, to) {
+ this.level = level;
+ this.from = from; this.to = to;
}
- 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;
- }
- });
- }
+ return function(str, direction) {
+ var outerType = direction == "ltr" ? "L" : "R";
- // 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);
- }
- }
+ 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))); }
- // SCROLLBARS
+ // 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; }
+ }
- // 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
- };
- }
+ // 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"; } }
+ }
- function NativeScrollbars(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);
+ // 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;
+ }
- on(vert, "scroll", function() {
- if (vert.clientHeight) scroll(vert.scrollTop, "vertical");
- });
- on(horiz, "scroll", function() {
- if (horiz.clientWidth) scroll(horiz.scrollLeft, "horizontal");
- });
+ // 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;
+ }
+ }
- this.checkedOverlay = 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 = copyObj({
- 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";
+ // 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; }
}
- 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 =
- (measure.scrollWidth - measure.clientWidth + totalWidth) + "px";
- } else {
- this.horiz.style.display = "";
- this.horiz.firstChild.style.width = "0";
+ // 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;
+ }
}
- if (!this.checkedOverlay && measure.clientHeight > 0) {
- if (sWidth == 0) this.overlayHack();
- this.checkedOverlay = true;
+ // 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, isRTL = direction == "rtl" ? 1 : 0;
+ 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)); at += isRTL; }
+ 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));
+ at += isRTL;
+ pos = j$2;
+ } else { ++j$2; }
+ }
+ if (pos < i$7) { order.splice(at, 0, new BidiSpan(1, pos, i$7)); }
+ }
+ }
+ if (direction == "ltr") {
+ 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 {right: needsV ? sWidth : 0, bottom: needsH ? sWidth : 0};
- },
- setScrollLeft: function(pos) {
- if (this.horiz.scrollLeft != pos) this.horiz.scrollLeft = pos;
- },
- setScrollTop: function(pos) {
- if (this.vert.scrollTop != pos) this.vert.scrollTop = pos;
- },
- overlayHack: function() {
- var w = mac && !mac_geMountainLion ? "12px" : "18px";
- this.horiz.style.minHeight = this.vert.style.minWidth = w;
- var self = this;
- var barMouseDown = function(e) {
- if (e_target(e) != self.vert && e_target(e) != self.horiz)
- operation(self.cm, onMouseDown)(e);
- };
- on(this.vert, "mousedown", barMouseDown);
- on(this.horiz, "mousedown", barMouseDown);
- },
- clear: function() {
- var parent = this.horiz.parentNode;
- parent.removeChild(this.horiz);
- parent.removeChild(this.vert);
+ return direction == "rtl" ? order.reverse() : order
}
- }, NativeScrollbars.prototype);
+ })();
+
+ // 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 NullScrollbars() {}
+ // EVENT HANDLING
- NullScrollbars.prototype = copyObj({
- update: function() { return {bottom: 0, right: 0}; },
- setScrollLeft: function() {},
- setScrollTop: function() {},
- clear: function() {}
- }, NullScrollbars.prototype);
+ // Lightweight event framework. on/off also work on DOM nodes,
+ // registering native DOM handlers.
- CodeMirror.scrollbarModel = {"native": NativeScrollbars, "null": NullScrollbars};
+ var noHandlers = [];
- function initScrollbars(cm) {
- if (cm.display.scrollbars) {
- cm.display.scrollbars.clear();
- if (cm.display.scrollbars.addClass)
- rmClass(cm.display.wrapper, cm.display.scrollbars.addClass);
+ 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);
}
+ };
- cm.display.scrollbars = new CodeMirror.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() { cm.display.input.focus(); }, 0);
- });
- node.setAttribute("cm-not-content", "true");
- }, function(pos, axis) {
- if (axis == "horizontal") setScrollLeft(cm, pos);
- else setScrollTop(cm, pos);
- }, cm);
- if (cm.display.scrollbars.addClass)
- addClass(cm.display.wrapper, cm.display.scrollbars.addClass);
+ function getHandlers(emitter, type) {
+ return emitter._handlers && emitter._handlers[type] || noHandlers
}
- 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;
+ 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)); }
+ }
}
}
- // 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);
+ 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); }
+ }
- d.sizer.style.paddingRight = (d.barWidth = sizes.right) + "px";
- d.sizer.style.paddingBottom = (d.barHeight = sizes.bottom) + "px";
+ // 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
+ }
- 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 = "";
+ 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]); } }
}
- // 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)};
+ function hasHandler(emitter, type) {
+ return getHandlers(emitter, type).length > 0
}
- // LINE NUMBERS
-
- // 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 && view[i].gutter)
- view[i].gutter.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";
+ // 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);};
}
- // 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);
- 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;
- }
+ // Due to the fact that we still support jurassic IE versions, some
+ // compatibility wrappers are needed.
- function lineNumberFor(options, i) {
- return String(options.lineNumberFormatter(i + options.firstLineNumber));
+ function e_preventDefault(e) {
+ if (e.preventDefault) { e.preventDefault(); }
+ else { e.returnValue = false; }
}
-
- // 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;
+ function e_stopPropagation(e) {
+ if (e.stopPropagation) { e.stopPropagation(); }
+ else { e.cancelBubble = true; }
}
-
- // DISPLAY DRAWING
-
- function DisplayUpdate(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 = [];
+ function e_defaultPrevented(e) {
+ return e.defaultPrevented != null ? e.defaultPrevented : e.returnValue == false
}
+ function e_stop(e) {e_preventDefault(e); e_stopPropagation(e);}
- DisplayUpdate.prototype.signal = function(emitter, type) {
- if (hasHandler(emitter, type))
- this.events.push(arguments);
- };
- DisplayUpdate.prototype.finish = function() {
- for (var i = 0; i < this.events.length; i++)
- signal.apply(null, this.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 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
}
- // 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;
+ // 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
+ }();
- if (update.editorIsHidden) {
- resetView(cm);
- return false;
+ 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
+ }
- // 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;
+ // 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)
+ }
- if (maybeUpdateLineNumberWidth(cm)) {
- resetView(cm);
- update.dims = getDimensions(cm);
+ // 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/); };
- // 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 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 different = from != display.viewFrom || to != display.viewTo ||
- display.lastWrapHeight != update.wrapperHeight || display.lastWrapWidth != update.wrapperWidth;
- adjustView(cm, from, to);
+ var hasCopyEvent = (function () {
+ var e = elt("div");
+ if ("oncopy" in e) { return true }
+ e.setAttribute("oncopy", "return;");
+ return typeof e.oncopy == "function"
+ })();
- 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 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 toUpdate = countDirtyView(cm);
- if (!different && toUpdate == 0 && !update.force && display.renderedView == display.view &&
- (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo))
- return false;
+ // Known modes, by name and by MIME
+ var modes = {}, mimeModes = {};
- // For big changes, we hide the enclosing element during the
- // update, since that speeds up the operations on most browsers.
- var focused = activeElt();
- 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.
- if (focused && activeElt() != focused && focused.offsetHeight) focused.focus();
+ // 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;
+ }
- // Prevent selection and cursors from interfering with the scroll
- // width and height.
- removeChildren(display.cursorDiv);
- removeChildren(display.selectionDiv);
- display.gutters.style.height = 0;
+ function defineMIME(mime, spec) {
+ mimeModes[mime] = spec;
+ }
- if (different) {
- display.lastWrapHeight = update.wrapperHeight;
- display.lastWrapWidth = update.wrapperWidth;
- startWorker(cm, 400);
+ // 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")
}
-
- display.updateLineNumbers = null;
-
- return true;
+ if (typeof spec == "string") { return {name: spec} }
+ else { return spec || {name: "null"} }
}
- function postUpdateDisplay(cm, update) {
- var force = update.force, viewport = update.viewport;
- for (var first = true;; first = false) {
- if (first && cm.options.lineWrapping && update.oldDisplayWidth != displayWidth(cm)) {
- force = true;
- } else {
- force = false;
- // 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;
+ // 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];
}
- if (!updateDisplayIfNeeded(cm, update)) break;
- updateHeightsInViewport(cm);
- var barMeasure = measureForScrollbars(cm);
- updateSelection(cm);
- setDocumentHeight(cm, barMeasure);
- updateScrollbars(cm, barMeasure);
}
+ 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]; } }
- 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;
+ 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
}
- 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);
- setDocumentHeight(cm, barMeasure);
- updateScrollbars(cm, barMeasure);
- update.finish();
+ // 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 setDocumentHeight(cm, measure) {
- cm.display.sizer.style.minHeight = measure.docHeight + "px";
- var total = measure.docHeight + cm.display.barHeight;
- cm.display.heightForcer.style.top = total + "px";
- cm.display.gutters.style.height = Math.max(total + scrollGap(cm), measure.clientHeight) + "px";
+ function startState(mode, a1, a2) {
+ return mode.startState ? mode.startState(a1, a2) : true
}
- // 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;
- 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 > .001 || diff < -.001) {
- 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.offsetHeight;
- }
+ // STRING STREAM
- // 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};
- }
+ // Fed to the mode parsers, provides helper functions to make
+ // parsers more succinct.
- // 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;
+ 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;
+ };
- 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;
+ 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 start = this.pos;
+ while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) { ++this.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;
}
-
- 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;
+ 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
}
- lineN += lineView.size;
- }
- while (cur) cur = rm(cur);
- }
-
- // 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(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;
+ } 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
}
- return lineView.node;
- }
+ };
+ 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)
+ };
+ StringStream.prototype.baseToken = function () {
+ var oracle = this.lineOracle;
+ return oracle && oracle.baseToken(this.pos)
+ };
- function updateLineBackground(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);
+ // 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]
}
- // 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);
+ // 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
}
-
- // 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(lineView);
- } else if (cls) {
- lineView.text.className = cls;
- }
+ // 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
}
- function updateLineClasses(lineView) {
- updateLineBackground(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 || "";
+ // 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; } }
}
- function updateLineGutter(cm, lineView, lineN, dims) {
- if (lineView.gutter) {
- lineView.node.removeChild(lineView.gutter);
- lineView.gutter = null;
- }
- var markers = lineView.line.gutterMarkers;
- if (cm.options.lineNumbers || markers) {
- var wrap = ensureLineWrapped(lineView);
- var gutterWrap = lineView.gutter = elt("div", null, "CodeMirror-gutter-wrapper", "left: " +
- (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) +
- "px; width: " + dims.gutterTotalWidth + "px");
- cm.display.input.setUneditable(gutterWrap);
- wrap.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"));
+ // 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
}
- function updateLineWidgets(cm, lineView, dims) {
- if (lineView.alignable) lineView.alignable = null;
- for (var node = lineView.node.firstChild, next; node; node = next) {
- var next = node.nextSibling;
- if (node.className == "CodeMirror-linewidget")
- lineView.node.removeChild(node);
+ // 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;
}
- insertLineWidgets(cm, lineView, dims);
+ return n + i
}
- // 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(lineView);
- updateLineGutter(cm, lineView, lineN, dims);
- insertLineWidgets(cm, lineView, dims);
- return lineView.node;
- }
+ function isLine(doc, l) {return l >= doc.first && l < doc.first + doc.size}
- // 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 lineNumberFor(options, i) {
+ return String(options.lineNumberFormatter(i + options.firstLineNumber))
}
- 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");
- }
- }
+ // A Pos instance represents a position within the text.
+ function Pos(line, ch, sticky) {
+ if ( sticky === void 0 ) sticky = null;
- 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";
- }
+ if (!(this instanceof Pos)) { return new Pos(line, ch, sticky) }
+ this.line = line;
+ this.ch = ch;
+ this.sticky = sticky;
}
- // POSITION OBJECT
-
- // A Pos instance represents a position within the text.
- var Pos = CodeMirror.Pos = function(line, ch) {
- if (!(this instanceof Pos)) return new Pos(line, ch);
- this.line = line; this.ch = ch;
- };
-
// Compare two positions, return 0 if they are the same, a negative
// number when a is less, and a positive number otherwise.
- var cmp = CodeMirror.cmpPos = function(a, b) { return a.line - b.line || a.ch - b.ch; };
+ function cmp(a, b) { return a.line - b.line || a.ch - b.ch }
- 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; }
+ function equalCursorPos(a, b) { return a.sticky == b.sticky && cmp(a, b) == 0 }
- // INPUT HANDLING
+ 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 }
- function ensureFocus(cm) {
- if (!cm.state.focused) { cm.display.input.focus(); onFocus(cm); }
+ // 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 isReadOnly(cm) {
- return cm.options.readOnly || cm.doc.cantEdit;
+ 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
}
- // This will be set to an array of strings when copying, so that,
- // when pasting, we know what kind of selections the copied text
- // was made out of.
- var lastCopied = null;
+ var SavedContext = function(state, lookAhead) {
+ this.state = state;
+ this.lookAhead = lookAhead;
+ };
- function applyTextInput(cm, inserted, deleted, sel) {
- var doc = cm.doc;
- cm.display.shift = false;
- if (!sel) sel = doc.sel;
-
- var textLines = splitLines(inserted), multiPaste = null;
- // When pasing N lines into N selections, insert one line per selection
- if (cm.state.pasteIncoming && sel.ranges.length > 1) {
- if (lastCopied && lastCopied.join("\n") == inserted)
- multiPaste = sel.ranges.length % lastCopied.length == 0 && map(lastCopied, splitLines);
- else if (textLines.length == sel.ranges.length)
- multiPaste = map(textLines, function(l) { return [l]; });
- }
-
- // Normal behavior is to insert the new text into every selection
- for (var i = sel.ranges.length - 1; i >= 0; i--) {
- var range = sel.ranges[i];
- 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 && !cm.state.pasteIncoming) // Handle overwrite
- to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + lst(textLines).length));
- }
- var updateInput = cm.curOp.updateInput;
- var changeEvent = {from: from, to: to, text: multiPaste ? multiPaste[i % multiPaste.length] : textLines,
- origin: cm.state.pasteIncoming ? "paste" : cm.state.cutIncoming ? "cut" : "+input"};
- makeChange(cm.doc, changeEvent);
- signalLater(cm, "inputRead", cm, changeEvent);
- // When an 'electric' character is inserted, immediately trigger a reindent
- if (inserted && !cm.state.pasteIncoming && cm.options.electricChars &&
- cm.options.smartIndent && range.head.ch < 100 &&
- (!i || sel.ranges[i - 1].head.line != range.head.line)) {
- var mode = cm.getModeAt(range.head);
- var end = changeEnd(changeEvent);
- if (mode.electricChars) {
- for (var j = 0; j < mode.electricChars.length; j++)
- if (inserted.indexOf(mode.electricChars.charAt(j)) > -1) {
- indentLine(cm, end.line, "smart");
- break;
- }
- } else if (mode.electricInput) {
- if (mode.electricInput.test(getLine(doc, end.line).text.slice(0, end.ch)))
- indentLine(cm, end.line, "smart");
- }
- }
- }
- ensureCursorVisible(cm);
- cm.curOp.updateInput = updateInput;
- cm.curOp.typing = true;
- cm.state.pasteIncoming = cm.state.cutIncoming = false;
- }
-
- 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) {
- field.setAttribute("autocorrect", "off");
- field.setAttribute("autocapitalize", "off");
- field.setAttribute("spellcheck", "false");
- }
-
- // TEXTAREA INPUT STYLE
+ var Context = function(doc, state, line, lookAhead) {
+ this.state = state;
+ this.doc = doc;
+ this.line = line;
+ this.maxLookAhead = lookAhead || 0;
+ this.baseTokens = null;
+ this.baseTokenPos = 1;
+ };
- function TextareaInput(cm) {
- this.cm = cm;
- // See input.poll and input.reset
- this.prevInput = "";
+ Context.prototype.lookAhead = function (n) {
+ var line = this.doc.getLine(this.line + n);
+ if (line != null && n > this.maxLookAhead) { this.maxLookAhead = n; }
+ return line
+ };
- // 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();
- // Tracks when input.reset has punted to just putting a short
- // string into the textarea instead of the full selection.
- this.inaccurateSelection = false;
- // Used to work around IE issue with selection being forgotten when focus moves away from textarea
- this.hasSelection = false;
+ Context.prototype.baseToken = function (n) {
+ if (!this.baseTokens) { return null }
+ while (this.baseTokens[this.baseTokenPos] <= n)
+ { this.baseTokenPos += 2; }
+ var type = this.baseTokens[this.baseTokenPos + 1];
+ return {type: type && type.replace(/( |^)overlay .*/, ""),
+ size: this.baseTokens[this.baseTokenPos] - n}
};
- function hiddenTextarea() {
- var te = elt("textarea", null, null, "position: absolute; 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;
- }
+ Context.prototype.nextLine = function () {
+ this.line++;
+ if (this.maxLookAhead > 0) { this.maxLookAhead--; }
+ };
- TextareaInput.prototype = copyObj({
- init: function(display) {
- var input = this, cm = this.cm;
+ 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) }
+ };
- // 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);
+ 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
+ };
- // 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 && input.hasSelection) input.hasSelection = null;
- input.poll();
- });
+ // 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;
- on(te, "paste", function() {
- // Workaround for webkit bug https://bugs.webkit.org/show_bug.cgi?id=90206
- // Add a char to the end of textarea before paste occur so that
- // selection doesn't span to the end of textarea.
- if (webkit && !cm.state.fakedLastChar && !(new Date - cm.state.lastMiddleDown < 200)) {
- var start = te.selectionStart, end = te.selectionEnd;
- te.value += "$";
- // The selection end needs to be set before the start, otherwise there
- // can be an intermediate non-empty selection between the two, which
- // can override the middle-click paste buffer on linux and cause the
- // wrong thing to get pasted.
- te.selectionEnd = end;
- te.selectionStart = start;
- cm.state.fakedLastChar = true;
+ // Run overlays, adjust style array.
+ var loop = function ( o ) {
+ context.baseTokens = st;
+ 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);
}
- cm.state.pasteIncoming = true;
- input.fastPoll();
- });
-
- function prepareCopyCut(e) {
- if (cm.somethingSelected()) {
- lastCopied = cm.getSelections();
- if (input.inaccurateSelection) {
- input.prevInput = "";
- input.inaccurateSelection = false;
- te.value = lastCopied.join("\n");
- selectInput(te);
- }
+ if (!style) { return }
+ if (overlay.opaque) {
+ st.splice(start, i - start, end, "overlay " + style);
+ i = start + 2;
} else {
- var ranges = copyableRanges(cm);
- lastCopied = ranges.text;
- if (e.type == "cut") {
- cm.setSelections(ranges.ranges, null, sel_dontScroll);
- } else {
- input.prevInput = "";
- te.value = ranges.text.join("\n");
- selectInput(te);
+ for (; start < i; start += 2) {
+ var cur = st[start+1];
+ st[start+1] = (cur ? cur + " " : "") + "overlay " + style;
}
}
- 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)) 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);
- });
- },
-
- 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;
- },
+ }, lineClasses);
+ context.state = state;
+ context.baseTokens = null;
+ context.baseTokenPos = 1;
+ };
- 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";
- }
- },
+ for (var o = 0; o < cm.state.overlays.length; ++o) loop( o );
- // Reset the input to correspond to the selection (or to be empty,
- // when not typing and nothing is selected)
- reset: function(typing) {
- if (this.contextMenuPending) return;
- var minimal, selected, cm = this.cm, doc = cm.doc;
- if (cm.somethingSelected()) {
- this.prevInput = "";
- var range = doc.sel.primary();
- minimal = hasCopyEvent &&
- (range.to().line - range.from().line > 100 || (selected = cm.getSelection()).length > 1000);
- var content = minimal ? "-" : selected || 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;
- }
- this.inaccurateSelection = minimal;
- },
+ return {styles: st, classes: lineClasses.bgClass || lineClasses.textClass ? lineClasses : null}
+ }
- getField: function() { return this.textarea; },
+ 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
+ }
- supportsTouch: function() { return false; },
+ 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
+ }
- 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
- }
- },
+ // 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;
+ }
+ }
- blur: function() { this.textarea.blur(); },
+ 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) }
+ }
- resetPosition: function() {
- this.wrapper.style.top = this.wrapper.style.left = 0;
- },
+ 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.")
+ }
- receivedFocus: function() { this.slowPoll(); },
+ 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;
+ };
- // Poll for input changes, using the normal rate of polling. This
- // runs as long as the editor is focused.
- slowPoll: function() {
- var input = this;
- if (input.pollingFast) return;
- input.polling.set(this.cm.options.pollInterval, function() {
- input.poll();
- if (input.cm.state.focused) input.slowPoll();
- });
- },
+ // 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)
+ }
- // 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.
- 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);
- },
+ 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
+ }
- // 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).
- poll: function() {
- 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 (!cm.state.focused || (hasSelection(input) && !prevInput) ||
- isReadOnly(cm) || cm.options.disableInput || cm.state.keySeq)
- return false;
- // See paste handler for more on the fakedLastChar kludge
- if (cm.state.pasteIncoming && cm.state.fakedLastChar) {
- input.value = input.value.substring(0, input.value.length - 1);
- cm.state.fakedLastChar = 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;
+ // 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 (text.charCodeAt(0) == 0x200b && cm.doc.sel == cm.display.selForContextMenu && !prevInput)
- prevInput = "\u200b";
- // 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;
-
- var self = this;
- runInOp(cm, function() {
- applyTextInput(cm, text.slice(same), prevInput.length - same);
-
- // Don't leave long text in the textarea, since it makes further polling slow
- if (text.length > 1000 || text.indexOf("\n") > -1) input.value = self.prevInput = "";
- else self.prevInput = text;
- });
- return true;
- },
-
- ensurePolled: function() {
- if (this.pollingFast && this.poll()) this.pollingFast = false;
- },
-
- onKeyPress: function() {
- if (ie && ie_version >= 9) this.hasSelection = null;
- this.fastPoll();
- },
-
- 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;
- input.wrapper.style.position = "absolute";
- te.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) +
- "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: " +
- (ie ? "rgba(255, 255, 255, .05)" : "transparent") +
- "; outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);";
- if (webkit) var 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 = te.value = "\u200b" + (selected ? te.value : "");
- 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;
- }
+ if (inner) {
+ var mName = inner[0].name;
+ if (mName) { style = "m-" + (style ? mName + " " + style : mName); }
}
- function rehide() {
- input.contextMenuPending = false;
- input.wrapper.style.position = "relative";
- 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)
- operation(cm, commands.selectAll)(cm);
- else if (i++ < 10) display.detectingSelectAll = setTimeout(poll, 500);
- else display.input.reset();
- };
- display.detectingSelectAll = setTimeout(poll, 200);
+ 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;
+ }
+ }
- 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);
+ // 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
+ }
- setUneditable: nothing,
+ 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);
+ }
- needsContentAttribute: false
- }, TextareaInput.prototype);
+ // Optimize some code when these features are not used.
+ var sawReadOnlySpans = false, sawCollapsedSpans = false;
- // CONTENTEDITABLE INPUT STYLE
+ function seeReadOnlySpans() {
+ sawReadOnlySpans = true;
+ }
- function ContentEditableInput(cm) {
- this.cm = cm;
- this.lastAnchorNode = this.lastAnchorOffset = this.lastFocusNode = this.lastFocusOffset = null;
- this.polling = new Delayed();
+ function seeCollapsedSpans() {
+ sawCollapsedSpans = true;
}
- ContentEditableInput.prototype = copyObj({
- init: function(display) {
- var input = this, cm = input.cm;
- var div = input.div = display.lineDiv;
- div.contentEditable = "true";
- disableBrowserMagic(div);
+ // TEXTMARKER SPANS
- on(div, "paste", function(e) {
- var pasted = e.clipboardData && e.clipboardData.getData("text/plain");
- if (pasted) {
- e.preventDefault();
- cm.replaceSelection(pasted, null, "paste");
- }
- });
+ function MarkedSpan(marker, from, to) {
+ this.marker = marker;
+ this.from = from; this.to = to;
+ }
- on(div, "compositionstart", function(e) {
- var data = e.data;
- input.composing = {sel: cm.doc.sel, data: data, startData: data};
- if (!data) return;
- var prim = cm.doc.sel.primary();
- var line = cm.getLine(prim.head.line);
- var found = line.indexOf(data, Math.max(0, prim.head.ch - data.length));
- if (found > -1 && found <= prim.head.ch)
- input.composing.sel = simpleSelection(Pos(prim.head.line, found),
- Pos(prim.head.line, found + data.length));
- });
- on(div, "compositionupdate", function(e) {
- input.composing.data = e.data;
- });
- on(div, "compositionend", function(e) {
- var ours = input.composing;
- if (!ours) return;
- if (e.data != ours.startData && !/\u200b/.test(e.data))
- ours.data = e.data;
- // Need a small delay to prevent other code (input event,
- // selection polling) from doing damage when fired right after
- // compositionend.
- setTimeout(function() {
- if (!ours.handled)
- input.applyComposition(ours);
- if (input.composing == ours)
- input.composing = null;
- }, 50);
- });
+ // 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 }
+ } }
+ }
- on(div, "touchstart", function() {
- input.forceCompositionEnd();
- });
+ // 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
+ }
- on(div, "input", function() {
- if (input.composing) return;
- if (!input.pollContent())
- runInOp(input.cm, function() {regChange(cm);});
- });
+ // Add a span to a line.
+ function addMarkedSpan(line, span, op) {
+ var inThisOp = op && window.WeakSet && (op.markedSpans || (op.markedSpans = new WeakSet));
+ if (inThisOp && inThisOp.has(line.markedSpans)) {
+ line.markedSpans.push(span);
+ } else {
+ line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [span];
+ if (inThisOp) { inThisOp.add(line.markedSpans); }
+ }
+ span.marker.attachLine(line);
+ }
- function onCopyCut(e) {
- if (cm.somethingSelected()) {
- lastCopied = cm.getSelections();
- if (e.type == "cut") cm.replaceSelection("", null, "cut");
- } else {
- var ranges = copyableRanges(cm);
- lastCopied = ranges.text;
- if (e.type == "cut") {
- cm.operation(function() {
- cm.setSelections(ranges.ranges, 0, sel_dontScroll);
- cm.replaceSelection("", null, "cut");
- });
- }
- }
- // iOS exposes the clipboard API, but seems to discard content inserted into it
- if (e.clipboardData && !ios) {
- e.preventDefault();
- e.clipboardData.clearData();
- e.clipboardData.setData("text/plain", lastCopied.join("\n"));
- } else {
- // 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.join("\n");
- var hadFocus = document.activeElement;
- selectInput(te);
- setTimeout(function() {
- cm.display.lineSpace.removeChild(kludge);
- hadFocus.focus();
- }, 50);
- }
- }
- on(div, "copy", onCopyCut);
- on(div, "cut", onCopyCut);
- },
-
- prepareSelection: function() {
- var result = prepareSelection(this.cm, false);
- result.focus = this.cm.state.focused;
- return result;
- },
-
- showSelection: function(info) {
- if (!info || !this.cm.display.view.length) return;
- if (info.focus) this.showPrimarySelection();
- this.showMultipleSelections(info);
- },
-
- showPrimarySelection: function() {
- var sel = window.getSelection(), prim = this.cm.doc.sel.primary();
- var curAnchor = domToPos(this.cm, sel.anchorNode, sel.anchorOffset);
- var curFocus = domToPos(this.cm, sel.focusNode, sel.focusOffset);
- if (curAnchor && !curAnchor.bad && curFocus && !curFocus.bad &&
- cmp(minPos(curAnchor, curFocus), prim.from()) == 0 &&
- cmp(maxPos(curAnchor, curFocus), prim.to()) == 0)
- return;
-
- var start = posToDOM(this.cm, prim.from());
- var end = posToDOM(this.cm, prim.to());
- if (!start && !end) return;
-
- var view = this.cm.display.view;
- var old = sel.rangeCount && sel.getRangeAt(0);
- if (!start) {
- start = {node: view[0].measure.map[2], offset: 0};
- } else if (!end) { // FIXME dangerously hacky
- 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]};
- }
-
- try { var 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) {
- sel.removeAllRanges();
- sel.addRange(rng);
- if (old && sel.anchorNode == null) sel.addRange(old);
- }
- this.rememberSelection();
- },
-
- showMultipleSelections: function(info) {
- removeChildrenAndAdd(this.cm.display.cursorDiv, info.cursors);
- removeChildrenAndAdd(this.cm.display.selectionDiv, info.selection);
- },
-
- rememberSelection: function() {
- var sel = window.getSelection();
- this.lastAnchorNode = sel.anchorNode; this.lastAnchorOffset = sel.anchorOffset;
- this.lastFocusNode = sel.focusNode; this.lastFocusOffset = sel.focusOffset;
- },
-
- selectionInEditor: function() {
- var sel = window.getSelection();
- if (!sel.rangeCount) return false;
- var node = sel.getRangeAt(0).commonAncestorContainer;
- return contains(this.div, node);
- },
-
- focus: function() {
- if (this.cm.options.readOnly != "nocursor") this.div.focus();
- },
- blur: function() { this.div.blur(); },
- getField: function() { return this.div; },
-
- supportsTouch: function() { return true; },
-
- receivedFocus: function() {
- var input = this;
- if (this.selectionInEditor())
- this.pollSelection();
- else
- runInOp(this.cm, function() { 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);
- },
-
- pollSelection: function() {
- if (this.composing) return;
-
- var sel = window.getSelection(), cm = this.cm;
- if (sel.anchorNode != this.lastAnchorNode || sel.anchorOffset != this.lastAnchorOffset ||
- sel.focusNode != this.lastFocusNode || sel.focusOffset != this.lastFocusOffset) {
- 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;
- });
- }
- },
-
- pollContent: function() {
- var cm = this.cm, display = cm.display, sel = cm.doc.sel.primary();
- var from = sel.from(), to = sel.to();
- if (from.line < display.viewFrom || to.line > display.viewTo - 1) return false;
-
- var fromIndex;
- if (from.line == display.viewFrom || (fromIndex = findViewIndex(cm, from.line)) == 0) {
- var fromLine = lineNo(display.view[0].line);
- var fromNode = display.view[0].node;
- } else {
- var fromLine = lineNo(display.view[fromIndex].line);
- var fromNode = display.view[fromIndex - 1].node.nextSibling;
+ // 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));
}
- var toIndex = findViewIndex(cm, to.line);
- if (toIndex == display.view.length - 1) {
- var toLine = display.viewTo - 1;
- var toNode = display.view[toIndex].node;
- } else {
- var toLine = lineNo(display.view[toIndex + 1].line) - 1;
- var toNode = display.view[toIndex + 1].node.previousSibling;
- }
-
- var newText = 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;
-
- newText[newText.length - 1] = newBot.slice(0, newBot.length - cutEnd);
- newText[0] = newText[0].slice(cutFront);
-
- 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;
+ } }
+ 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));
}
- },
-
- ensurePolled: function() {
- this.forceCompositionEnd();
- },
- reset: function() {
- this.forceCompositionEnd();
- },
- forceCompositionEnd: function() {
- if (!this.composing || this.composing.handled) return;
- this.applyComposition(this.composing);
- this.composing.handled = true;
- this.div.blur();
- this.div.focus();
- },
- applyComposition: function(composing) {
- if (composing.data && composing.data != composing.startData)
- operation(this.cm, applyTextInput)(this.cm, composing.data, 0, composing.sel);
- },
-
- setUneditable: function(node) {
- node.setAttribute("contenteditable", "false");
- },
-
- onKeyPress: function(e) {
- e.preventDefault();
- operation(this.cm, applyTextInput)(this.cm, String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode), 0);
- },
-
- onContextMenu: nothing,
- resetPosition: nothing,
-
- needsContentAttribute: true
- }, ContentEditableInput.prototype);
-
- 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), side = "left";
- if (order) {
- var partPos = getBidiPartAt(order, pos.ch);
- side = partPos % 2 ? "right" : "left";
- }
- var result = nodeAndOffsetInLineMap(info.map, pos.ch, "left");
- result.offset = result.collapse == "right" ? result.end : result.start;
- return result;
+ } }
+ return nw
}
- function badPos(pos, bad) { if (bad) pos.bad = true; return pos; }
+ // 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 }
- 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);
- }
- }
+ 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);
- 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);
+ // 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; }
+ }
}
}
-
- 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);
+ 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); }
}
}
}
- var found = find(textNode, topNode, offset);
- if (found) return badPos(found, bad);
+ // Make sure we didn't create any zero-length spans
+ if (first) { first = clearEmptySpans(first); }
+ if (last && last != first) { last = clearEmptySpans(last); }
- // 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 = offset; before; before = before.previousSibling) {
- found = find(before, before.firstChild, -1);
- if (found)
- return badPos(Pos(found.line, found.ch + dist), bad);
- else
- dist += after.textContent.length;
+ 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
}
- function domTextBetween(cm, from, to, fromLine, toLine) {
- var text = "", closing = false;
- function recognizeMarker(id) { return function(marker) { return marker.id == id; }; }
- function walk(node) {
- if (node.nodeType == 1) {
- var cmText = node.getAttribute("cm-text");
- if (cmText != null) {
- if (cmText == "") cmText = node.textContent.replace(/\u200b/g, "");
- text += cmText;
- 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()))
- text += getBetween(cm.doc, range.from, range.to).join("\n");
- return;
- }
- if (node.getAttribute("contenteditable") == "false") return;
- for (var i = 0; i < node.childNodes.length; i++)
- walk(node.childNodes[i]);
- if (/^(pre|div|p)$/i.test(node.nodeName))
- closing = true;
- } else if (node.nodeType == 3) {
- var val = node.nodeValue;
- if (!val) return;
- if (closing) {
- text += "\n";
- closing = false;
- }
- text += val;
- }
- }
- for (;;) {
- walk(from);
- if (from == to) break;
- from = from.nextSibling;
+ // 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); }
}
- return text;
- }
-
- CodeMirror.inputStyles = {"textarea": TextareaInput, "contenteditable": ContentEditableInput};
-
- // SELECTION / CURSOR
-
- // 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).
- function Selection(ranges, primIndex) {
- this.ranges = ranges;
- this.primIndex = primIndex;
+ if (!spans.length) { return null }
+ return spans
}
- Selection.prototype = {
- primary: function() { return this.ranges[this.primIndex]; },
- equals: function(other) {
- 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.ranges[i], there = other.ranges[i];
- if (cmp(here.anchor, there.anchor) != 0 || cmp(here.head, there.head) != 0) return false;
- }
- return true;
- },
- deepCopy: function() {
- for (var out = [], i = 0; i < this.ranges.length; i++)
- out[i] = new Range(copyPos(this.ranges[i].anchor), copyPos(this.ranges[i].head));
- return new Selection(out, this.primIndex);
- },
- somethingSelected: function() {
- for (var i = 0; i < this.ranges.length; i++)
- if (!this.ranges[i].empty()) return true;
- return false;
- },
- contains: function(pos, end) {
- if (!end) end = pos;
- for (var i = 0; i < this.ranges.length; i++) {
- var range = this.ranges[i];
- if (cmp(end, range.from()) >= 0 && cmp(pos, range.to()) <= 0)
- return i;
+ // 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 -1;
}
- };
+ return parts
+ }
- function Range(anchor, head) {
- this.anchor = anchor; this.head = head;
+ // 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;
}
- Range.prototype = {
- from: function() { return minPos(this.anchor, this.head); },
- to: function() { return maxPos(this.anchor, this.head); },
- empty: function() {
- return this.head.line == this.anchor.line && this.head.ch == this.anchor.ch;
- }
- };
+ // 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 }
- // 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);
+ // 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
}
- function simpleSelection(anchor, head) {
- return new Selection([new Range(anchor, head || anchor)], 0);
+ // 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) }
- // 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 collapsedSpanAround(line, ch) {
+ var sps = sawCollapsedSpans && line.markedSpans, found;
+ if (sps) { for (var i = 0; i < sps.length; ++i) {
+ var sp = sps[i];
+ if (sp.marker.collapsed && (sp.from == null || sp.from < ch) && (sp.to == null || sp.to > ch) &&
+ (!found || compareCollapsedMarkers(found, sp.marker) < 0)) { found = sp.marker; }
+ } }
+ return found
}
- 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 isLine(doc, l) {return l >= doc.first && l < doc.first + doc.size;}
- function clipPosArray(doc, array) {
- for (var out = [], i = 0; i < array.length; i++) out[i] = clipPos(doc, array[i]);
- return out;
+
+ // 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 }
+ } }
}
- // SELECTION UPDATES
+ // 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
+ }
- // The 'scroll' parameter given to many of these indicated whether
- // the new cursor position should be scrolled into view after
- // modifying the selection.
+ function visualLineEnd(line) {
+ var merged;
+ while (merged = collapsedSpanAtEnd(line))
+ { line = merged.find(1, true).line; }
+ return line
+ }
- // 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(doc, range, head, other) {
- if (doc.cm && doc.cm.display.shift || doc.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);
+ // 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
}
- // Extend the primary selection range, discard the rest.
- function extendSelection(doc, head, other, options) {
- setSelection(doc, new Selection([extendRange(doc, doc.sel.primary(), head, other)], 0), options);
+ // 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)
}
- // Extend all selections (pos is an array of selections with length
- // equal the number of selections)
- function extendSelections(doc, heads, options) {
- for (var out = [], i = 0; i < doc.sel.ranges.length; i++)
- out[i] = extendRange(doc, doc.sel.ranges[i], heads[i], null);
- var newSel = normalizeSelection(out, doc.sel.primIndex);
- setSelection(doc, newSel, options);
+ // 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
}
- // 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);
+ // 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 }
+ } }
}
-
- // Reset the selection to a single range.
- function setSimpleSelection(doc, anchor, head, options) {
- setSelection(doc, simpleSelection(anchor, head), options);
+ 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 }
+ }
}
- // Give beforeSelectionChange handlers a change to influence a
- // selection update.
- function filterSelectionChange(doc, sel) {
- var obj = {
- ranges: sel.ranges,
- update: function(ranges) {
- this.ranges = [];
- for (var i = 0; i < ranges.length; i++)
- this.ranges[i] = new Range(clipPos(doc, ranges[i].anchor),
- clipPos(doc, ranges[i].head));
+ // 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; }
}
- };
- 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;
+ }
+ return h
}
- 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);
+ // 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
}
- // 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);
+ // 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;
+ }
+ });
}
- function setSelectionNoUndo(doc, sel, options) {
- if (hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange"))
- sel = filterSelectionChange(doc, sel);
-
- var bias = options && options.bias ||
- (cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1);
- setSelectionInner(doc, skipAtomicInSelection(doc, sel, bias, true));
+ // LINE DATA STRUCTURE
- if (!(options && options.scroll === false) && doc.cm)
- ensureCursorVisible(doc.cm);
- }
+ // 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;
+ };
- function setSelectionInner(doc, sel) {
- if (sel.equals(doc.sel)) return;
+ Line.prototype.lineNo = function () { return lineNo(this) };
+ eventMixin(Line);
- doc.sel = sel;
+ // 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); }
+ }
- if (doc.cm) {
- doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged = true;
- signalCursorActivity(doc.cm);
- }
- signalLater(doc, "cursorActivity", doc);
+ // Detach a line from the document tree and its markers.
+ function cleanUpLine(line) {
+ line.parent = null;
+ detachMarkedSpans(line);
}
- // Verify that the selection does not partially select any atomic
- // marked ranges.
- function reCheckSelection(doc) {
- setSelectionInner(doc, skipAtomicInSelection(doc, doc.sel, null, false), sel_dontScroll);
+ // 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 = {}, 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-$&"))
}
- // 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 newAnchor = skipAtomic(doc, range.anchor, bias, mayClear);
- var newHead = skipAtomic(doc, range.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);
+ // 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: 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 || ""); }
}
- }
- return out ? normalizeSelection(out, sel.primIndex) : sel;
- }
- // Ensure a given position is not inside an atomic range.
- function skipAtomic(doc, pos, bias, mayClear) {
- var flipped = false, curPos = pos;
- var dir = bias || 1;
- doc.cantEdit = false;
- search: for (;;) {
- var line = getLine(doc, curPos.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 <= curPos.ch : sp.from < curPos.ch)) &&
- (sp.to == null || (m.inclusiveRight ? sp.to >= curPos.ch : sp.to > curPos.ch))) {
- if (mayClear) {
- signal(m, "beforeCursorEnter");
- if (m.explicitlyCleared) {
- if (!line.markedSpans) break;
- else {--i; continue;}
- }
- }
- if (!m.atomic) continue;
- var newPos = m.find(dir < 0 ? -1 : 1);
- if (cmp(newPos, curPos) == 0) {
- newPos.ch += dir;
- if (newPos.ch < 0) {
- if (newPos.line > doc.first) newPos = clipPos(doc, Pos(newPos.line - 1));
- else newPos = null;
- } else if (newPos.ch > line.text.length) {
- if (newPos.line < doc.first + doc.size - 1) newPos = Pos(newPos.line + 1, 0);
- else newPos = null;
- }
- if (!newPos) {
- if (flipped) {
- // Driven in a corner -- no valid cursor position found at all
- // -- try again *with* clearing, if we didn't already
- if (!mayClear) return skipAtomic(doc, pos, bias, true);
- // Otherwise, turn off editing until further notice, and return the start of the doc
- doc.cantEdit = true;
- return Pos(doc.first, 0);
- }
- flipped = true; newPos = pos; dir = -dir;
- }
- }
- curPos = newPos;
- continue search;
- }
- }
+ // 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({});
}
- return curPos;
}
- }
- // SELECTION DRAWING
+ // 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"; }
+ }
- function updateSelection(cm) {
- cm.display.input.showSelection(cm.display.input.prepareSelection());
- }
+ signal(cm, "renderLine", cm, lineView.line, builder.pre);
+ if (builder.pre.className)
+ { builder.textClass = joinClasses(builder.pre.className, builder.textClass || ""); }
- function prepareSelection(cm, primary) {
- var doc = cm.doc, result = {};
- var curFragment = result.cursors = document.createDocumentFragment();
- var selFragment = result.selection = document.createDocumentFragment();
+ return builder
+ }
- for (var i = 0; i < doc.sel.ranges.length; i++) {
- if (primary === false && i == doc.sel.primIndex) continue;
- var range = doc.sel.ranges[i];
- var collapsed = range.empty();
- if (collapsed || cm.options.showCursorWhenSelecting)
- drawSelectionCursor(cm, range, curFragment);
- if (!collapsed)
- drawSelectionRange(cm, range, selFragment);
- }
- return result;
+ 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
}
- // Draws a cursor for the given range
- function drawSelectionCursor(cm, range, output) {
- var pos = cursorCoords(cm, range.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";
+ // 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, css, attributes) {
+ 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 || attributes) {
+ var fullStyle = style || "";
+ if (startStyle) { fullStyle += startStyle; }
+ if (endStyle) { fullStyle += endStyle; }
+ var token = elt("span", [content], fullStyle, css);
+ if (attributes) {
+ for (var attr in attributes) { if (attributes.hasOwnProperty(attr) && attr != "style" && attr != "class")
+ { token.setAttribute(attr, attributes[attr]); } }
+ }
+ return builder.content.appendChild(token)
+ }
+ builder.content.appendChild(content);
+ }
- 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";
+ // Change some spaces to NBSP to prevent the browser from collapsing
+ // trailing spaces at the end of a line when rendering text (issue #1362).
+ 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
}
- // 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;
+ // Work around nonsense dimensions being reported for stretches of
+ // right-to-left text.
+ function buildTokenBadBidi(inner, order) {
+ return function (builder, text, style, startStyle, endStyle, css, attributes) {
+ 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, css, attributes) }
+ inner(builder, text.slice(0, part.to - start), style, startStyle, null, css, attributes);
+ startStyle = null;
+ text = text.slice(part.to - start);
+ start = part.to;
+ }
+ }
+ }
- 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; top: " + top + "px; width: " + (width == null ? rightSide - left : width) +
- "px; height: " + (bottom - top) + "px"));
+ 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;
+ }
- 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);
- }
+ // 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
+ }
- iterateBidiSections(getOrder(lineObj), 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;
+ var len = allText.length, pos = 0, i = 1, text = "", style, css;
+ var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, collapsed, attributes;
+ for (;;) {
+ if (nextChange == pos) { // Update current marker set
+ spanStyle = spanEndStyle = spanStartStyle = css = "";
+ attributes = null;
+ 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); }
+ // support for the old title property
+ // https://github.com/codemirror/CodeMirror/pull/5673
+ if (m.title) { (attributes || (attributes = {})).title = m.title; }
+ if (m.attributes) {
+ for (var attr in m.attributes)
+ { (attributes || (attributes = {}))[attr] = m.attributes[attr]; }
+ }
+ if (m.collapsed && (!collapsed || compareCollapsedMarkers(collapsed.marker, m) < 0))
+ { collapsed = sp; }
+ } else if (sp.from > pos && nextChange > sp.from) {
+ nextChange = sp.from;
+ }
}
- 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 (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 (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};
- }
+ }
+ if (pos >= len) { break }
- 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);
+ 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 : "", css, attributes);
+ }
+ 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);
}
- if (leftEnd.bottom < rightStart.top)
- add(leftSide, leftEnd.bottom, null, rightStart.top);
}
+ }
- output.appendChild(fragment);
+
+ // 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);
}
- // 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() {
- display.cursorDiv.style.visibility = (on = !on) ? "" : "hidden";
- }, cm.options.cursorBlinkRate);
- else if (cm.options.cursorBlinkRate < 0)
- display.cursorDiv.style.visibility = "hidden";
+ // 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
}
- // HIGHLIGHT WORKER
+ var operationGroup = null;
- function startWorker(cm, time) {
- if (cm.doc.mode.startState && cm.doc.frontier < cm.display.viewTo)
- cm.state.highlight.set(time, bind(highlightWorker, cm));
+ function pushOperation(op) {
+ if (operationGroup) {
+ operationGroup.ops.push(op);
+ } else {
+ op.ownsGroup = operationGroup = {
+ ops: [op],
+ delayedCallbacks: []
+ };
+ }
}
- function highlightWorker(cm) {
- var doc = cm.doc;
- if (doc.frontier < doc.first) doc.frontier = doc.first;
- if (doc.frontier >= cm.display.viewTo) return;
- var end = +new Date + cm.options.workTime;
- var state = copyState(doc.mode, getStateBefore(cm, doc.frontier));
- var changedLines = [];
-
- doc.iter(doc.frontier, Math.min(doc.first + doc.size, cm.display.viewTo + 500), function(line) {
- if (doc.frontier >= cm.display.viewFrom) { // Visible
- var oldStyles = line.styles;
- var highlighted = highlightLine(cm, line, state, true);
- 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(doc.frontier);
- line.stateAfter = copyState(doc.mode, state);
- } else {
- processLine(cm, line.text, state);
- line.stateAfter = doc.frontier % 5 == 0 ? copyState(doc.mode, state) : null;
- }
- ++doc.frontier;
- if (+new Date > end) {
- startWorker(cm, cm.options.workDelay);
- return true;
+ 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); } }
}
- });
- if (changedLines.length) runInOp(cm, function() {
- for (var i = 0; i < changedLines.length; i++)
- regLineChange(cm, changedLines[i], "text");
- });
+ } while (i < callbacks.length)
}
- // 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);
- if (line.stateAfter && (!precise || search <= doc.frontier)) return search;
- var indented = countColumn(line.text, null, cm.options.tabSize);
- if (minline == null || minindent > indented) {
- minline = search - 1;
- minindent = indented;
- }
+ function finishOperation(op, endCb) {
+ var group = op.ownsGroup;
+ if (!group) { return }
+
+ try { fireCallbacksForOps(group); }
+ finally {
+ operationGroup = null;
+ endCb(group);
}
- return minline;
}
- function getStateBefore(cm, n, precise) {
- var doc = cm.doc, display = cm.display;
- if (!doc.mode.startState) return true;
- var pos = findStartLine(cm, n, precise), state = pos > doc.first && getLine(doc, pos-1).stateAfter;
- if (!state) state = startState(doc.mode);
- else state = copyState(doc.mode, state);
- doc.iter(pos, n, function(line) {
- processLine(cm, line.text, state);
- var save = pos == n - 1 || pos % 5 == 0 || pos >= display.viewFrom && pos < display.viewTo;
- line.stateAfter = save ? copyState(doc.mode, state) : null;
- ++pos;
- });
- if (precise) doc.frontier = pos;
- return state;
- }
+ var orphanDelayedCallbacks = null;
- // POSITION MEASUREMENT
+ // 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); });
+ };
- 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;
+ for (var i = 0; i < arr.length; ++i)
+ loop( i );
}
- 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;
+ 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"));
+ gutterWrap.setAttribute("aria-hidden", "true");
+ 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.display.gutterSpecs.length; ++k) {
+ var id = cm.display.gutterSpecs[k].className, 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; }
+ var isWidget = classTest("CodeMirror-linewidget");
+ for (var node = lineView.node.firstChild, next = (void 0); node; node = next) {
+ next = node.nextSibling;
+ if (isWidget.test(node.className)) { 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" + (widget.className ? " " + widget.className : ""));
+ 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", "CodeMirror-line-like"));
+ 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
@@ -2441,7 +2338,7 @@
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((cur.bottom + next.top) / 2 - rect.top); }
}
}
heights.push(rect.bottom - rect.top);
@@ -2453,13 +2350,15 @@
// 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 = 0; i < lineView.rest.length; i++)
- if (lineNo(lineView.rest[i]) > lineN)
- return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i], before: true};
+ { return {map: lineView.measure.map, cache: lineView.measure.cache} }
+ if (lineView.rest) {
+ 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
@@ -2472,22 +2371,22 @@
var built = view.built = buildLineContent(cm, view);
view.text = built.pre;
removeChildrenAndAdd(cm.display.lineMeasure, built.pre);
- return view;
+ 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);
+ 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)];
+ { return cm.display.view[findViewIndex(cm, lineN)] }
var ext = cm.display.externalMeasured;
if (ext && lineN >= ext.lineN && lineN < ext.lineN + ext.size)
- return ext;
+ { return ext }
}
// Measurement can be split in two steps, the set-up work that
@@ -2498,51 +2397,54 @@
function prepareMeasureForLine(cm, line) {
var lineN = lineNo(line);
var view = findViewForLine(cm, lineN);
- if (view && !view.text)
+ if (view && !view.text) {
view = null;
- else if (view && view.changes)
+ } else if (view && view.changes) {
updateLineForChanges(cm, view, lineN, getDimensions(cm));
+ cm.curOp.forceUpdate = true;
+ }
if (!view)
- view = updateExternalMeasurement(cm, line);
+ { 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;
+ 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();
+ { 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;
+ 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};
+ 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;
+ 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) {
- var mStart = map[i], mEnd = map[i + 1];
+ mStart = map[i];
+ mEnd = map[i + 1];
if (ch < mStart) {
start = 0; end = 1;
collapse = "left";
@@ -2552,26 +2454,36 @@
} else if (i == map.length - 3 || ch == mEnd && map[i + 3] > ch) {
end = mEnd - mStart;
start = end - 1;
- if (ch >= mEnd) collapse = "right";
+ if (ch >= mEnd) { collapse = "right"; }
}
if (start != null) {
node = map[i + 2];
if (mStart == mEnd && bias == (node.insertLeft ? "left" : "right"))
- collapse = bias;
+ { collapse = bias; }
if (bias == "left" && start == 0)
- while (i && map[i - 2] == map[i - 3] && map[i - 1].insertLeft) {
+ { 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) {
+ { while (i < map.length - 3 && map[i + 3] == map[i + 4] && !map[i + 5].insertLeft) {
node = map[(i += 3) + 2];
collapse = "right";
- }
- break;
+ } }
+ break
}
}
- return {node: node, start: start, end: end, collapse: collapse, coverStart: mStart, coverEnd: mEnd};
+ 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) {
@@ -2580,55 +2492,49 @@
var rect;
if (node.nodeType == 3) { // If it is a text node, use a range to retrieve the coordinates.
- for (var i = 0; i < 4; i++) { // 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 if (ie && cm.options.lineWrapping) {
- var rects = range(node, start, end).getClientRects();
- if (rects.length)
- rect = rects[bias == "right" ? rects.length - 1 : 0];
- else
- rect = nullRect;
- } else {
- rect = range(node, start, end).getBoundingClientRect() || nullRect;
- }
- if (rect.left || rect.right || start == 0) break;
+ 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);
+ 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";
+ 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];
+ { rect = rects[bias == "right" ? rects.length - 1 : 0]; }
else
- rect = node.getBoundingClientRect();
+ { 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};
+ { rect = {left: rSpan.left, right: rSpan.left + charWidth(cm.display), top: rSpan.top, bottom: rSpan.bottom}; }
else
- rect = nullRect;
+ { 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;
- for (var i = 0; i < heights.length - 1; i++)
- if (mid < heights[i]) break;
+ 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 (!rect.left && !rect.right) { result.bogus = true; }
if (!cm.options.singleCursorHeightPerLine) { result.rtop = rtop; result.rbottom = rbot; }
- return result;
+ return result
}
// Work around problem with bounding client rects on ranges being
@@ -2636,19 +2542,19 @@
function maybeUpdateRectForZooming(measure, rect) {
if (!window.screen || screen.logicalXDPI == null ||
screen.logicalXDPI == screen.deviceXDPI || !hasBadZoomedRects(measure))
- return rect;
+ { 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};
+ 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] = {};
+ if (lineView.rest) { for (var i = 0; i < lineView.rest.length; i++)
+ { lineView.measure.caches[i] = {}; } }
}
}
@@ -2656,33 +2562,51 @@
cm.display.externalMeasure = null;
removeChildren(cm.display.lineMeasure);
for (var i = 0; i < cm.display.view.length; i++)
- clearLineMeasurementCacheFor(cm.display.view[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;
+ if (!cm.options.lineWrapping) { cm.display.maxLineChanged = true; }
cm.display.lineNumChars = null;
}
- function pageScrollX() { return window.pageXOffset || (document.documentElement || document.body).scrollLeft; }
- function pageScrollY() { return window.pageYOffset || (document.documentElement || document.body).scrollTop; }
+ 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
+ }
+
+ function widgetTopHeight(lineObj) {
+ var ref = visualLine(lineObj);
+ var widgets = ref.widgets;
+ var height = 0;
+ if (widgets) { for (var i = 0; i < widgets.length; ++i) { if (widgets[i].above)
+ { height += widgetHeight(widgets[i]); } } }
+ return height
+ }
// 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",
+ // "line", "div" (display.lineDiv), "local"./null (editor), "window",
// or "page".
- function intoCoordSystem(cm, lineObj, rect, context) {
- if (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;
+ function intoCoordSystem(cm, lineObj, rect, context, includeWidgets) {
+ if (!includeWidgets) {
+ var height = widgetTopHeight(lineObj);
+ rect.top += height; rect.bottom += height;
}
- if (context == "line") return rect;
- if (!context) context = "local";
+ 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 == "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());
@@ -2690,13 +2614,13 @@
rect.left += xOff; rect.right += xOff;
}
rect.top += yOff; rect.bottom += yOff;
- return rect;
+ return rect
}
// Coverts a box from "div" coords to another coordinate system.
- // Context may be "window", "page", "div", or "local"/null.
+ // Context may be "window", "page", "div", or "local"./null.
function fromCoordSystem(cm, coords, context) {
- if (context == "div") return coords;
+ if (context == "div") { return coords }
var left = coords.left, top = coords.top;
// First move into "page" coordinate system
if (context == "page") {
@@ -2709,55 +2633,68 @@
}
var lineSpaceBox = cm.display.lineSpace.getBoundingClientRect();
- return {left: left - lineSpaceBox.left, top: top - lineSpaceBox.top};
+ 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);
+ 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);
+ 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);
- }
- function getBidi(ch, partPos) {
- var part = order[partPos], right = part.level % 2;
- if (ch == bidiLeft(part) && partPos && part.level < order[partPos - 1].level) {
- part = order[--partPos];
- ch = bidiRight(part) - (part.level % 2 ? 0 : 1);
- right = true;
- } else if (ch == bidiRight(part) && partPos < order.length - 1 && part.level < order[partPos + 1].level) {
- part = order[++partPos];
- ch = bidiLeft(part) - part.level % 2;
- right = false;
- }
- if (right && ch == part.to && ch > part.from) return get(ch - 1);
- return get(ch, right);
- }
- var order = getOrder(lineObj), ch = pos.ch;
- if (!order) return get(ch);
- var partPos = getBidiPartAt(order, ch);
- var val = getBidi(ch, partPos);
- if (bidiOther != null) val.other = getBidi(ch, bidiOther);
- return val;
+ 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 == 1;
+ 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 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};
+ return {left: left, right: left, top: top, bottom: top + lineObj.height}
}
// Positions returned by coordsChar contain some extra information.
@@ -2766,11 +2703,11 @@
// 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, outside, xRel) {
- var pos = Pos(line, ch);
+ function PosWithInfo(line, ch, sticky, outside, xRel) {
+ var pos = Pos(line, ch, sticky);
pos.xRel = xRel;
- if (outside) pos.outside = true;
- return pos;
+ if (outside) { pos.outside = outside; }
+ return pos
}
// Compute the character position closest to the given coordinates.
@@ -2778,70 +2715,173 @@
function coordsChar(cm, x, y) {
var doc = cm.doc;
y += cm.display.viewOffset;
- if (y < 0) return PosWithInfo(doc.first, 0, true, -1);
+ if (y < 0) { return PosWithInfo(doc.first, 0, null, -1, -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, true, 1);
- if (x < 0) x = 0;
+ { return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, null, 1, 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;
+ var collapsed = collapsedSpanAround(lineObj, found.ch + (found.xRel > 0 || found.outside > 0 ? 1 : 0));
+ if (!collapsed) { return found }
+ var rangeEnd = collapsed.find(1);
+ if (rangeEnd.line == lineN) { return rangeEnd }
+ lineObj = getLine(doc, lineN = rangeEnd.line);
}
}
- function coordsCharInner(cm, lineObj, lineNo, x, y) {
- var innerOff = y - heightAtLine(lineObj);
- var wrongLine = false, adjust = 2 * cm.display.wrapper.clientWidth;
- var preparedMeasure = prepareMeasureForLine(cm, lineObj);
+ function wrappedLineExtent(cm, lineObj, preparedMeasure, y) {
+ y -= widgetTopHeight(lineObj);
+ var end = lineObj.text.length;
+ var begin = findFirst(function (ch) { return measureCharPrepared(cm, preparedMeasure, ch - 1).bottom <= y; }, end, 0);
+ end = findFirst(function (ch) { return measureCharPrepared(cm, preparedMeasure, ch).top > y; }, begin, end);
+ return {begin: begin, end: end}
+ }
- function getX(ch) {
- var sp = cursorCoords(cm, Pos(lineNo, ch), "line", lineObj, preparedMeasure);
- wrongLine = true;
- if (innerOff > sp.bottom) return sp.left - adjust;
- else if (innerOff < sp.top) return sp.left + adjust;
- else wrongLine = false;
- return sp.left;
- }
+ function wrappedLineExtentChar(cm, lineObj, preparedMeasure, target) {
+ if (!preparedMeasure) { preparedMeasure = prepareMeasureForLine(cm, lineObj); }
+ var targetTop = intoCoordSystem(cm, lineObj, measureCharPrepared(cm, preparedMeasure, target), "line").top;
+ return wrappedLineExtent(cm, lineObj, preparedMeasure, targetTop)
+ }
- var bidi = getOrder(lineObj), dist = lineObj.text.length;
- var from = lineLeft(lineObj), to = lineRight(lineObj);
- var fromX = getX(from), fromOutside = wrongLine, toX = getX(to), toOutside = wrongLine;
+ // Returns true if the given side of a box is after the given
+ // coordinates, in top-to-bottom, left-to-right order.
+ function boxIsAfter(box, x, y, left) {
+ return box.bottom <= y ? false : box.top > y ? true : (left ? box.left : box.right) > x
+ }
- if (x > toX) return PosWithInfo(lineNo, to, toOutside, 1);
- // Do a binary search between these bounds.
- for (;;) {
- if (bidi ? to == from || to == moveVisually(lineObj, from, 1) : to - from <= 1) {
- var ch = x < fromX || x - fromX <= toX - x ? from : to;
- var xDiff = x - (ch == from ? fromX : toX);
- while (isExtendingChar(lineObj.text.charAt(ch))) ++ch;
- var pos = PosWithInfo(lineNo, ch, ch == from ? fromOutside : toOutside,
- xDiff < -1 ? -1 : xDiff > 1 ? 1 : 0);
- return pos;
- }
- var step = Math.ceil(dist / 2), middle = from + step;
- if (bidi) {
- middle = from;
- for (var i = 0; i < step; ++i) middle = moveVisually(lineObj, middle, 1);
- }
- var middleX = getX(middle);
- if (middleX > x) {to = middle; toX = middleX; if (toOutside = wrongLine) toX += 1000; dist = step;}
- else {from = middle; fromX = middleX; fromOutside = wrongLine; dist -= step;}
- }
+ function coordsCharInner(cm, lineObj, lineNo, x, y) {
+ // Move y into line-local coordinate space
+ y -= heightAtLine(lineObj);
+ var preparedMeasure = prepareMeasureForLine(cm, lineObj);
+ // When directly calling `measureCharPrepared`, we have to adjust
+ // for the widgets at this line.
+ var widgetHeight = widgetTopHeight(lineObj);
+ var begin = 0, end = lineObj.text.length, ltr = true;
+
+ var order = getOrder(lineObj, cm.doc.direction);
+ // If the line isn't plain left-to-right text, first figure out
+ // which bidi section the coordinates fall into.
+ if (order) {
+ var part = (cm.options.lineWrapping ? coordsBidiPartWrapped : coordsBidiPart)
+ (cm, lineObj, lineNo, preparedMeasure, order, x, y);
+ ltr = part.level != 1;
+ // The awkward -1 offsets are needed because findFirst (called
+ // on these below) will treat its first bound as inclusive,
+ // second as exclusive, but we want to actually address the
+ // characters in the part's range
+ begin = ltr ? part.from : part.to - 1;
+ end = ltr ? part.to : part.from - 1;
+ }
+
+ // A binary search to find the first character whose bounding box
+ // starts after the coordinates. If we run across any whose box wrap
+ // the coordinates, store that.
+ var chAround = null, boxAround = null;
+ var ch = findFirst(function (ch) {
+ var box = measureCharPrepared(cm, preparedMeasure, ch);
+ box.top += widgetHeight; box.bottom += widgetHeight;
+ if (!boxIsAfter(box, x, y, false)) { return false }
+ if (box.top <= y && box.left <= x) {
+ chAround = ch;
+ boxAround = box;
+ }
+ return true
+ }, begin, end);
+
+ var baseX, sticky, outside = false;
+ // If a box around the coordinates was found, use that
+ if (boxAround) {
+ // Distinguish coordinates nearer to the left or right side of the box
+ var atLeft = x - boxAround.left < boxAround.right - x, atStart = atLeft == ltr;
+ ch = chAround + (atStart ? 0 : 1);
+ sticky = atStart ? "after" : "before";
+ baseX = atLeft ? boxAround.left : boxAround.right;
+ } else {
+ // (Adjust for extended bound, if necessary.)
+ if (!ltr && (ch == end || ch == begin)) { ch++; }
+ // To determine which side to associate with, get the box to the
+ // left of the character and compare it's vertical position to the
+ // coordinates
+ sticky = ch == 0 ? "after" : ch == lineObj.text.length ? "before" :
+ (measureCharPrepared(cm, preparedMeasure, ch - (ltr ? 1 : 0)).bottom + widgetHeight <= y) == ltr ?
+ "after" : "before";
+ // Now get accurate coordinates for this place, in order to get a
+ // base X position
+ var coords = cursorCoords(cm, Pos(lineNo, ch, sticky), "line", lineObj, preparedMeasure);
+ baseX = coords.left;
+ outside = y < coords.top ? -1 : y >= coords.bottom ? 1 : 0;
+ }
+
+ ch = skipExtendingChars(lineObj.text, ch, 1);
+ return PosWithInfo(lineNo, ch, sticky, outside, x - baseX)
+ }
+
+ function coordsBidiPart(cm, lineObj, lineNo, preparedMeasure, order, x, y) {
+ // Bidi parts are sorted left-to-right, and in a non-line-wrapping
+ // situation, we can take this ordering to correspond to the visual
+ // ordering. This finds the first part whose end is after the given
+ // coordinates.
+ var index = findFirst(function (i) {
+ var part = order[i], ltr = part.level != 1;
+ return boxIsAfter(cursorCoords(cm, Pos(lineNo, ltr ? part.to : part.from, ltr ? "before" : "after"),
+ "line", lineObj, preparedMeasure), x, y, true)
+ }, 0, order.length - 1);
+ var part = order[index];
+ // If this isn't the first part, the part's start is also after
+ // the coordinates, and the coordinates aren't on the same line as
+ // that start, move one part back.
+ if (index > 0) {
+ var ltr = part.level != 1;
+ var start = cursorCoords(cm, Pos(lineNo, ltr ? part.from : part.to, ltr ? "after" : "before"),
+ "line", lineObj, preparedMeasure);
+ if (boxIsAfter(start, x, y, true) && start.top > y)
+ { part = order[index - 1]; }
+ }
+ return part
+ }
+
+ function coordsBidiPartWrapped(cm, lineObj, _lineNo, preparedMeasure, order, x, y) {
+ // In a wrapped line, rtl text on wrapping boundaries can do things
+ // that don't correspond to the ordering in our `order` array at
+ // all, so a binary search doesn't work, and we want to return a
+ // part that only spans one line so that the binary search in
+ // coordsCharInner is safe. As such, we first find the extent of the
+ // wrapped line, and then do a flat search in which we discard any
+ // spans that aren't on the line.
+ var ref = wrappedLineExtent(cm, lineObj, preparedMeasure, y);
+ var begin = ref.begin;
+ var end = ref.end;
+ if (/\s/.test(lineObj.text.charAt(end - 1))) { end--; }
+ var part = null, closestDist = null;
+ for (var i = 0; i < order.length; i++) {
+ var p = order[i];
+ if (p.from >= end || p.to <= begin) { continue }
+ var ltr = p.level != 1;
+ var endX = measureCharPrepared(cm, preparedMeasure, ltr ? Math.min(end, p.to) - 1 : Math.max(begin, p.from)).right;
+ // Weigh against spans ending before this, so that they are only
+ // picked if nothing ends after
+ var dist = endX < x ? x - endX + 1e9 : endX - x;
+ if (!part || closestDist > dist) {
+ part = p;
+ closestDist = dist;
+ }
+ }
+ if (!part) { part = order[order.length - 1]; }
+ // Clip the part to the wrapped line.
+ if (part.from < begin) { part = {from: begin, to: part.to, level: part.level}; }
+ if (part.to > end) { part = {from: part.from, to: end, level: part.level}; }
+ return part
}
var measureText;
// Compute the default text height.
function textHeight(display) {
- if (display.cachedTextHeight != null) return display.cachedTextHeight;
+ if (display.cachedTextHeight != null) { return display.cachedTextHeight }
if (measureText == null) {
- measureText = elt("pre");
+ measureText = elt("pre", null, "CodeMirror-line-like");
// Measure a bunch of lines, for browsers that compute
// fractional heights.
for (var i = 0; i < 49; ++i) {
@@ -2852,275 +2892,107 @@
}
removeChildrenAndAdd(display.measure, measureText);
var height = measureText.offsetHeight / 50;
- if (height > 3) display.cachedTextHeight = height;
+ if (height > 3) { display.cachedTextHeight = height; }
removeChildren(display.measure);
- return height || 1;
+ return height || 1
}
// Compute the default character width.
function charWidth(display) {
- if (display.cachedCharWidth != null) return display.cachedCharWidth;
+ if (display.cachedCharWidth != null) { return display.cachedCharWidth }
var anchor = elt("span", "xxxxxxxxxx");
- var pre = elt("pre", [anchor]);
+ var pre = elt("pre", [anchor], "CodeMirror-line-like");
removeChildrenAndAdd(display.measure, pre);
var rect = anchor.getBoundingClientRect(), width = (rect.right - rect.left) / 10;
- if (width > 2) display.cachedCharWidth = width;
- return width || 10;
+ if (width > 2) { display.cachedCharWidth = width; }
+ return width || 10
}
- // OPERATIONS
-
- // 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 operationGroup = null;
-
- 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
- id: ++nextOpId // Unique ID
- };
- if (operationGroup) {
- operationGroup.ops.push(cm.curOp);
- } else {
- cm.curOp.ownsGroup = operationGroup = {
- ops: [cm.curOp],
- delayedCallbacks: []
- };
+ // 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) {
+ var id = cm.display.gutterSpecs[i].className;
+ left[id] = n.offsetLeft + n.clientLeft + gutterLeft;
+ width[id] = n.clientWidth;
}
+ return {fixedPos: compensateForHScroll(d),
+ gutterTotalWidth: d.gutters.offsetWidth,
+ gutterLeft: left,
+ gutterWidth: width,
+ wrapperWidth: d.wrapper.clientWidth}
}
- 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]();
- 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++](op.cm);
- }
- } while (i < callbacks.length);
+ // 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
}
- // Finish an operation, updating the display and signalling delayed events
- function endOperation(cm) {
- var op = cm.curOp, group = op.ownsGroup;
- if (!group) return;
+ // 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 }
- try { fireCallbacksForOps(group); }
- finally {
- operationGroup = null;
- 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 = 0; i < ops.length; i++) // Write DOM (maybe)
- endOperation_W1(ops[i]);
- for (var i = 0; i < ops.length; i++) // Read DOM
- endOperation_R2(ops[i]);
- for (var i = 0; i < ops.length; i++) // Write DOM (maybe)
- endOperation_W2(ops[i]);
- for (var i = 0; i < ops.length; i++) // Read DOM
- endOperation_finish(ops[i]);
- }
-
- 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);
+ 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 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 (wrapping)
+ { return widgetsHeight + (Math.ceil(line.text.length / perLine) || 1) * th }
+ else
+ { return widgetsHeight + th }
}
-
- if (op.updatedDisplay || op.selectionChanged)
- op.preparedSelection = display.input.prepareSelection();
}
- 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;
- }
-
- if (op.preparedSelection)
- cm.display.input.showSelection(op.preparedSelection);
- if (op.updatedDisplay)
- setDocumentHeight(cm, op.barMeasure);
- if (op.updatedDisplay || op.startHeight != cm.doc.height)
- updateScrollbars(cm, op.barMeasure);
-
- if (op.selectionChanged) restartBlink(cm);
-
- if (cm.state.focused && op.updateInput)
- cm.display.input.reset(op.typing);
+ 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); }
+ });
}
- 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;
+ // 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 }
- // Propagate the scroll position to the actual DOM scroller
- if (op.scrollTop != null && (display.scroller.scrollTop != op.scrollTop || op.forceScroll)) {
- doc.scrollTop = Math.max(0, Math.min(display.scroller.scrollHeight - display.scroller.clientHeight, op.scrollTop));
- display.scrollbars.setScrollTop(doc.scrollTop);
- display.scroller.scrollTop = doc.scrollTop;
- }
- if (op.scrollLeft != null && (display.scroller.scrollLeft != op.scrollLeft || op.forceScroll)) {
- doc.scrollLeft = Math.max(0, Math.min(display.scroller.scrollWidth - displayWidth(cm), op.scrollLeft));
- display.scrollbars.setScrollLeft(doc.scrollLeft);
- display.scroller.scrollLeft = doc.scrollLeft;
- alignHorizontally(cm);
- }
- // If we need to scroll a specific position into view, do so.
- if (op.scrollToPos) {
- var coords = scrollPosIntoView(cm, clipPos(doc, op.scrollToPos.from),
- clipPos(doc, op.scrollToPos.to), op.scrollToPos.margin);
- if (op.scrollToPos.isCursor && cm.state.focused) maybeScrollWindow(cm, coords);
+ 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$1) { return null }
+ var coords = coordsChar(cm, x, y), line;
+ if (forRect && coords.xRel > 0 && (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));
}
-
- // 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 = 0; i < unhidden.length; ++i)
- if (unhidden[i].lines.length) signal(unhidden[i], "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); }
- };
- }
-
- // VIEW TRACKING
-
- // 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);
+ return coords
}
- // 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);
+ // 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 }
}
- return array;
}
// Updates the display.view data structure for a given change to the
@@ -3130,20 +3002,20 @@
// 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;
+ 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;
+ { display.updateLineNumbers = from; }
cm.curOp.viewChanged = true;
if (from >= display.viewTo) { // Change after
if (sawCollapsedSpans && visualLineNo(cm.doc, from) < display.viewTo)
- resetView(cm);
+ { resetView(cm); }
} else if (to <= display.viewFrom) { // Change before
if (sawCollapsedSpans && visualLineEndNo(cm.doc, to + lendiff) > display.viewFrom) {
resetView(cm);
@@ -3163,10 +3035,10 @@
resetView(cm);
}
} else if (to >= display.viewTo) { // Bottom overlap
- var cut = viewCuttingPoint(cm, from, from, -1);
- if (cut) {
- display.view = display.view.slice(0, cut.index);
- display.viewTo = cut.lineN;
+ 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);
}
@@ -3186,9 +3058,9 @@
var ext = display.externalMeasured;
if (ext) {
if (to < ext.lineN)
- ext.lineN += lendiff;
+ { ext.lineN += lendiff; }
else if (from < ext.lineN + ext.size)
- display.externalMeasured = null;
+ { display.externalMeasured = null; }
}
}
@@ -3198,13 +3070,13 @@
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;
+ { display.externalMeasured = null; }
- if (line < display.viewFrom || line >= display.viewTo) return;
+ if (line < display.viewFrom || line >= display.viewTo) { return }
var lineView = display.view[findViewIndex(cm, line)];
- if (lineView.node == null) return;
+ if (lineView.node == null) { return }
var arr = lineView.changes || (lineView.changes = []);
- if (indexOf(arr, type) == -1) arr.push(type);
+ if (indexOf(arr, type) == -1) { arr.push(type); }
}
// Clear the view.
@@ -3214,28 +3086,16 @@
cm.display.viewOffset = 0;
}
- // 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 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};
- for (var i = 0, n = cm.display.viewFrom; i < index; i++)
- n += view[i].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;
+ if (index == view.length - 1) { return null }
diff = (n + view[index].size) - oldN;
index++;
} else {
@@ -3244,11 +3104,11 @@
oldN += diff; newN += diff;
}
while (visualLineNo(cm.doc, newN) != newN) {
- if (index == (dir < 0 ? 0 : view.length - 1)) return null;
+ 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};
+ return {index: index, lineN: newN}
}
// Force the view to cover a given range, adding empty view element
@@ -3260,14 +3120,14 @@
display.viewFrom = from;
} else {
if (display.viewFrom > from)
- display.view = buildViewArray(cm, from, display.viewFrom).concat(display.view);
+ { display.view = buildViewArray(cm, from, display.viewFrom).concat(display.view); }
else if (display.viewFrom < from)
- display.view = display.view.slice(findViewIndex(cm, 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));
+ { 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.view = display.view.slice(0, findViewIndex(cm, to)); }
}
display.viewTo = to;
}
@@ -3278,5368 +3138,6712 @@
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;
+ if (!lineView.hidden && (!lineView.node || lineView.changes)) { ++dirty; }
}
- return dirty;
+ return dirty
}
- // EVENT HANDLERS
+ function updateSelection(cm) {
+ cm.display.input.showSelection(cm.display.input.prepareSelection());
+ }
- // 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) { 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) {onContextMenu(cm, e);});
+ function prepareSelection(cm, primary) {
+ if ( primary === void 0 ) primary = true;
- // Used to suppress mouse event handling when a touch happens
- var touchFinished, prevTouch = {end: 0};
- function finishTouch() {
- if (d.activeTouch) {
- touchFinished = setTimeout(function() {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 (!isMouseLikeTouchEvent(e)) {
- 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);
+ var doc = cm.doc, result = {};
+ var curFragment = result.cursors = document.createDocumentFragment();
+ var selFragment = result.selection = document.createDocumentFragment();
- // Sync scrolling between fake scrollbars and real scrollable
- // area, ensure viewport is updated when scrolling.
- on(d.scroller, "scroll", function() {
- if (d.scroller.clientHeight) {
- setScrollTop(cm, d.scroller.scrollTop);
- setScrollLeft(cm, d.scroller.scrollLeft, true);
- signal(cm, "scroll", cm);
+ var customCursor = cm.options.$customCursor;
+ if (customCursor) { primary = true; }
+ for (var i = 0; i < doc.sel.ranges.length; i++) {
+ if (!primary && 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 (customCursor) {
+ var head = customCursor(cm, range);
+ if (head) { drawSelectionCursor(cm, head, curFragment); }
+ } else if (collapsed || cm.options.showCursorWhenSelecting) {
+ drawSelectionCursor(cm, range.head, curFragment);
}
- });
+ if (!collapsed)
+ { drawSelectionRange(cm, range, selFragment); }
+ }
+ return result
+ }
- // Listen to wheel events in order to try and update the viewport on time.
- on(d.scroller, "mousewheel", function(e){onScrollWheel(cm, e);});
- on(d.scroller, "DOMMouseScroll", function(e){onScrollWheel(cm, e);});
+ // Draws a cursor for the given range
+ function drawSelectionCursor(cm, head, output) {
+ var pos = cursorCoords(cm, head, "div", null, null, !cm.options.singleCursorHeightPerLine);
- // Prevent wrapper from ever scrolling
- on(d.wrapper, "scroll", function() { d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; });
+ 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";
- function drag_(e) {
- if (!signalDOMEvent(cm, e)) e_stop(e);
- }
- if (cm.options.dragDrop) {
- on(d.scroller, "dragstart", function(e){onDragStart(cm, e);});
- on(d.scroller, "dragenter", drag_);
- on(d.scroller, "dragover", drag_);
- on(d.scroller, "drop", operation(cm, onDrop));
+ if (/\bcm-fat-cursor\b/.test(cm.getWrapperElement().className)) {
+ var charPos = charCoords(cm, head, "div", null, null);
+ var width = charPos.right - charPos.left;
+ cursor.style.width = (width > 0 ? width : cm.defaultCharWidth()) + "px";
}
- var inp = d.input.getField();
- on(inp, "keyup", function(e) { onKeyUp.call(cm, e); });
- on(inp, "keydown", operation(cm, onKeyDown));
- on(inp, "keypress", operation(cm, onKeyPress));
- on(inp, "focus", bind(onFocus, cm));
- on(inp, "blur", bind(onBlur, cm));
- }
-
- // 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();
- }
-
- // MOUSE EVENTS
-
- // 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;
+ 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";
}
}
- // 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;
- }
+ function cmpCoords(a, b) { return a.top - b.top || a.left - b.left }
- // 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 (display.activeTouch && display.input.supportsTouch() || signalDOMEvent(cm, e)) return;
- display.shift = e.shiftKey;
+ // 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;
+ var docLTR = doc.direction == "ltr";
- if (eventInWidget(display, e)) {
- if (!webkit) {
- // Briefly turn off draggability, to allow widgets to do
- // normal dragging things.
- display.scroller.draggable = false;
- setTimeout(function(){display.scroller.draggable = true;}, 100);
- }
- return;
+ 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")));
}
- if (clickInGutter(cm, e)) return;
- var start = posFromMouse(cm, e);
- window.focus();
- switch (e_button(e)) {
- case 1:
- if (start)
- leftButtonDown(cm, e, start);
- else if (e_target(e) == display.scroller)
- e_preventDefault(e);
- break;
- case 2:
- if (webkit) cm.state.lastMiddleDown = +new Date;
- if (start) extendSelection(cm.doc, start);
- setTimeout(function() {display.input.focus();}, 20);
- e_preventDefault(e);
- break;
- case 3:
- if (captureRightClick) onContextMenu(cm, e);
- break;
+ 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)
+ }
+
+ function wrapX(pos, dir, side) {
+ var extent = wrappedLineExtentChar(cm, lineObj, null, pos);
+ var prop = (dir == "ltr") == (side == "after") ? "left" : "right";
+ var ch = side == "after" ? extent.begin : extent.end - (/\s/.test(lineObj.text.charAt(extent.end - 1)) ? 2 : 1);
+ return coords(ch, prop)[prop]
+ }
+
+ var order = getOrder(lineObj, doc.direction);
+ iterateBidiSections(order, fromArg || 0, toArg == null ? lineLen : toArg, function (from, to, dir, i) {
+ var ltr = dir == "ltr";
+ var fromPos = coords(from, ltr ? "left" : "right");
+ var toPos = coords(to - 1, ltr ? "right" : "left");
+
+ var openStart = fromArg == null && from == 0, openEnd = toArg == null && to == lineLen;
+ var first = i == 0, last = !order || i == order.length - 1;
+ if (toPos.top - fromPos.top <= 3) { // Single line
+ var openLeft = (docLTR ? openStart : openEnd) && first;
+ var openRight = (docLTR ? openEnd : openStart) && last;
+ var left = openLeft ? leftSide : (ltr ? fromPos : toPos).left;
+ var right = openRight ? rightSide : (ltr ? toPos : fromPos).right;
+ add(left, fromPos.top, right - left, fromPos.bottom);
+ } else { // Multiple lines
+ var topLeft, topRight, botLeft, botRight;
+ if (ltr) {
+ topLeft = docLTR && openStart && first ? leftSide : fromPos.left;
+ topRight = docLTR ? rightSide : wrapX(from, dir, "before");
+ botLeft = docLTR ? leftSide : wrapX(to, dir, "after");
+ botRight = docLTR && openEnd && last ? rightSide : toPos.right;
+ } else {
+ topLeft = !docLTR ? leftSide : wrapX(from, dir, "before");
+ topRight = !docLTR && openStart && first ? rightSide : fromPos.right;
+ botLeft = !docLTR && openEnd && last ? leftSide : toPos.left;
+ botRight = !docLTR ? rightSide : wrapX(to, dir, "after");
+ }
+ add(topLeft, fromPos.top, topRight - topLeft, fromPos.bottom);
+ if (fromPos.bottom < toPos.top) { add(leftSide, fromPos.bottom, null, toPos.top); }
+ add(botLeft, toPos.top, botRight - botLeft, toPos.bottom);
+ }
+
+ if (!start || cmpCoords(fromPos, start) < 0) { start = fromPos; }
+ if (cmpCoords(toPos, start) < 0) { start = toPos; }
+ if (!end || cmpCoords(fromPos, end) < 0) { end = fromPos; }
+ if (cmpCoords(toPos, end) < 0) { end = toPos; }
+ });
+ return {start: start, end: end}
}
- }
- var lastClick, lastDoubleClick;
- function leftButtonDown(cm, e, start) {
- if (ie) setTimeout(bind(ensureFocus, cm), 0);
- else ensureFocus(cm);
-
- var now = +new Date, type;
- if (lastDoubleClick && lastDoubleClick.time > now - 400 && cmp(lastDoubleClick.pos, start) == 0) {
- type = "triple";
- } else if (lastClick && lastClick.time > now - 400 && cmp(lastClick.pos, start) == 0) {
- type = "double";
- lastDoubleClick = {time: now, pos: start};
+ var sFrom = range.from(), sTo = range.to();
+ if (sFrom.line == sTo.line) {
+ drawForLine(sFrom.line, sFrom.ch, sTo.ch);
} else {
- type = "single";
- lastClick = {time: now, pos: start};
+ 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); }
}
- var sel = cm.doc.sel, modifier = mac ? e.metaKey : e.ctrlKey, contained;
- if (cm.options.dragDrop && dragAndDrop && !isReadOnly(cm) &&
- type == "single" && (contained = sel.contains(start)) > -1 &&
- !sel.ranges[contained].empty())
- leftButtonStartDrag(cm, e, start, modifier);
- else
- leftButtonSelect(cm, e, start, type, modifier);
+ output.appendChild(fragment);
}
- // 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, e, start, modifier) {
+ // Cursor-blinking
+ function restartBlink(cm) {
+ if (!cm.state.focused) { return }
var display = cm.display;
- var dragEnd = operation(cm, function(e2) {
- if (webkit) display.scroller.draggable = false;
- cm.state.draggingText = false;
- off(document, "mouseup", dragEnd);
- off(display.scroller, "drop", dragEnd);
- if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) {
- e_preventDefault(e2);
- if (!modifier)
- extendSelection(cm.doc, start);
- display.input.focus();
- // Work around unexplainable focus problem in IE9 (#2127)
- if (ie && ie_version == 9)
- setTimeout(function() {document.body.focus(); display.input.focus();}, 20);
- }
- });
- // Let the drag handler handle this.
- if (webkit) display.scroller.draggable = true;
- cm.state.draggingText = dragEnd;
- // IE's approach to draggable
- if (display.scroller.dragDrop) display.scroller.dragDrop();
- on(document, "mouseup", dragEnd);
- on(display.scroller, "drop", dragEnd);
+ clearInterval(display.blinker);
+ var on = true;
+ display.cursorDiv.style.visibility = "";
+ if (cm.options.cursorBlinkRate > 0)
+ { display.blinker = setInterval(function () {
+ if (!cm.hasFocus()) { onBlur(cm); }
+ display.cursorDiv.style.visibility = (on = !on) ? "" : "hidden";
+ }, cm.options.cursorBlinkRate); }
+ else if (cm.options.cursorBlinkRate < 0)
+ { display.cursorDiv.style.visibility = "hidden"; }
}
- // Normal selection, as opposed to text dragging.
- function leftButtonSelect(cm, e, start, type, addNew) {
- var display = cm.display, doc = cm.doc;
- e_preventDefault(e);
-
- var ourRange, ourIndex, startSel = doc.sel, ranges = startSel.ranges;
- if (addNew && !e.shiftKey) {
- ourIndex = doc.sel.contains(start);
- if (ourIndex > -1)
- ourRange = ranges[ourIndex];
- else
- ourRange = new Range(start, start);
- } else {
- ourRange = doc.sel.primary();
- }
-
- if (e.altKey) {
- type = "rect";
- if (!addNew) ourRange = new Range(start, start);
- start = posFromMouse(cm, e, true, true);
- ourIndex = -1;
- } else if (type == "double") {
- var word = cm.findWordAt(start);
- if (cm.display.shift || doc.extend)
- ourRange = extendRange(doc, ourRange, word.anchor, word.head);
- else
- ourRange = word;
- } else if (type == "triple") {
- var line = new Range(Pos(start.line, 0), clipPos(doc, Pos(start.line + 1, 0)));
- if (cm.display.shift || doc.extend)
- ourRange = extendRange(doc, ourRange, line.anchor, line.head);
- else
- ourRange = line;
- } else {
- ourRange = extendRange(doc, ourRange, start);
+ function ensureFocus(cm) {
+ if (!cm.hasFocus()) {
+ cm.display.input.focus();
+ if (!cm.state.focused) { onFocus(cm); }
}
+ }
- if (!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() && type == "single") {
- setSelection(doc, normalizeSelection(ranges.slice(0, ourIndex).concat(ranges.slice(ourIndex + 1)), 0));
- startSel = doc.sel;
- } else {
- replaceOneSelection(doc, ourIndex, ourRange, sel_mouse);
- }
+ function delayBlurEvent(cm) {
+ cm.state.delayingBlurEvent = true;
+ setTimeout(function () { if (cm.state.delayingBlurEvent) {
+ cm.state.delayingBlurEvent = false;
+ if (cm.state.focused) { onBlur(cm); }
+ } }, 100);
+ }
- var lastPos = start;
- function extendTo(pos) {
- if (cmp(lastPos, pos) == 0) return;
- lastPos = pos;
+ function onFocus(cm, e) {
+ if (cm.state.delayingBlurEvent && !cm.state.draggingText) { cm.state.delayingBlurEvent = false; }
- if (type == "rect") {
- 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 anchor = oldRange.anchor, head = pos;
- if (type != "single") {
- if (type == "double")
- var range = cm.findWordAt(pos);
- else
- var range = new Range(Pos(pos.line, 0), clipPos(doc, Pos(pos.line + 1, 0)));
- 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 = startSel.ranges.slice(0);
- ranges[ourIndex] = new Range(clipPos(doc, anchor), head);
- setSelection(doc, normalizeSelection(ranges, ourIndex), sel_mouse);
+ 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 }
- 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;
+ 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);
+ }
- function extend(e) {
- var curCount = ++counter;
- var cur = posFromMouse(cm, e, true, type == "rect");
- if (!cur) return;
- if (cmp(cur, lastPos) != 0) {
- ensureFocus(cm);
- 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);
+ // 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;
+ var viewTop = Math.max(0, display.scroller.getBoundingClientRect().top);
+ var oldHeight = display.lineDiv.getBoundingClientRect().top;
+ var mustScroll = 0;
+ for (var i = 0; i < display.view.length; i++) {
+ var cur = display.view[i], wrapping = cm.options.lineWrapping;
+ var height = (void 0), width = 0;
+ if (cur.hidden) { continue }
+ oldHeight += cur.line.height;
+ if (ie && ie_version < 8) {
+ var bot = cur.node.offsetTop + cur.node.offsetHeight;
+ height = bot - prevBottom;
+ prevBottom = bot;
} 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);
+ var box = cur.node.getBoundingClientRect();
+ height = box.bottom - box.top;
+ // Check that lines don't extend past the right of the current
+ // editor width
+ if (!wrapping && cur.text.firstChild)
+ { width = cur.text.firstChild.getBoundingClientRect().right - box.left - 1; }
+ }
+ var diff = cur.line.height - height;
+ if (diff > .005 || diff < -.005) {
+ if (oldHeight < viewTop) { mustScroll -= diff; }
+ updateLineHeight(cur.line, height);
+ updateWidgetHeight(cur.line);
+ if (cur.rest) { for (var j = 0; j < cur.rest.length; j++)
+ { updateWidgetHeight(cur.rest[j]); } }
+ }
+ if (width > cm.display.sizerWidth) {
+ var chWidth = Math.ceil(width / charWidth(cm.display));
+ if (chWidth > cm.display.maxLineLength) {
+ cm.display.maxLineLength = chWidth;
+ cm.display.maxLine = cur.line;
+ cm.display.maxLineChanged = true;
+ }
}
}
-
- function done(e) {
- 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);
- on(document, "mousemove", move);
- on(document, "mouseup", up);
+ if (Math.abs(mustScroll) > 2) { display.scroller.scrollTop += mustScroll; }
}
- // Determines whether an event happened in the gutter, and fires the
- // handlers for the corresponding event.
- function gutterEvent(cm, e, type, prevent, signalfn) {
- try { var 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();
+ // 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) {
+ var w = line.widgets[i], parent = w.node.parentNode;
+ if (parent) { w.height = parent.offsetHeight; }
+ } }
+ }
- if (mY > lineBox.bottom || !hasHandler(cm, type)) return e_defaultPrevented(e);
- mY -= lineBox.top - display.viewOffset;
+ // 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;
- 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];
- signalfn(cm, type, cm, line, gutter, e);
- return e_defaultPrevented(e);
+ 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)}
}
- function clickInGutter(cm, e) {
- return gutterEvent(cm, e, "gutterClick", true, signalLater);
- }
+ // SCROLLING THINGS INTO VIEW
- // 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;
+ // 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 }
- function onDrop(e) {
- var cm = this;
- 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 || isReadOnly(cm)) 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) {
- var reader = new FileReader;
- reader.onload = operation(cm, function() {
- text[i] = reader.result;
- if (++read == n) {
- pos = clipPos(cm.doc, pos);
- var change = {from: pos, to: pos, text: splitLines(text.join("\n")), 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() {cm.display.input.focus();}, 20);
- 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
+ end = pos.sticky == "before" ? Pos(pos.line, pos.ch + 1, "before") : pos;
+ pos = pos.ch ? Pos(pos.line, pos.sticky == "before" ? pos.ch - 1 : pos.ch, "after") : 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; }
}
- try {
- var text = e.dataTransfer.getData("Text");
- if (text) {
- if (cm.state.draggingText && !(mac ? e.metaKey : e.ctrlKey))
- var selected = cm.listSelections();
- setSelectionNoUndo(cm.doc, simpleSelection(pos, pos));
- if (selected) for (var i = 0; i < selected.length; ++i)
- replaceRange(cm.doc, "", selected[i].anchor, selected[i].head, "drag");
- cm.replaceSelection(text, "around", "paste");
- cm.display.input.focus();
- }
+ if (scrollPos.scrollLeft != null) {
+ setScrollLeft(cm, scrollPos.scrollLeft);
+ if (Math.abs(cm.doc.scrollLeft - startLeft) > 1) { changed = true; }
}
- catch(e){}
+ if (!changed) { break }
}
+ return rect
}
- 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;
+ // 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); }
+ }
- e.dataTransfer.setData("Text", cm.getSelection());
+ // 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 gutterSpace = cm.options.fixedGutter ? 0 : display.gutters.offsetWidth;
+ var screenleft = cm.curOp && cm.curOp.scrollLeft != null ? cm.curOp.scrollLeft : display.scroller.scrollLeft - gutterSpace;
+ var screenw = displayWidth(cm) - display.gutters.offsetWidth;
+ 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 + gutterSpace - (tooWide ? 0 : 10)); }
+ else if (rect.right > screenw + screenleft - 3)
+ { result.scrollLeft = rect.right + (tooWide ? 0 : 10) - screenw; }
+ return result
+ }
- // 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);
+ // 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);
}
}
- // SCROLL EVENTS
+ 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 setScrollTop(cm, val) {
- if (Math.abs(cm.doc.scrollTop - val) < 2) return;
+ 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.max(0, Math.min(cm.display.scroller.scrollHeight - cm.display.scroller.clientHeight, val));
+ if (cm.display.scroller.scrollTop == val && !forceScroll) { return }
cm.doc.scrollTop = val;
- if (!gecko) updateDisplaySimple(cm, {top: val});
- if (cm.display.scroller.scrollTop != val) cm.display.scroller.scrollTop = val;
cm.display.scrollbars.setScrollTop(val);
- if (gecko) updateDisplaySimple(cm);
- startWorker(cm, 100);
+ 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) {
- if (isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) return;
- val = Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clientWidth);
+ function setScrollLeft(cm, val, isScroller, forceScroll) {
+ val = Math.max(0, 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;
+ if (cm.display.scroller.scrollLeft != val) { cm.display.scroller.scrollLeft = val; }
cm.display.scrollbars.setScrollLeft(val);
}
- // Since the delta values reported on mouse wheel events are
- // unstandardized between browsers and even browser versions, and
- // generally horribly unpredictable, this code starts by measuring
- // the scroll effect that the first few mouse wheel events have,
- // and, from that, detects the way it can convert deltas to pixel
- // offsets afterwards.
- //
- // The reason we want to know the amount a wheel event will scroll
- // is that it gives us a chance to update the display before the
- // actual scrolling happens, reducing flickering.
+ // SCROLLBARS
- var wheelSamples = 0, 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;
+ // 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 wheelEventDelta = function(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};
- };
- CodeMirror.wheelEventPixels = function(e) {
- var delta = wheelEventDelta(e);
- delta.x *= wheelPixelsPerUnit;
- delta.y *= wheelPixelsPerUnit;
- return delta;
- };
+ 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");
+ vert.tabIndex = horiz.tabIndex = -1;
+ place(vert); place(horiz);
- function onScrollWheel(cm, e) {
- var delta = wheelEventDelta(e), dx = delta.x, dy = delta.y;
+ on(vert, "scroll", function () {
+ if (vert.clientHeight) { scroll(vert.scrollTop, "vertical"); }
+ });
+ on(horiz, "scroll", function () {
+ if (horiz.clientWidth) { scroll(horiz.scrollLeft, "horizontal"); }
+ });
- var display = cm.display, scroll = display.scroller;
- // Quit if there's nothing to scroll here
- if (!(dx && scroll.scrollWidth > scroll.clientWidth ||
- dy && scroll.scrollHeight > scroll.clientHeight)) return;
+ 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"; }
+ };
- // 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;
- }
- }
- }
+ 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.scrollTop = 0;
+ 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";
}
- // 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)
- setScrollTop(cm, Math.max(0, Math.min(scroll.scrollTop + dy * wheelPixelsPerUnit, scroll.scrollHeight - scroll.clientHeight)));
- setScrollLeft(cm, Math.max(0, Math.min(scroll.scrollLeft + dx * wheelPixelsPerUnit, scroll.scrollWidth - scroll.clientWidth)));
- e_preventDefault(e);
- display.wheelStartX = null; // Abort measurement, if in progress
- return;
+ if (!this.checkedZeroWidth && measure.clientHeight > 0) {
+ if (sWidth == 0) { this.zeroWidthHack(); }
+ this.checkedZeroWidth = true;
}
- // '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});
- }
+ return {right: needsV ? sWidth : 0, bottom: needsH ? sWidth : 0}
+ };
- 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;
- }
- }
- }
+ NativeScrollbars.prototype.setScrollLeft = function (pos) {
+ if (this.horiz.scrollLeft != pos) { this.horiz.scrollLeft = pos; }
+ if (this.disableHoriz) { this.enableZeroWidthBar(this.horiz, this.disableHoriz, "horiz"); }
+ };
- // KEY EVENTS
+ NativeScrollbars.prototype.setScrollTop = function (pos) {
+ if (this.vert.scrollTop != pos) { this.vert.scrollTop = pos; }
+ if (this.disableVert) { this.enableZeroWidthBar(this.vert, this.disableVert, "vert"); }
+ };
- // 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 (isReadOnly(cm)) 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;
- }
+ 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;
+ };
- 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);
- }
+ 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);
+ };
- 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);
+ NativeScrollbars.prototype.clear = function () {
+ var parent = this.horiz.parentNode;
+ parent.removeChild(this.horiz);
+ parent.removeChild(this.vert);
+ };
- if (result == "multi")
- cm.state.keySeq = name;
- if (result == "handled")
- signalLater(cm, "keyHandled", cm, name, e);
+ var NullScrollbars = function () {};
- if (result == "handled" || result == "multi") {
- e_preventDefault(e);
- restartBlink(cm);
- }
+ NullScrollbars.prototype.update = function () { return {bottom: 0, right: 0} };
+ NullScrollbars.prototype.setScrollLeft = function () {};
+ NullScrollbars.prototype.setScrollTop = function () {};
+ NullScrollbars.prototype.clear = function () {};
- if (seq && !result && /\'$/.test(name)) {
- e_preventDefault(e);
- return true;
+ 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;
}
- 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); });
- }
+ // 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);
- var lastStoppedKey = null;
- function onKeyDown(e) {
- var cm = this;
- ensureFocus(cm);
- 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");
- }
+ 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";
- // Turn mouse into crosshair when Alt is held on Mac.
- if (code == 18 && !/\bCodeMirror-crosshair\b/.test(cm.display.lineDiv.className))
- showCrossHair(cm);
+ 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 = ""; }
}
- function showCrossHair(cm) {
- var lineDiv = cm.display.lineDiv;
- addClass(lineDiv, "CodeMirror-crosshair");
+ var scrollbarModel = {"native": NativeScrollbars, "null": NullScrollbars};
- function up(e) {
- if (e.keyCode == 18 || !e.altKey) {
- rmClass(lineDiv, "CodeMirror-crosshair");
- off(document, "keyup", up);
- off(document, "mouseover", up);
- }
+ function initScrollbars(cm) {
+ if (cm.display.scrollbars) {
+ cm.display.scrollbars.clear();
+ if (cm.display.scrollbars.addClass)
+ { rmClass(cm.display.wrapper, cm.display.scrollbars.addClass); }
}
- on(document, "keyup", up);
- on(document, "mouseover", up);
- }
- function onKeyUp(e) {
- if (e.keyCode == 16) this.doc.sel.shift = false;
- signalDOMEvent(this, e);
+ 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); }
}
- 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);
- if (handleCharBinding(cm, e, ch)) return;
- cm.display.input.onKeyPress(e);
- }
+ // 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.
- // FOCUS/BLUR EVENTS
+ 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: 0, // 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
+ markArrays: null // Used by addMarkedSpan
+ };
+ pushOperation(cm.curOp);
+ }
- function onFocus(cm) {
- if (cm.options.readOnly == "nocursor") return;
- if (!cm.state.focused) {
- signal(cm, "focus", cm);
- 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() { cm.display.input.reset(true); }, 20); // Issue #1730
- }
- cm.display.input.receivedFocus();
- }
- restartBlink(cm);
+ // Finish an operation, updating the display and signalling delayed events
+ function endOperation(cm) {
+ var op = cm.curOp;
+ if (op) { finishOperation(op, function (group) {
+ for (var i = 0; i < group.ops.length; i++)
+ { group.ops[i].cm.curOp = null; }
+ endOperations(group);
+ }); }
}
- function onBlur(cm) {
- if (cm.state.focused) {
- signal(cm, "blur", cm);
- 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);
+
+ // 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]); }
}
- // CONTEXT MENU HANDLING
+ function endOperation_R1(op) {
+ var cm = op.cm, display = cm.display;
+ maybeClipScrollbars(cm);
+ if (op.updateMaxLine) { findMaxLine(cm); }
- // 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;
- cm.display.input.onContextMenu(e);
+ 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 contextMenuInGutter(cm, e) {
- if (!hasHandler(cm, "gutterContextMenu")) return false;
- return gutterEvent(cm, e, "gutterContextMenu", false, signal);
+ function endOperation_W1(op) {
+ op.updatedDisplay = op.mustUpdate && updateDisplayIfNeeded(op.cm, op.update);
}
- // UPDATING
+ function endOperation_R2(op) {
+ var cm = op.cm, display = cm.display;
+ if (op.updatedDisplay) { updateHeightsInViewport(cm); }
- // Compute the position of the end of a change (its 'to' property
- // refers to the pre-change end).
- var changeEnd = CodeMirror.changeEnd = function(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));
- };
+ op.barMeasure = measureForScrollbars(cm);
- // 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);
+ // 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));
+ }
- 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);
+ if (op.updatedDisplay || op.selectionChanged)
+ { op.preparedSelection = display.input.prepareSelection(); }
}
- 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)));
+ 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;
}
- 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);
- }
+ var takeFocus = op.focus && op.focus == activeElt();
+ 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); }
- // 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);
+ if (op.selectionChanged) { restartBlink(cm); }
+
+ if (cm.state.focused && op.updateInput)
+ { cm.display.input.reset(op.typing); }
+ if (takeFocus) { ensureFocus(op.cm); }
}
- // 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() { this.canceled = true; }
- };
- if (update) obj.update = function(from, to, text, origin) {
- if (from) this.from = clipPos(doc, from);
- if (to) this.to = clipPos(doc, to);
- if (text) this.text = text;
- if (origin !== undefined) this.origin = origin;
- };
- signal(doc, "beforeChange", doc, obj);
- if (doc.cm) signal(doc.cm, "beforeChange", doc.cm, obj);
+ function endOperation_finish(op) {
+ var cm = op.cm, display = cm.display, doc = cm.doc;
- if (obj.canceled) return null;
- return {from: obj.from, to: obj.to, text: obj.text, origin: obj.origin};
- }
+ if (op.updatedDisplay) { postUpdateDisplay(cm, op.update); }
- // 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;
- }
+ // 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; }
- if (hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange")) {
- change = filterChange(doc, change, true);
- if (!change) return;
- }
+ // Propagate the scroll position to the actual DOM scroller
+ if (op.scrollTop != null) { setScrollTop(cm, op.scrollTop, op.forceScroll); }
- // 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);
+ 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);
}
- }
- 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);
+ // 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"); } } }
- makeChangeSingleDoc(doc, change, selAfter, stretchSpansOverChange(doc, change));
- var rebased = [];
+ if (display.wrapper.offsetHeight)
+ { doc.scrollTop = cm.display.scroller.scrollTop; }
- 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));
- });
+ // Fire change events, and delayed event handlers
+ if (op.changeObjs)
+ { signal(cm, "changes", cm, op.changeObjs); }
+ if (op.update)
+ { op.update.finish(); }
}
- // Revert a change stored in a document's history.
- function makeChangeFromHistory(doc, type, allowSelectionOnly) {
- if (doc.cm && doc.cm.state.suppressEdits) 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)
- for (var i = 0; i < source.length; i++) {
- event = source[i];
- if (allowSelectionOnly ? event.ranges && !event.equals(doc.sel) : !event.ranges)
- break;
+ // 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); }
}
- 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;
+ }
+ // 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); }
}
+ }
- // 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;
+ // HIGHLIGHT WORKER
- var filter = hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange");
+ function startWorker(cm, time) {
+ if (cm.doc.highlightFrontier < cm.display.viewTo)
+ { cm.state.highlight.set(time, bind(highlightWorker, cm)); }
+ }
- for (var i = event.changes.length - 1; i >= 0; --i) {
- var change = event.changes[i];
- change.origin = type;
- if (filter && !filterChange(doc, change, false)) {
- source.length = 0;
- return;
+ 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"); }
+ }); }
+ }
- antiChanges.push(historyChangeFromChange(doc, change));
+ // DISPLAY DRAWING
- 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 = [];
+ var DisplayUpdate = function(cm, viewport, force) {
+ var display = cm.display;
- // 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));
- });
+ 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 () {
+ for (var i = 0; i < this.events.length; i++)
+ { signal.apply(null, this.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;
}
}
- // 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");
+ 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
}
- // 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;
+ function restoreSelection(snapshot) {
+ if (!snapshot || !snapshot.activeElt || snapshot.activeElt == activeElt()) { return }
+ snapshot.activeElt.focus();
+ if (!/^(INPUT|TEXTAREA)$/.test(snapshot.activeElt.nodeName) &&
+ 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);
}
- 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};
- }
+ // 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;
- change.removed = getBetween(doc, change.from, change.to);
+ if (update.editorIsHidden) {
+ resetView(cm);
+ return false
+ }
- if (!selAfter) selAfter = computeSelAfterChange(doc, change);
- if (doc.cm) makeChangeSingleDocInEditor(doc.cm, change, spans);
- else updateDoc(doc, change, spans);
- setSelectionNoUndo(doc, selAfter, sel_dontScroll);
- }
+ // 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 }
- // 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;
+ if (maybeUpdateLineNumberWidth(cm)) {
+ resetView(cm);
+ update.dims = getDimensions(cm);
+ }
- 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;
- }
- });
+ // 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);
}
- if (doc.sel.contains(change.from, change.to) > -1)
- signalCursorActivity(cm);
+ var different = from != display.viewFrom || to != display.viewTo ||
+ display.lastWrapHeight != update.wrapperHeight || display.lastWrapWidth != update.wrapperWidth;
+ adjustView(cm, from, to);
- updateDoc(doc, change, spans, estimateHeight(cm));
+ 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";
- 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;
- }
+ var toUpdate = countDirtyView(cm);
+ if (!different && toUpdate == 0 && !update.force && display.renderedView == display.view &&
+ (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo))
+ { return false }
- // Adjust frontier, schedule worker
- doc.frontier = Math.min(doc.frontier, from.line);
- startWorker(cm, 400);
+ // 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);
- 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);
+ // 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;
- 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);
+ if (different) {
+ display.lastWrapHeight = update.wrapperHeight;
+ display.lastWrapWidth = update.wrapperWidth;
+ startWorker(cm, 400);
}
- 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 = splitLines(code);
- makeChange(doc, {from: from, to: to, text: code, origin: origin});
+ display.updateLineNumbers = null;
+
+ return true
}
- // SCROLLING THINGS INTO VIEW
+ function postUpdateDisplay(cm, update) {
+ var viewport = update.viewport;
- // 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, coords) {
- if (signalDOMEvent(cm, "scrollCursorIntoView")) return;
+ 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 }
+ } else if (first) {
+ update.visible = visibleLines(cm.display, cm.doc, viewport);
+ }
+ if (!updateDisplayIfNeeded(cm, update)) { break }
+ updateHeightsInViewport(cm);
+ var barMeasure = measureForScrollbars(cm);
+ updateSelection(cm);
+ updateScrollbars(cm, barMeasure);
+ setDocumentHeight(cm, barMeasure);
+ update.force = false;
+ }
- var display = cm.display, box = display.sizer.getBoundingClientRect(), doScroll = null;
- if (coords.top + box.top < 0) doScroll = true;
- else if (coords.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) doScroll = false;
- if (doScroll != null && !phantom) {
- var scrollNode = elt("div", "\u200b", null, "position: absolute; top: " +
- (coords.top - display.viewOffset - paddingTop(cm.display)) + "px; height: " +
- (coords.bottom - coords.top + scrollGap(cm) + display.barHeight) + "px; left: " +
- coords.left + "px; width: 2px;");
- 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;
- for (var limit = 0; limit < 5; limit++) {
- var changed = false, coords = cursorCoords(cm, pos);
- var endCoords = !end || end == pos ? coords : cursorCoords(cm, end);
- var scrollPos = calculateScrollPos(cm, Math.min(coords.left, endCoords.left),
- Math.min(coords.top, endCoords.top) - margin,
- Math.max(coords.left, endCoords.left),
- Math.max(coords.bottom, endCoords.bottom) + margin);
- var startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft;
- if (scrollPos.scrollTop != null) {
- setScrollTop(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 coords;
- }
-
- // Scroll a given set of coordinates into view (immediately).
- function scrollIntoView(cm, x1, y1, x2, y2) {
- var scrollPos = calculateScrollPos(cm, x1, y1, x2, y2);
- if (scrollPos.scrollTop != null) setScrollTop(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, x1, y1, x2, y2) {
- var display = cm.display, snapMargin = textHeight(cm.display);
- if (y1 < 0) y1 = 0;
- var screentop = cm.curOp && cm.curOp.scrollTop != null ? cm.curOp.scrollTop : display.scroller.scrollTop;
- var screen = displayHeight(cm), result = {};
- if (y2 - y1 > screen) y2 = y1 + screen;
- var docBottom = cm.doc.height + paddingVert(display);
- var atTop = y1 < snapMargin, atBottom = y2 > docBottom - snapMargin;
- if (y1 < screentop) {
- result.scrollTop = atTop ? 0 : y1;
- } else if (y2 > screentop + screen) {
- var newTop = Math.min(y1, (atBottom ? docBottom : y2) - 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 = x2 - x1 > screenw;
- if (tooWide) x2 = x1 + screenw;
- if (x1 < 10)
- result.scrollLeft = 0;
- else if (x1 < screenleft)
- result.scrollLeft = Math.max(0, x1 - (tooWide ? 0 : 10));
- else if (x2 > screenw + screenleft - 3)
- result.scrollLeft = x2 + (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 addToScrollPos(cm, left, top) {
- if (left != null || top != null) resolveScrollToPos(cm);
- if (left != null)
- cm.curOp.scrollLeft = (cm.curOp.scrollLeft == null ? cm.doc.scrollLeft : cm.curOp.scrollLeft) + left;
- if (top != null)
- 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(), from = cur, to = cur;
- if (!cm.options.lineWrapping) {
- from = cur.ch ? Pos(cur.line, cur.ch - 1) : cur;
- to = Pos(cur.line, cur.ch + 1);
+ 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;
}
- cm.curOp.scrollToPos = {from: from, to: to, margin: cm.options.cursorScrollMargin, isCursor: true};
}
- // 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);
- var sPos = calculateScrollPos(cm, Math.min(from.left, to.left),
- Math.min(from.top, to.top) - range.margin,
- Math.max(from.right, to.right),
- Math.max(from.bottom, to.bottom) + range.margin);
- cm.scrollTo(sPos.scrollLeft, sPos.scrollTop);
+ 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();
}
}
- // API UTILITIES
-
- // 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 = getStateBefore(cm, n);
- }
+ // 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;
- 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;
+ 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
}
- 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");
- } 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 = 0; i < doc.sel.ranges.length; i++) {
- var range = doc.sel.ranges[i];
- if (range.head.line == n && range.head.ch < curSpaceString.length) {
- var pos = Pos(n, curSpaceString.length);
- replaceOneSelection(doc, i, new Range(pos, pos));
- break;
+ 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;
}
- line.stateAfter = null;
+ while (cur) { cur = rm(cur); }
}
- // 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;
+ function updateGutterSpace(display) {
+ var width = display.gutters.offsetWidth;
+ display.sizer.style.marginLeft = width + "px";
+ // Send an event to consumers responding to changes in gutter width.
+ signalLater(display, "gutterChanged", display);
}
- // 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;
- }
+ 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";
+ }
+
+ // 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; }
}
- 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);
- });
+ 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 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 line = pos.line, ch = pos.ch, origDir = dir;
- var lineObj = getLine(doc, line);
- var possible = true;
- function findNextLine() {
- var l = line + dir;
- if (l < doc.first || l >= doc.first + doc.size) return (possible = false);
- line = l;
- return lineObj = getLine(doc, l);
- }
- function moveOnce(boundToLine) {
- var next = (visually ? moveVisually : moveLogically)(lineObj, ch, dir, true);
- if (next == null) {
- if (!boundToLine && findNextLine()) {
- if (visually) ch = (dir < 0 ? lineRight : lineLeft)(lineObj);
- else ch = dir < 0 ? lineObj.text.length : 0;
- } else return (possible = false);
- } else ch = next;
- return true;
+ // 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.display);
+ return true
}
+ return false
+ }
- 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(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();}
- break;
- }
-
- if (type) sawType = type;
- if (dir > 0 && !moveOnce(!first)) break;
+ function getGutters(gutters, lineNumbers) {
+ var result = [], sawLineNumbers = false;
+ for (var i = 0; i < gutters.length; i++) {
+ var name = gutters[i], style = null;
+ if (typeof name != "string") { style = name.style; name = name.className; }
+ if (name == "CodeMirror-linenumbers") {
+ if (!lineNumbers) { continue }
+ else { sawLineNumbers = true; }
}
+ result.push({className: name, style: style});
}
- var result = skipAtomic(doc, Pos(line, ch), origDir, true);
- if (!possible) result.hitSide = true;
- return result;
+ if (lineNumbers && !sawLineNumbers) { result.push({className: "CodeMirror-linenumbers", style: null}); }
+ 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);
- y = pos.top + dir * (pageSize - (dir < 0 ? 1.5 : .5) * textHeight(cm.display));
- } else if (unit == "line") {
- y = dir > 0 ? pos.bottom + 3 : pos.top - 3;
- }
- for (;;) {
- var target = coordsChar(cm, x, y);
- if (!target.outside) break;
- if (dir < 0 ? y <= 0 : y >= doc.height) { target.hitSide = true; break; }
- y += dir * 5;
+ // Rebuild the gutter elements, ensure the margin to the left of the
+ // code matches their width.
+ function renderGutters(display) {
+ var gutters = display.gutters, specs = display.gutterSpecs;
+ removeChildren(gutters);
+ display.lineGutter = null;
+ for (var i = 0; i < specs.length; ++i) {
+ var ref = specs[i];
+ var className = ref.className;
+ var style = ref.style;
+ var gElt = gutters.appendChild(elt("div", null, "CodeMirror-gutter " + className));
+ if (style) { gElt.style.cssText = style; }
+ if (className == "CodeMirror-linenumbers") {
+ display.lineGutter = gElt;
+ gElt.style.width = (display.lineNumWidth || 1) + "px";
+ }
}
- return target;
+ gutters.style.display = specs.length ? "" : "none";
+ updateGutterSpace(display);
}
- // EDITOR METHODS
-
- // 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.
-
- 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);
- },
-
- 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;
- }
- },
+ function updateGutters(cm) {
+ renderGutters(cm.display);
+ regChange(cm);
+ alignHorizontally(cm);
+ }
- 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.");
- this.state.overlays.push({mode: mode, modeSpec: spec, opaque: options && options.opaque});
- this.state.modeGen++;
- regChange(this);
- }),
- removeOverlay: methodOp(function(spec) {
- 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.state.modeGen++;
- regChange(this);
- return;
- }
- }
- }),
+ // The display handles the DOM integration, both for input reading
+ // and content drawing. It holds references to DOM nodes and
+ // display-related state.
- 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 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.lastLine(), to.line - (to.ch ? 0 : 1)) + 1;
- for (var j = start; j < end; ++j)
- indentLine(this, j, how);
- var newRanges = this.doc.sel.ranges;
- if (from.ch == 0 && ranges.length == newRanges.length && newRanges[i].from().ch > 0)
- replaceOneSelection(this.doc, i, new Range(from, newRanges[i].to()), sel_dontScroll);
- } else if (range.head.line > end) {
- indentLine(this, range.head.line, how, true);
- end = range.head.line;
- if (i == this.doc.sel.primIndex) ensureCursorVisible(this);
- }
- }
- }),
+ function Display(place, doc, input, options) {
+ var d = this;
+ this.input = input;
- // 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);
- },
+ // 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");
- getLineTokens: function(line, precise) {
- return takeToken(this, Pos(line), precise, true);
- },
+ // This attribute is respected by automatic translation systems such as Google Translate,
+ // and may also be respected by tools used by human translators.
+ d.wrapper.setAttribute('translate', 'no');
- 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("cm-overlay ") : -1;
- return cut < 0 ? type : cut == 0 ? null : type.slice(0, cut - 1);
- },
+ // 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; }
- getModeAt: function(pos) {
- var mode = this.doc.mode;
- if (!mode.innerMode) return mode;
- return CodeMirror.innerMode(mode, this.getTokenAt(pos).state).mode;
- },
+ if (place) {
+ if (place.appendChild) { place.appendChild(d.wrapper); }
+ else { place(d.wrapper); }
+ }
- getHelper: function(pos, type) {
- return this.getHelpers(pos, type)[0];
- },
+ // 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;
- getHelpers: function(pos, type) {
- var found = [];
- if (!helpers.hasOwnProperty(type)) return helpers;
- 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 = 0; i < help._global.length; i++) {
- var cur = help._global[i];
- if (cur.pred(mode, this) && indexOf(found, cur.val) == -1)
- found.push(cur.val);
- }
- return found;
- },
+ d.nativeBarWidth = d.barHeight = d.barWidth = 0;
+ d.scrollbarsClipped = false;
- getStateAfter: function(line, precise) {
- var doc = this.doc;
- line = clipLine(doc, line == null ? doc.first + doc.size - 1: line);
- return getStateBefore(this, line + 1, precise);
- },
+ // 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;
- 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");
- },
+ d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null;
- charCoords: function(pos, mode) {
- return charCoords(this, clipPos(this.doc, pos), mode || "page");
- },
+ // 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;
- coordsChar: function(coords, mode) {
- coords = fromCoordSystem(this, coords, mode || "page");
- return coordsChar(this, coords.left, coords.top);
- },
+ // Used for measuring wheel scrolling granularity
+ d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null;
- 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) {
- var end = false, 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; }
- var lineObj = getLine(this.doc, line);
- return intoCoordSystem(this, lineObj, {top: 0, left: 0}, mode || "page").top +
- (end ? this.doc.height - heightAtLine(lineObj) : 0);
- },
+ // True when shift is held down.
+ d.shift = false;
- defaultTextHeight: function() { return textHeight(this.display); },
- defaultCharWidth: function() { return charWidth(this.display); },
+ // Used to track whether anything happened since the context menu
+ // was opened.
+ d.selForContextMenu = null;
- setGutterMarker: methodOp(function(line, gutterID, value) {
- return changeLine(this.doc, line, "gutter", function(line) {
- var markers = line.gutterMarkers || (line.gutterMarkers = {});
- markers[gutterID] = value;
- if (!value && isEmpty(markers)) line.gutterMarkers = null;
- return true;
- });
- }),
+ d.activeTouch = null;
- clearGutter: methodOp(function(gutterID) {
- var cm = this, doc = cm.doc, i = doc.first;
- doc.iter(function(line) {
- if (line.gutterMarkers && line.gutterMarkers[gutterID]) {
- line.gutterMarkers[gutterID] = null;
- regLineChange(cm, i, "gutter");
- if (isEmpty(line.gutterMarkers)) line.gutterMarkers = null;
- }
- ++i;
- });
- }),
+ d.gutterSpecs = getGutters(options.gutters, options.lineNumbers);
+ renderGutters(d);
- addLineWidget: methodOp(function(handle, node, options) {
- return addLineWidget(this, handle, node, options);
- }),
+ input.init(d);
+ }
- removeLineWidget: function(widget) { widget.clear(); },
+ // Since the delta values reported on mouse wheel events are
+ // unstandardized between browsers and even browser versions, and
+ // generally horribly unpredictable, this code starts by measuring
+ // the scroll effect that the first few mouse wheel events have,
+ // and, from that, detects the way it can convert deltas to pixel
+ // offsets afterwards.
+ //
+ // The reason we want to know the amount a wheel event will scroll
+ // is that it gives us a chance to update the display before the
+ // actual scrolling happens, reducing flickering.
- lineInfo: function(line) {
- if (typeof line == "number") {
- if (!isLine(this.doc, line)) return null;
- var n = line;
- line = getLine(this.doc, line);
- if (!line) return null;
- } else {
- var 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};
- },
+ var wheelSamples = 0, 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; }
- 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, top, left + node.offsetWidth, top + node.offsetHeight);
- },
+ 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
+ }
- triggerOnKeyDown: methodOp(onKeyDown),
- triggerOnKeyPress: methodOp(onKeyPress),
- triggerOnKeyUp: onKeyUp,
+ function onScrollWheel(cm, e) {
+ var delta = wheelEventDelta(e), dx = delta.x, dy = delta.y;
+ var pixelsPerUnit = wheelPixelsPerUnit;
+ if (e.deltaMode === 0) {
+ dx = e.deltaX;
+ dy = e.deltaY;
+ pixelsPerUnit = 1;
+ }
- execCommand: function(cmd) {
- if (commands.hasOwnProperty(cmd))
- return commands[cmd](this);
- },
+ 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 }
- findPosH: function(from, amount, unit, visually) {
- var dir = 1;
- if (amount < 0) { dir = -1; amount = -amount; }
- for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) {
- cur = findPosH(this.doc, cur, dir, unit, visually);
- if (cur.hitSide) break;
+ // 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
+ }
+ }
}
- return cur;
- },
-
- moveH: methodOp(function(dir, unit) {
- var cm = this;
- cm.extendSelectionsBy(function(range) {
- if (cm.display.shift || cm.doc.extend || range.empty())
- return findPosH(cm.doc, range.head, dir, unit, cm.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 dir = 1, x = goalColumn;
- if (amount < 0) { dir = -1; amount = -amount; }
- for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) {
- var coords = cursorCoords(this, cur, "div");
- if (x == null) x = coords.left;
- else coords.left = x;
- cur = findPosV(this, coords, dir, unit);
- if (cur.hitSide) break;
- }
- return cur;
- },
-
- moveV: methodOp(function(dir, unit) {
- var cm = this, doc = this.doc, goals = [];
- var collapse = !cm.display.shift && !doc.extend && doc.sel.somethingSelected();
- doc.extendSelectionsBy(function(range) {
- if (collapse)
- return dir < 0 ? range.from() : range.to();
- var headPos = cursorCoords(cm, range.head, "div");
- if (range.goalColumn != null) headPos.left = range.goalColumn;
- goals.push(headPos.left);
- var pos = findPosV(cm, headPos, dir, unit);
- if (unit == "page" && range == doc.sel.primary())
- addToScrollPos(cm, null, charCoords(cm, 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.xRel < 0 || 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(); },
+ // 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 && pixelsPerUnit != null) {
+ if (dy && canScrollY)
+ { updateScrollTop(cm, Math.max(0, scroll.scrollTop + dy * pixelsPerUnit)); }
+ setScrollLeft(cm, Math.max(0, scroll.scrollLeft + dx * pixelsPerUnit));
+ // 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
+ }
- scrollTo: methodOp(function(x, y) {
- if (x != null || y != null) resolveScrollToPos(this);
- if (x != null) this.curOp.scrollLeft = x;
- if (y != null) this.curOp.scrollTop = 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)};
- },
+ // 'Project' the visible viewport to cover the area that is being
+ // scrolled into view (if we know enough to estimate it).
+ if (dy && pixelsPerUnit != null) {
+ var pixels = dy * pixelsPerUnit;
+ 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});
+ }
- 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) {
- resolveScrollToPos(this);
- this.curOp.scrollToPos = range;
+ if (wheelSamples < 20 && e.deltaMode !== 0) {
+ 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 {
- var sPos = calculateScrollPos(this, Math.min(range.from.left, range.to.left),
- Math.min(range.from.top, range.to.top) - range.margin,
- Math.max(range.from.right, range.to.right),
- Math.max(range.from.bottom, range.to.bottom) + range.margin);
- this.scrollTo(sPos.scrollLeft, sPos.scrollTop);
+ display.wheelDX += dx; display.wheelDY += dy;
}
- }),
-
- setSize: methodOp(function(width, height) {
- var cm = this;
- function interpret(val) {
- return typeof val == "number" || /^\d+$/.test(String(val)) ? val + "px" : val;
- }
- if (width != null) cm.display.wrapper.style.width = interpret(width);
- if (height != null) cm.display.wrapper.style.height = interpret(height);
- if (cm.options.lineWrapping) clearLineMeasurementCache(this);
- var lineNo = cm.display.viewFrom;
- cm.doc.iter(lineNo, cm.display.viewTo, function(line) {
- if (line.widgets) for (var i = 0; i < line.widgets.length; i++)
- if (line.widgets[i].noHScroll) { regLineChange(cm, lineNo, "widget"); break; }
- ++lineNo;
- });
- cm.curOp.forceUpdate = true;
- signal(cm, "refresh", this);
- }),
+ }
+ }
- operation: function(f){return runInOp(this, f);},
-
- refresh: methodOp(function() {
- var oldHeight = this.display.cachedTextHeight;
- regChange(this);
- this.curOp.forceUpdate = true;
- clearCaches(this);
- this.scrollTo(this.doc.scrollLeft, this.doc.scrollTop);
- updateGutterSpace(this);
- if (oldHeight == null || Math.abs(oldHeight - textHeight(this.display)) > .5)
- estimateLineHeights(this);
- signal(this, "refresh", this);
- }),
+ // 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;
+ };
- swapDoc: methodOp(function(doc) {
- var old = this.doc;
- old.cm = null;
- attachDoc(this, doc);
- clearCaches(this);
- this.display.input.reset();
- this.scrollTo(doc.scrollLeft, doc.scrollTop);
- this.curOp.forceScroll = true;
- signalLater(this, "swapDoc", this, old);
- return old;
- }),
+ Selection.prototype.primary = function () { return this.ranges[this.primIndex] };
- 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;}
+ Selection.prototype.equals = function (other) {
+ 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.ranges[i], there = other.ranges[i];
+ if (!equalCursorPos(here.anchor, there.anchor) || !equalCursorPos(here.head, there.head)) { return false }
+ }
+ return true
};
- eventMixin(CodeMirror);
- // OPTION DEFAULTS
+ Selection.prototype.deepCopy = function () {
+ var out = [];
+ for (var i = 0; i < this.ranges.length; i++)
+ { out[i] = new Range(copyPos(this.ranges[i].anchor), copyPos(this.ranges[i].head)); }
+ return new Selection(out, this.primIndex)
+ };
- // The default configuration options.
- var defaults = CodeMirror.defaults = {};
- // Functions to run when options are changed.
- var optionHandlers = CodeMirror.optionHandlers = {};
+ Selection.prototype.somethingSelected = function () {
+ for (var i = 0; i < this.ranges.length; i++)
+ { if (!this.ranges[i].empty()) { return true } }
+ return false
+ };
- 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;
- }
+ Selection.prototype.contains = function (pos, end) {
+ if (!end) { end = pos; }
+ for (var i = 0; i < this.ranges.length; i++) {
+ var range = this.ranges[i];
+ if (cmp(end, range.from()) >= 0 && cmp(pos, range.to()) <= 0)
+ { return i }
+ }
+ return -1
+ };
- // Passed to option handlers when there is no old value.
- var Init = CodeMirror.Init = {toString: function(){return "CodeMirror.Init";}};
+ var Range = function(anchor, head) {
+ this.anchor = anchor; this.head = head;
+ };
- // 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) {
- cm.setValue(val);
- }, true);
- option("mode", null, function(cm, val) {
- cm.doc.modeOption = val;
- loadMode(cm);
- }, true);
+ 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 };
- option("indentUnit", 2, loadMode, true);
- option("indentWithTabs", false);
- option("smartIndent", true);
- option("tabSize", 4, function(cm) {
- resetModeState(cm);
- clearCaches(cm);
- regChange(cm);
- }, true);
- option("specialChars", /[\t\u0000-\u0019\u00ad\u200b-\u200f\u2028\u2029\ufeff]/g, function(cm, val) {
- cm.options.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"), "g");
- cm.refresh();
- }, true);
- option("specialCharPlaceholder", defaultSpecialCharPlaceholder, function(cm) {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("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 != CodeMirror.Init && getKeyMap(old);
- if (prev && prev.detach) prev.detach(cm, next);
- if (next.attach) next.attach(cm, prev || null);
- });
- option("extraKeys", 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) {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("readOnly", false, function(cm, val) {
- if (val == "nocursor") {
- onBlur(cm);
- cm.display.input.blur();
- cm.display.disabled = true;
- } else {
- cm.display.disabled = false;
- if (!val) cm.display.input.reset();
+ // Take an unsorted, potentially overlapping set of ranges, and
+ // build a selection out of it. 'Consumes' ranges array (modifying
+ // it).
+ function normalizeSelection(cm, ranges, primIndex) {
+ var mayTouch = cm && cm.options.selectionsMayTouch;
+ 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];
+ var diff = cmp(prev.to(), cur.from());
+ if (mayTouch && !cur.empty() ? diff > 0 : diff >= 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));
+ }
}
- });
- option("disableInput", false, function(cm, val) {if (!val) cm.display.input.reset();}, true);
- option("dragDrop", true);
-
- 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){cm.doc.history.undoDepth = val;});
- option("historyEventDelay", 1250);
- option("viewportMargin", 10, function(cm){cm.refresh();}, true);
- option("maxHighlightLength", 10000, resetModeState, true);
- option("moveInputWithCursor", true, function(cm, val) {
- if (!val) cm.display.input.resetPosition();
- });
+ return new Selection(ranges, primIndex)
+ }
- option("tabindex", null, function(cm, val) {
- cm.display.input.getField().tabIndex = val || "";
- });
- option("autofocus", null);
+ function simpleSelection(anchor, head) {
+ return new Selection([new Range(anchor, head || anchor)], 0)
+ }
- // MODE DEFINITION AND QUERYING
+ // 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))
+ }
- // Known modes, by name and by MIME
- var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {};
+ // 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) }
- // 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;
- if (arguments.length > 2)
- mode.dependencies = Array.prototype.slice.call(arguments, 2);
- modes[name] = mode;
- };
-
- CodeMirror.defineMIME = function(mime, spec) {
- mimeModes[mime] = spec;
- };
+ 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)
+ }
- // Given a MIME type, a {name, ...options} config object, or a name
- // string, return a mode config object.
- CodeMirror.resolveMode = function(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 CodeMirror.resolveMode("application/xml");
+ 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)));
}
- if (typeof spec == "string") return {name: spec};
- else return spec || {name: "null"};
- };
+ return normalizeSelection(doc.cm, out, doc.sel.primIndex)
+ }
- // Given a mode spec (anything that resolveMode accepts), find and
- // initialize an actual mode object.
- CodeMirror.getMode = function(options, spec) {
- var spec = CodeMirror.resolveMode(spec);
- var mfactory = modes[spec.name];
- if (!mfactory) return CodeMirror.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];
+ 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);
}
}
- modeObj.name = spec.name;
- if (spec.helperType) modeObj.helperType = spec.helperType;
- if (spec.modeProps) for (var prop in spec.modeProps)
- modeObj[prop] = spec.modeProps[prop];
-
- return modeObj;
- };
-
- // Minimal default mode.
- CodeMirror.defineMode("null", function() {
- return {token: function(stream) {stream.skipToEnd();}};
- });
- CodeMirror.defineMIME("text/plain", "null");
-
- // This can be used to attach properties to mode objects from
- // outside the actual mode definition.
- var modeExtensions = CodeMirror.modeExtensions = {};
- CodeMirror.extendMode = function(mode, properties) {
- var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {});
- copyObj(properties, exts);
- };
-
- // EXTENSIONS
+ return new Selection(out, doc.sel.primIndex)
+ }
- CodeMirror.defineExtension = function(name, func) {
- CodeMirror.prototype[name] = func;
- };
- CodeMirror.defineDocExtension = function(name, func) {
- Doc.prototype[name] = func;
- };
- CodeMirror.defineOption = option;
+ // Used to get the editor into a consistent state again when options change.
- var initHooks = [];
- CodeMirror.defineInitHook = function(f) {initHooks.push(f);};
+ function loadMode(cm) {
+ cm.doc.mode = getMode(cm.options, cm.doc.modeOption);
+ resetModeState(cm);
+ }
- var helpers = CodeMirror.helpers = {};
- 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});
- };
+ 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); }
+ }
- // MODE STATE HANDLING
+ // DOCUMENT DATA STRUCTURE
- // Utility functions for working with state. Exported because nested
- // modes need to do this for their inner modes.
+ // 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)
+ }
- var copyState = CodeMirror.copyState = function(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;
+ // 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);
}
- return nstate;
- };
-
- var startState = CodeMirror.startState = function(mode, a1, a2) {
- return mode.startState ? mode.startState(a1, a2) : true;
- };
-
- // Given a mode and a state (for that mode), find the inner mode and
- // state at the position that the state refers to.
- CodeMirror.innerMode = function(mode, state) {
- while (mode.innerMode) {
- var info = mode.innerMode(state);
- if (!info || info.mode == mode) break;
- state = info.state;
- mode = info.mode;
+ function linesFor(start, end) {
+ var result = [];
+ for (var i = start; i < end; ++i)
+ { result.push(new Line(text[i], spansFor(i), estimateHeight)); }
+ return result
}
- return info || {mode: mode, state: state};
- };
- // STANDARD COMMANDS
+ 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;
- // Commands are parameter-less actions that can be performed on an
- // editor, mostly used for keybindings.
- var commands = CodeMirror.commands = {
- selectAll: function(cm) {cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.lastLine()), sel_dontScroll);},
- singleSelection: function(cm) {
- cm.setSelection(cm.getCursor("anchor"), cm.getCursor("head"), sel_dontScroll);
- },
- killLine: function(cm) {
- 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) {
- deleteNearSelection(cm, function(range) {
- return {from: Pos(range.from().line, 0),
- to: clipPos(cm.doc, Pos(range.to().line + 1, 0))};
- });
- },
- delLineLeft: function(cm) {
- deleteNearSelection(cm, function(range) {
- return {from: Pos(range.from().line, 0), to: range.from()};
- });
- },
- delWrappedLineLeft: function(cm) {
- 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) {
- 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) {cm.undo();},
- redo: function(cm) {cm.redo();},
- undoSelection: function(cm) {cm.undoSelection();},
- redoSelection: function(cm) {cm.redoSelection();},
- goDocStart: function(cm) {cm.extendSelection(Pos(cm.firstLine(), 0));},
- goDocEnd: function(cm) {cm.extendSelection(Pos(cm.lastLine()));},
- goLineStart: function(cm) {
- cm.extendSelectionsBy(function(range) { return lineStart(cm, range.head.line); },
- {origin: "+move", bias: 1});
- },
- goLineStartSmart: function(cm) {
- cm.extendSelectionsBy(function(range) {
- return lineStartSmart(cm, range.head);
- }, {origin: "+move", bias: 1});
- },
- goLineEnd: function(cm) {
- cm.extendSelectionsBy(function(range) { return lineEnd(cm, range.head.line); },
- {origin: "+move", bias: -1});
- },
- goLineRight: function(cm) {
- cm.extendSelectionsBy(function(range) {
- var top = cm.charCoords(range.head, "div").top + 5;
- return cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div");
- }, sel_move);
- },
- goLineLeft: function(cm) {
- cm.extendSelectionsBy(function(range) {
- var top = cm.charCoords(range.head, "div").top + 5;
- return cm.coordsChar({left: 0, top: top}, "div");
- }, sel_move);
- },
- goLineLeftSmart: function(cm) {
- cm.extendSelectionsBy(function(range) {
- var top = cm.charCoords(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) {cm.moveV(-1, "line");},
- goLineDown: function(cm) {cm.moveV(1, "line");},
- goPageUp: function(cm) {cm.moveV(-1, "page");},
- goPageDown: function(cm) {cm.moveV(1, "page");},
- goCharLeft: function(cm) {cm.moveH(-1, "char");},
- goCharRight: function(cm) {cm.moveH(1, "char");},
- goColumnLeft: function(cm) {cm.moveH(-1, "column");},
- goColumnRight: function(cm) {cm.moveH(1, "column");},
- goWordLeft: function(cm) {cm.moveH(-1, "word");},
- goGroupRight: function(cm) {cm.moveH(1, "group");},
- goGroupLeft: function(cm) {cm.moveH(-1, "group");},
- goWordRight: function(cm) {cm.moveH(1, "word");},
- delCharBefore: function(cm) {cm.deleteH(-1, "char");},
- delCharAfter: function(cm) {cm.deleteH(1, "char");},
- delWordBefore: function(cm) {cm.deleteH(-1, "word");},
- delWordAfter: function(cm) {cm.deleteH(1, "word");},
- delGroupBefore: function(cm) {cm.deleteH(-1, "group");},
- delGroupAfter: function(cm) {cm.deleteH(1, "group");},
- indentAuto: function(cm) {cm.indentSelection("smart");},
- indentMore: function(cm) {cm.indentSelection("add");},
- indentLess: function(cm) {cm.indentSelection("subtract");},
- insertTab: function(cm) {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(new Array(tabSize - col % tabSize + 1).join(" "));
+ // 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);
}
- cm.replaceSelections(spaces);
- },
- defaultTab: function(cm) {
- if (cm.somethingSelected()) cm.indentSelection("add");
- else cm.execCommand("insertTab");
- },
- transposeChars: function(cm) {
- runInOp(cm, function() {
- var ranges = cm.listSelections(), newSel = [];
- for (var i = 0; i < ranges.length; i++) {
- 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)
- cm.replaceRange(line.charAt(0) + "\n" + prev.charAt(prev.length - 1),
- Pos(cur.line - 1, prev.length - 1), Pos(cur.line, 1), "+transpose");
- }
- }
- newSel.push(new Range(cur, cur));
- }
- cm.setSelections(newSel);
- });
- },
- newlineAndIndent: function(cm) {
- runInOp(cm, function() {
- var len = cm.listSelections().length;
- for (var i = 0; i < len; i++) {
- var range = cm.listSelections()[i];
- cm.replaceRange("\n", range.anchor, range.head, "+input");
- cm.indentLine(range.from().line + 1, null, true);
- ensureCursorVisible(cm);
- }
- });
- },
- toggleOverwrite: function(cm) {cm.toggleOverwrite();}
- };
+ } 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);
+ }
- // STANDARD KEYMAPS
+ // 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);
+ }
- var keyMap = CodeMirror.keyMap = {};
+ // 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);
+ cm.options.direction = doc.direction;
+ if (!cm.options.lineWrapping) { findMaxLine(cm); }
+ cm.options.mode = doc.modeOption;
+ regChange(cm);
+ }
- 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"
- };
- 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;
+ function setDirectionClass(cm) {
+ (cm.doc.direction == "rtl" ? addClass : rmClass)(cm.display.lineDiv, "CodeMirror-rtl");
+ }
- // KEYMAP DISPATCH
+ function directionChanged(cm) {
+ runInOp(cm, function () {
+ setDirectionClass(cm);
+ regChange(cm);
+ });
+ }
- 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;
+ function History(prev) {
+ // 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 = prev ? prev.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 = prev ? prev.maxGeneration : 1;
}
- // 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.
- CodeMirror.normalizeKeyMap = function(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; }
+ // 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
+ }
- var keys = map(keyname.split(" "), normalizeKeyName);
- for (var i = 0; i < keys.length; i++) {
- var val, name;
- if (i == keys.length - 1) {
- name = keyname;
- 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];
+ // 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 }
}
- for (var prop in copy) keymap[prop] = copy[prop];
- return keymap;
- };
+ }
- var lookupKey = CodeMirror.lookupKey = function(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";
+ // 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)
+ }
+ }
- 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;
+ // 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) == "+" && hist.lastModTime > time - (doc.cm ? doc.cm.options.historyEventDelay : 500)) ||
+ 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;
- // Modifier key presses don't count as 'real' key presses for the
- // purpose of keymap fallthrough.
- var isModifierKey = CodeMirror.isModifierKey = function(value) {
- var name = typeof value == "string" ? value : keyNames[value.keyCode];
- return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod";
- };
+ if (!last) { signal(doc, "historyAdded"); }
+ }
- // Look up the name of a key as indicated by an event object.
- var keyName = CodeMirror.keyName = function(event, noShift) {
- if (presto && event.keyCode == 34 && event["char"]) return false;
- var base = keyNames[event.keyCode], name = base;
- if (name == null || event.altGraphKey) return false;
- 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;
- };
+ 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)
+ }
- function getKeyMap(val) {
- return typeof val == "string" ? keyMap[val] : val;
+ // 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); }
}
- // FROMTEXTAREA
+ function pushSelectionToHistory(sel, dest) {
+ var top = lst(dest);
+ if (!(top && top.ranges && top.equals(sel)))
+ { dest.push(sel); }
+ }
- CodeMirror.fromTextArea = function(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;
- }
+ // 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;
+ });
+ }
- function save() {textarea.value = cm.getValue();}
- 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) {}
- }
+ // 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
+ }
- 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;
- }
- };
- };
+ // 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
+ }
- textarea.style.display = "none";
- var cm = CodeMirror(function(node) {
- textarea.parentNode.insertBefore(node, textarea.nextSibling);
- }, options);
- return cm;
- };
+ // 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 }
- // STRING STREAM
+ 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
+ }
- // Fed to the mode parsers, provides helper functions to make
- // parsers more succinct.
+ // 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
+ }
- var StringStream = CodeMirror.StringStream = function(string, tabSize) {
- this.pos = this.start = 0;
- this.string = string;
- this.tabSize = tabSize || 8;
- this.lastColumnPos = this.lastColumnValue = 0;
- this.lineStart = 0;
- };
+ // The 'scroll' parameter given to many of these indicated whether
+ // the new cursor position should be scrolled into view after
+ // modifying the selection.
- StringStream.prototype = {
- eol: function() {return this.pos >= this.string.length;},
- sol: function() {return this.pos == this.lineStart;},
- peek: function() {return this.string.charAt(this.pos) || undefined;},
- next: function() {
- if (this.pos < this.string.length)
- return this.string.charAt(this.pos++);
- },
- eat: function(match) {
- var ch = this.string.charAt(this.pos);
- if (typeof match == "string") var ok = ch == match;
- else var ok = ch && (match.test ? match.test(ch) : match(ch));
- if (ok) {++this.pos; return ch;}
- },
- eatWhile: function(match) {
- var start = this.pos;
- while (this.eat(match)){}
- return this.pos > start;
- },
- eatSpace: function() {
- var start = this.pos;
- while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos;
- return this.pos > start;
- },
- skipToEnd: function() {this.pos = this.string.length;},
- skipTo: function(ch) {
- var found = this.string.indexOf(ch, this.pos);
- if (found > -1) {this.pos = found; return true;}
- },
- backUp: function(n) {this.pos -= n;},
- 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);
- },
- indentation: function() {
- return countColumn(this.string, null, this.tabSize) -
- (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0);
- },
- 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;
+ // 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;
}
- } 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;
}
- },
- current: function(){return this.string.slice(this.start, this.pos);},
- hideFirstChars: function(n, inner) {
- this.lineStart += n;
- try { return inner(); }
- finally { this.lineStart -= n; }
+ return new Range(anchor, head)
+ } else {
+ return new Range(other || head, head)
}
- };
+ }
- // TEXTMARKERS
+ // 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);
+ }
- // 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.
-
- var nextMarkerId = 0;
+ // 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(doc.cm, out, doc.sel.primIndex);
+ setSelection(doc, newSel, options);
+ }
- var TextMarker = CodeMirror.TextMarker = function(doc, type) {
- this.lines = [];
- this.type = type;
- this.doc = doc;
- this.id = ++nextMarkerId;
- };
- eventMixin(TextMarker);
+ // 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(doc.cm, ranges, doc.sel.primIndex), options);
+ }
- // Clear the marker.
- TextMarker.prototype.clear = function() {
- 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.lines[i];
- var span = getMarkedSpanFor(line.markedSpans, this);
- if (cm && !this.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.collapsed && !lineIsHidden(this.doc, line) && cm)
- updateLineHeight(line, textHeight(cm.display));
- }
- if (cm && this.collapsed && !cm.options.lineWrapping) for (var i = 0; i < this.lines.length; ++i) {
- var visual = visualLine(this.lines[i]), len = lineLength(visual);
- if (len > cm.display.maxLineLength) {
- cm.display.maxLine = visual;
- cm.display.maxLineLength = len;
- cm.display.maxLineChanged = true;
- }
- }
+ // Reset the selection to a single range.
+ function setSimpleSelection(doc, anchor, head, options) {
+ setSelection(doc, simpleSelection(anchor, head), options);
+ }
- 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);
- if (withOp) endOperation(cm);
- if (this.parent) this.parent.clear();
- };
+ // Give beforeSelectionChange handlers a change to influence a
+ // selection update.
+ function filterSelectionChange(doc, sel, options) {
+ var obj = {
+ ranges: sel.ranges,
+ update: function(ranges) {
+ this.ranges = [];
+ for (var i = 0; i < ranges.length; i++)
+ { this.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(doc.cm, obj.ranges, obj.ranges.length - 1) }
+ else { return sel }
+ }
- // 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) {
- if (side == null && this.type == "bookmark") side = 1;
- var from, to;
- for (var i = 0; i < this.lines.length; ++i) {
- var line = this.lines[i];
- var span = getMarkedSpanFor(line.markedSpans, this);
- 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;
- }
+ 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);
}
- return from && {from: from, to: to};
- };
+ }
- // Signals that the marker's widget changed, and surrounding layout
- // should be recomputed.
- TextMarker.prototype.changed = function() {
- 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);
- }
- });
- };
+ // 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);
+ }
- 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);
- }
- };
+ function setSelectionNoUndo(doc, sel, options) {
+ if (hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange"))
+ { sel = filterSelectionChange(doc, sel, options); }
- // 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 bias = options && options.bias ||
+ (cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1);
+ setSelectionInner(doc, skipAtomicInSelection(doc, sel, bias, true));
- // 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);
+ if (!(options && options.scroll === false) && doc.cm && doc.cm.getOption("readOnly") != "nocursor")
+ { ensureCursorVisible(doc.cm); }
+ }
- 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 = elt("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");
- sawCollapsedSpans = true;
- }
+ function setSelectionInner(doc, sel) {
+ if (sel.equals(doc.sel)) { return }
- if (marker.addToHistory)
- addChangeToHistory(doc, {from: from, to: to, origin: "markText"}, doc.sel, NaN);
+ doc.sel = sel;
- 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 (doc.cm) {
+ doc.cm.curOp.updateInput = 1;
+ doc.cm.curOp.selectionChanged = true;
+ signalCursorActivity(doc.cm);
+ }
+ signalLater(doc, "cursorActivity", doc);
+ }
- if (marker.clearOnEnter) on(marker, "beforeCursorEnter", function() { marker.clear(); });
+ // Verify that the selection does not partially select any atomic
+ // marked ranges.
+ function reCheckSelection(doc) {
+ setSelectionInner(doc, skipAtomicInSelection(doc, doc.sel, null, false));
+ }
- if (marker.readOnly) {
- sawReadOnlySpans = true;
- 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 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 marker;
+ return out ? normalizeSelection(doc.cm, out, sel.primIndex) : sel
}
- // SHARED TEXTMARKERS
-
- // A shared marker spans multiple linked documents. It is
- // implemented as a meta-marker-object controlling multiple normal
- // markers.
- var SharedTextMarker = CodeMirror.SharedTextMarker = function(markers, primary) {
- this.markers = markers;
- this.primary = primary;
- for (var i = 0; i < markers.length; ++i)
- markers[i].parent = this;
- };
- eventMixin(SharedTextMarker);
+ 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;
- SharedTextMarker.prototype.clear = function() {
- if (this.explicitlyCleared) return;
- this.explicitlyCleared = true;
- for (var i = 0; i < this.markers.length; ++i)
- this.markers[i].clear();
- signalLater(this, "clear");
- };
- SharedTextMarker.prototype.find = function(side, lineObj) {
- return this.primary.find(side, lineObj);
- };
+ // Determine if we should prevent the cursor being placed to the left/right of an atomic marker
+ // Historically this was determined using the inclusiveLeft/Right option, but the new way to control it
+ // is with selectLeft/Right
+ var preventCursorLeft = ("selectLeft" in m) ? !m.selectLeft : m.inclusiveLeft;
+ var preventCursorRight = ("selectRight" in m) ? !m.selectRight : m.inclusiveRight;
- 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);
- }
+ if ((sp.from == null || (preventCursorLeft ? sp.from <= pos.ch : sp.from < pos.ch)) &&
+ (sp.to == null || (preventCursorRight ? 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 ? preventCursorRight : preventCursorLeft)
+ { 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) }
+ }
- function findSharedMarkers(doc) {
- return doc.findMarks(Pos(doc.first, 0), doc.clipPos(Pos(doc.lastLine())),
- function(m) { return m.parent; });
+ var far = m.find(dir < 0 ? -1 : 1);
+ if (dir < 0 ? preventCursorLeft : preventCursorRight)
+ { far = movePos(doc, far, dir, far.line == pos.line ? line : null); }
+ return far ? skipAtomicInner(doc, far, pos, dir, mayClear) : null
+ }
+ } }
+ return pos
}
- 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;
- }
+ // 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 detachSharedMarkers(markers) {
- for (var i = 0; i < markers.length; i++) {
- var marker = markers[i], linked = [marker.primary.doc];;
- linkedDocs(marker.primary.doc, function(d) { 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);
- }
- }
- }
+ function selectAll(cm) {
+ cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.lastLine()), sel_dontScroll);
}
- // TEXTMARKER SPANS
+ // UPDATING
- function MarkedSpan(marker, from, to) {
- this.marker = marker;
- this.from = from; this.to = to;
- }
+ // 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); }
- // 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;
+ if (obj.canceled) {
+ if (doc.cm) { doc.cm.curOp.updateInput = 2; }
+ return null
}
- }
- // 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) {
- for (var r, 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);
+ return {from: obj.from, to: obj.to, text: obj.text, origin: obj.origin}
}
- // 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) {
- if (old) for (var i = 0, nw; 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));
- }
+ // 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 }
}
- return nw;
- }
- function markedSpansAfter(old, endCh, isInsert) {
- if (old) for (var i = 0, nw; 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));
- }
+
+ 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, origin: change.origin}); }
+ } else {
+ makeChangeInner(doc, change);
}
- 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;
+ 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);
- 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);
+ makeChangeSingleDoc(doc, change, selAfter, stretchSpansOverChange(doc, change));
+ var rebased = [];
- // 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;
- }
+ 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) {
+ var suppress = doc.cm && doc.cm.state.suppressEdits;
+ if (suppress && !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 (last) {
- // Fix up .from in last (or move them into first in case of sameLine)
- for (var i = 0; i < last.length; ++i) {
- var span = last[i];
- if (span.to != null) span.to += offset;
- if (span.from == null) {
- var found = getMarkedSpanFor(first, span.marker);
- if (!found) {
- span.from = offset;
- if (sameLine) (first || (first = [])).push(span);
- }
- } else {
- span.from += offset;
- if (sameLine) (first || (first = [])).push(span);
+ 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 if (suppress) {
+ source.push(event);
+ return
+ } else { break }
}
- // 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 = 0; i < first.length; ++i)
- if (first[i].to == null)
- (gapMarkers || (gapMarkers = [])).push(new MarkedSpan(first[i].marker, null, null));
- for (var i = 0; i < gap; ++i)
- newMarkers.push(gapMarkers);
- newMarkers.push(last);
+ // 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;
}
- 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);
+ // 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"); }
}
- if (!spans.length) return null;
- return spans;
}
- // 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;
+ // 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) }
- 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;
- }
+ if (change.to.line < doc.first) {
+ shiftDoc(doc, change.text.length - 1 - (change.to.line - change.from.line));
+ return
}
- return old;
- }
+ if (change.from.line > doc.lastLine()) { return }
- // 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 - 1;
- }
+ // 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};
}
- 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;
- }
+ change.removed = getBetween(doc, change.from, change.to);
- // 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; }
+ if (!selAfter) { selAfter = computeSelAfterChange(doc, change); }
+ if (doc.cm) { makeChangeSingleDocInEditor(doc.cm, change, spans); }
+ else { updateDoc(doc, change, spans); }
+ setSelectionNoUndo(doc, selAfter, sel_dontScroll);
- // 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;
+ if (doc.cantEdit && skipAtomic(doc, Pos(doc.firstLine(), 0)))
+ { doc.cantEdit = false; }
}
- // 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, 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); }
+ // 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;
- // 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 && (cmp(found.to, from) > 0 || (sp.marker.inclusiveRight && marker.inclusiveLeft)) ||
- fromCmp >= 0 && (cmp(found.from, to) < 0 || (sp.marker.inclusiveLeft && marker.inclusiveRight)))
- return true;
+ 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
+ }
+ });
}
- }
- // 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;
- }
+ if (doc.sel.contains(change.from, change.to) > -1)
+ { signalCursorActivity(cm); }
- // 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);
+ 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; }
}
- 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;
- }
+ retreatFrontier(doc, from.line);
+ startWorker(cm, 400);
- // 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, 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, 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;
+ 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;
}
- // LINE WIDGETS
-
- // Line widgets are block elements displayed above or below a line.
-
- var LineWidget = CodeMirror.LineWidget = function(cm, node, options) {
- if (options) for (var opt in options) if (options.hasOwnProperty(opt))
- this[opt] = options[opt];
- this.cm = cm;
- this.node = node;
- };
- eventMixin(LineWidget);
+ function replaceRange(doc, code, from, to, origin) {
+ var assign;
- function adjustScrollWhenAboveVisible(cm, line, diff) {
- if (heightAtLine(line) < ((cm.curOp && cm.curOp.scrollTop) || cm.doc.scrollTop))
- addToScrollPos(cm, null, diff);
+ if (!to) { to = from; }
+ if (cmp(to, from) < 0) { (assign = [to, from], from = assign[0], to = assign[1]); }
+ if (typeof code == "string") { code = doc.splitLines(code); }
+ makeChange(doc, {from: from, to: to, text: code, origin: origin});
}
- LineWidget.prototype.clear = function() {
- var cm = this.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) ws.splice(i--, 1);
- if (!ws.length) line.widgets = null;
- var height = widgetHeight(this);
- runInOp(cm, function() {
- adjustScrollWhenAboveVisible(cm, line, -height);
- regLineChange(cm, no, "widget");
- updateLineHeight(line, Math.max(0, line.height - height));
- });
- };
- LineWidget.prototype.changed = function() {
- var oldH = this.height, cm = this.cm, line = this.line;
- this.height = null;
- var diff = widgetHeight(this) - oldH;
- if (!diff) return;
- runInOp(cm, function() {
- cm.curOp.forceUpdate = true;
- adjustScrollWhenAboveVisible(cm, line, diff);
- updateLineHeight(line, line.height + diff);
- });
- };
+ // Rebasing/resetting history to deal with externally-sourced changes
- function widgetHeight(widget) {
- if (widget.height != null) return widget.height;
- if (!contains(document.body, widget.node)) {
- var parentStyle = "position: relative;";
- if (widget.coverGutter)
- parentStyle += "margin-left: -" + widget.cm.display.gutters.offsetWidth + "px;";
- if (widget.noHScroll)
- parentStyle += "width: " + widget.cm.display.wrapper.clientWidth + "px;";
- removeChildrenAndAdd(widget.cm.display.measure, elt("div", [widget.node], null, parentStyle));
+ function rebaseHistSelSingle(pos, from, to, diff) {
+ if (to < pos.line) {
+ pos.line += diff;
+ } else if (from < pos.line) {
+ pos.line = from;
+ pos.ch = 0;
}
- return widget.height = widget.node.offsetHeight;
}
- function addLineWidget(cm, handle, node, options) {
- var widget = new LineWidget(cm, node, options);
- if (widget.noHScroll) cm.display.alignWidgets = true;
- changeLine(cm.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 (!lineIsHidden(cm.doc, line)) {
- var aboveVisible = heightAtLine(line) < cm.doc.scrollTop;
- updateLineHeight(line, line.height + widgetHeight(widget));
- if (aboveVisible) addToScrollPos(cm, null, widget.height);
- cm.curOp.forceUpdate = true;
+ // 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
}
- return true;
- });
- return widget;
+ 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;
+ }
+ }
}
- // LINE DATA STRUCTURE
-
- // Line objects. These hold state related to a line, including
- // highlighting info (the styles array).
- var Line = CodeMirror.Line = function(text, markedSpans, estimateHeight) {
- this.text = text;
- attachMarkedSpans(this, markedSpans);
- this.height = estimateHeight ? estimateHeight(this) : 1;
- };
- eventMixin(Line);
- Line.prototype.lineNo = function() { return lineNo(this); };
-
- // 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);
+ 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);
}
- // Detach a line from the document tree and its markers.
- function cleanUpLine(line) {
- line.parent = null;
- detachMarkedSpans(line);
+ // 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
}
- 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];
+ // 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) {
+ this.lines = lines;
+ this.parent = null;
+ var height = 0;
+ for (var i = 0; i < lines.length; ++i) {
+ lines[i].parent = this;
+ height += lines[i].height;
}
- return type;
+ this.height = height;
}
- function callBlankLine(mode, state) {
- if (mode.blankLine) return mode.blankLine(state);
- if (!mode.innerMode) return;
- var inner = CodeMirror.innerMode(mode, state);
- if (inner.mode.blankLine) return inner.mode.blankLine(inner.state);
- }
+ LeafChunk.prototype = {
+ chunkSize: function() { return this.lines.length },
- function readToken(mode, stream, state, inner) {
- for (var i = 0; i < 10; i++) {
- if (inner) inner[0] = CodeMirror.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.");
- }
+ // Remove the n lines at offset 'at'.
+ removeInner: function(at, n) {
+ for (var i = at, e = at + n; i < e; ++i) {
+ var line = this.lines[i];
+ this.height -= line.height;
+ cleanUpLine(line);
+ signalLater(line, "delete");
+ }
+ this.lines.splice(at, n);
+ },
- // Utility for getTokenAt and getLineTokens
- function takeToken(cm, pos, precise, asArray) {
- function getObj(copy) {
- return {start: stream.start, end: stream.pos,
- string: stream.current(),
- type: style || null,
- state: copy ? copyState(doc.mode, state) : state};
+ // Helper used to collapse a small branch into a single leaf.
+ collapse: function(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(at, lines, height) {
+ 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; }
+ },
+
+ // Used to iterate over a part of the tree.
+ iterN: function(at, n, op) {
+ for (var e = at + n; at < e; ++at)
+ { if (op(this.lines[at])) { return true } }
}
+ };
- var doc = cm.doc, mode = doc.mode, style;
- pos = clipPos(doc, pos);
- var line = getLine(doc, pos.line), state = getStateBefore(cm, pos.line, precise);
- var stream = new StringStream(line.text, cm.options.tabSize), tokens;
- if (asArray) tokens = [];
- while ((asArray || stream.pos < pos.ch) && !stream.eol()) {
- stream.start = stream.pos;
- style = readToken(mode, stream, state);
- if (asArray) tokens.push(getObj(true));
+ function BranchChunk(children) {
+ 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;
}
- return asArray ? tokens : getObj();
+ this.size = size;
+ this.height = height;
+ this.parent = null;
}
- // Run the given mode's parser over a line, calling f for each token.
- function runMode(cm, text, mode, state, 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), style;
- var inner = cm.options.addModeClass && [null];
- if (text == "") extractLineClasses(callBlankLine(mode, state), lineClasses);
- while (!stream.eol()) {
- if (stream.pos > cm.options.maxHighlightLength) {
- flattenSpans = false;
- if (forceToEnd) processLine(cm, text, state, stream.pos);
- stream.pos = text.length;
- style = null;
- } else {
- style = extractLineClasses(readToken(mode, stream, 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 + 50000);
- f(curStart, curStyle);
+ BranchChunk.prototype = {
+ chunkSize: function() { return this.size },
+
+ removeInner: function(at, n) {
+ this.size -= n;
+ for (var i = 0; i < this.children.length; ++i) {
+ var child = this.children[i], sz = child.chunkSize();
+ if (at < sz) {
+ var rm = Math.min(n, sz - at), oldHeight = child.height;
+ child.removeInner(at, rm);
+ this.height -= oldHeight - child.height;
+ if (sz == rm) { this.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(lines) {
+ for (var i = 0; i < this.children.length; ++i) { this.children[i].collapse(lines); }
+ },
+
+ insertInner: function(at, lines, height) {
+ this.size += lines.length;
+ this.height += height;
+ for (var i = 0; i < this.children.length; ++i) {
+ var child = this.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.children.splice(++i, 0, leaf);
+ leaf.parent = this;
+ }
+ child.lines = child.lines.slice(0, remaining);
+ this.maybeSpill();
+ }
+ break
+ }
+ at -= sz;
+ }
+ },
+
+ // When a node has grown, check whether it should be split.
+ maybeSpill: function() {
+ 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(at, n, op) {
+ for (var i = 0; i < this.children.length; ++i) {
+ var child = this.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) {
+ if (options) { for (var opt in options) { if (options.hasOwnProperty(opt))
+ { this[opt] = options[opt]; } } }
+ this.doc = doc;
+ this.node = node;
+ };
+
+ LineWidget.prototype.clear = function () {
+ 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) { 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 }
+ if (!lineIsHidden(this.doc, line)) { 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, 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
+ });
+ if (cm) { 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 () {
+ 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.lines[i];
+ var span = getMarkedSpanFor(line.markedSpans, this);
+ if (cm && !this.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.collapsed && !lineIsHidden(this.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.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) {
+ if (side == null && this.type == "bookmark") { side = 1; }
+ var from, to;
+ for (var i = 0; i < this.lines.length; ++i) {
+ var line = this.lines[i];
+ var span = getMarkedSpanFor(line.markedSpans, this);
+ 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), doc.cm && doc.cm.curOp);
+ ++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.startStyle || marker.endStyle || marker.css ||
+ marker.attributes || marker.title)
+ { 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) {
+ this.markers = markers;
+ this.primary = primary;
+ for (var i = 0; i < markers.length; ++i)
+ { markers[i].parent = this; }
+ };
+
+ SharedTextMarker.prototype.clear = function () {
+ if (this.explicitlyCleared) { return }
+ this.explicitlyCleared = true;
+ for (var i = 0; i < this.markers.length; ++i)
+ { this.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 }
+ if (lineSep === '') { return lines.join('') }
+ 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) {
+ if (!ranges.length) { return }
+ var out = [];
+ for (var i = 0; i < ranges.length; i++)
+ { out[i] = new Range(clipPos(this, ranges[i].anchor),
+ clipPos(this, ranges[i].head || ranges[i].anchor)); }
+ if (primary == null) { primary = Math.min(ranges.length - 1, this.sel.primIndex); }
+ setSelection(this, normalizeSelection(this.cm, 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(this.cm, ranges, ranges.length - 1), options);
+ }),
+
+ getSelection: function(lineSep) {
+ var ranges = this.sel.ranges, lines;
+ for (var i = 0; i < ranges.length; i++) {
+ var sel = getBetween(this, 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 parts = [], ranges = this.sel.ranges;
+ for (var i = 0; i < ranges.length; i++) {
+ var sel = getBetween(this, ranges[i].from(), ranges[i].to());
+ if (lineSep !== false) { sel = sel.join(lineSep || this.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 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.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, 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() {
+ var this$1 = this;
+
+ this.history = new History(this.history);
+ linkedDocs(this, function (doc) { return doc.history = this$1.history; }, true);
+ },
+
+ 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);
+ 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
+ });
}
- curStyle = style;
+ });
+ }),
+
+ 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 }
}
- stream.start = stream.pos;
- }
- while (curStart < stream.pos) {
- // Webkit seems to refuse to render text nodes longer than 57444 characters
- var pos = Math.min(stream.pos, curStart + 50000);
- f(pos, curStyle);
- curStart = pos;
- }
- }
+ return {line: n, handle: line, text: line.text, gutterMarkers: line.gutterMarkers,
+ textClass: line.textClass, bgClass: line.bgClass, wrapClass: line.wrapClass,
+ widgets: line.widgets}
+ },
- // 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, state, 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, state, function(end, style) {
- st.push(end, style);
- }, lineClasses, forceToEnd);
+ 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
+ })
+ }),
- // Run overlays, adjust style array.
- for (var o = 0; o < cm.state.overlays.length; ++o) {
- var overlay = cm.state.overlays[o], i = 1, at = 0;
- runMode(cm, line.text, overlay.mode, true, 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);
+ 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) {
+ if (other instanceof CodeMirror) { other = other.doc; }
+ if (this.linked) { for (var i = 0; i < this.linked.length; ++i) {
+ var link = this.linked[i];
+ if (link.doc != other) { continue }
+ this.linked.splice(i, 1);
+ other.unlinkDoc(this);
+ detachSharedMarkers(findSharedMarkers(this));
+ 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 markAsReadAndPasteIfAllFilesAreRead = function () {
+ if (++read == n) {
+ operation(cm, function () {
+ pos = clipPos(cm.doc, pos);
+ var change = {from: pos, to: pos,
+ text: cm.doc.splitLines(
+ text.filter(function (t) { return t != null; }).join(cm.doc.lineSeparator())),
+ origin: "paste"};
+ makeChange(cm.doc, change);
+ setSelectionReplaceHistory(cm.doc, simpleSelection(clipPos(cm.doc, pos), clipPos(cm.doc, changeEnd(change))));
+ })();
}
- if (!style) return;
- if (overlay.opaque) {
- st.splice(start, i - start, end, "cm-overlay " + style);
- i = start + 2;
- } else {
- for (; start < i; start += 2) {
- var cur = st[start+1];
- st[start+1] = (cur ? cur + " " : "") + "cm-overlay " + style;
+ };
+ var readTextFromFile = function (file, i) {
+ if (cm.options.allowDropFileTypes &&
+ indexOf(cm.options.allowDropFileTypes, file.type) == -1) {
+ markAsReadAndPasteIfAllFilesAreRead();
+ return
+ }
+ var reader = new FileReader;
+ reader.onerror = function () { return markAsReadAndPasteIfAllFilesAreRead(); };
+ reader.onload = function () {
+ var content = reader.result;
+ if (/[\x00-\x08\x0e-\x1f]{2}/.test(content)) {
+ markAsReadAndPasteIfAllFilesAreRead();
+ return
}
+ text[i] = content;
+ markAsReadAndPasteIfAllFilesAreRead();
+ };
+ reader.readAsText(file);
+ };
+ for (var i = 0; i < files.length; i++) { readTextFromFile(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();
}
- }, lineClasses);
+ }
+ catch(e$1){}
+ }
+ }
+
+ 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); }
}
-
- 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 result = highlightLine(cm, line, line.stateAfter = getStateBefore(cm, lineNo(line)));
- line.styles = result.styles;
- if (result.classes) line.styleClasses = result.classes;
- else if (line.styleClasses) line.styleClasses = null;
- if (updateFrontier === cm.doc.frontier) cm.doc.frontier++;
+ 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);
}
- return line.styles;
+ removeChildrenAndAdd(cm.display.dragCursor, frag);
}
- // 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, state, startAt) {
- var mode = cm.doc.mode;
- var stream = new StringStream(text, cm.options.tabSize);
- stream.start = stream.pos = startAt || 0;
- if (text == "") callBlankLine(mode, state);
- while (!stream.eol() && stream.pos <= cm.options.maxHighlightLength) {
- readToken(mode, stream, state);
- stream.start = stream.pos;
+ function clearDragCursor(cm) {
+ if (cm.display.dragCursor) {
+ cm.display.lineSpace.removeChild(cm.display.dragCursor);
+ cm.display.dragCursor = null;
}
}
- // 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 = {}, 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-$&"));
+ // 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"), editors = [];
+ for (var i = 0; i < byClass.length; i++) {
+ var cm = byClass[i].CodeMirror;
+ if (cm) { editors.push(cm); }
+ }
+ if (editors.length) { editors[0].operation(function () {
+ for (var i = 0; i < editors.length; i++) { f(editors[i]); }
+ }); }
}
- // 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 = elt("span", null, null, webkit ? "padding-right: .1px" : null);
- var builder = {pre: elt("pre", [content]), content: content, col: 0, pos: 0, cm: cm};
- lineView.measure = {};
+ 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;
+ // Might be a text scaling operation, clear size caches.
+ d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null;
+ d.scrollbarsClipped = false;
+ cm.setSize();
+ }
- // 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;
- builder.pos = 0;
- builder.addToken = buildToken;
- // Optionally wire in some hacks into the token-rendering
- // algorithm, to deal with browser quirks.
- if ((ie || webkit) && cm.getOption("lineWrapping"))
- builder.addToken = buildTokenSplitSpaces(builder.addToken);
- if (hasBadBidiRects(cm.display.measure) && (order = getOrder(line)))
- 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 || "");
- }
+ var keyNames = {
+ 3: "Pause", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt",
+ 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End",
+ 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert",
+ 46: "Delete", 59: ";", 61: "=", 91: "Mod", 92: "Mod", 93: "Mod",
+ 106: "*", 107: "=", 109: "-", 110: ".", 111: "/", 145: "ScrollLock",
+ 173: "-", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\",
+ 221: "]", 222: "'", 224: "Mod", 63232: "Up", 63233: "Down", 63234: "Left", 63235: "Right", 63272: "Delete",
+ 63273: "Home", 63275: "End", 63276: "PageUp", 63277: "PageDown", 63302: "Insert"
+ };
- // 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)));
+ // 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; }
- // 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({});
- }
- }
+ var keyMap = {};
- // See issue #2901
- if (webkit && /\bcm-tab\b/.test(builder.content.lastChild.className))
- builder.content.className = "cm-tab-wrap-hack";
+ 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",
+ "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd", "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp",
+ "Ctrl-D": "delCharAfter", "Ctrl-H": "delCharBefore", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine",
+ "Ctrl-T": "transposeChars", "Ctrl-O": "openLine"
+ };
+ keyMap.macDefault = {
+ "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo",
+ "Cmd-Home": "goDocStart", "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goGroupLeft",
+ "Alt-Right": "goGroupRight", "Cmd-Left": "goLineLeft", "Cmd-Right": "goLineRight", "Alt-Backspace": "delGroupBefore",
+ "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S": "save", "Cmd-F": "find",
+ "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll",
+ "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delWrappedLineLeft", "Cmd-Delete": "delWrappedLineRight",
+ "Cmd-U": "undoSelection", "Shift-Cmd-U": "redoSelection", "Ctrl-Up": "goDocStart", "Ctrl-Down": "goDocEnd",
+ "fallthrough": ["basic", "emacsy"]
+ };
+ keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault;
- signal(cm, "renderLine", cm, lineView.line, builder.pre);
- if (builder.pre.className)
- builder.textClass = joinClasses(builder.pre.className, builder.textClass || "");
+ // KEYMAP DISPATCH
- return builder;
+ 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
}
- 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;
- }
+ // 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 }
- // 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 special = builder.cm.options.specialChars, mustWrap = false;
- if (!special.test(text)) {
- builder.col += text.length;
- var content = document.createTextNode(text);
- builder.map.push(builder.pos, builder.pos + text.length, content);
- if (ie && ie_version < 9) mustWrap = true;
- builder.pos += text.length;
- } else {
- var content = document.createDocumentFragment(), 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(text.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;
- if (m[0] == "\t") {
- var tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder.col % tabSize;
- var txt = content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab"));
- txt.setAttribute("role", "presentation");
- txt.setAttribute("cm-text", "\t");
- builder.col += tabWidth;
+ 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 {
- var txt = builder.cm.options.specialCharPlaceholder(m[0]);
- txt.setAttribute("cm-text", m[0]);
- if (ie && ie_version < 9) content.appendChild(elt("span", [txt]));
- else content.appendChild(txt);
- builder.col += 1;
+ name = keys.slice(0, i + 1).join(" ");
+ val = "...";
}
- builder.map.push(builder.pos, builder.pos + 1, txt);
- builder.pos++;
+ var prev = copy[name];
+ if (!prev) { copy[name] = val; }
+ else if (prev != val) { throw new Error("Inconsistent bindings for " + name) }
}
- }
- 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);
+ delete keymap[keyname];
+ } }
+ for (var prop in copy) { keymap[prop] = copy[prop]; }
+ return keymap
}
- function buildTokenSplitSpaces(inner) {
- function split(old) {
- var out = " ";
- for (var i = 0; i < old.length - 2; ++i) out += i % 2 ? " " : "\u00a0";
- out += " ";
- return out;
+ 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 }
+ }
}
- return function(builder, text, style, startStyle, endStyle, title) {
- inner(builder, text.replace(/ {3,}/g, split), style, startStyle, endStyle, title);
- };
}
- // 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) {
- 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
- for (var i = 0; i < order.length; i++) {
- var part = order[i];
- if (part.to > start && part.from <= start) break;
- }
- if (part.to >= end) return inner(builder, text, style, startStyle, endStyle, title);
- inner(builder, text.slice(0, part.to - start), style, startStyle, null, title);
- startStyle = null;
- text = text.slice(part.to - start);
- start = part.to;
- }
- };
+ // 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 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;
+ 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 != "Mod") { name = "Cmd-" + name; }
+ if (!noShift && event.shiftKey && base != "Shift") { name = "Shift-" + name; }
+ return name
}
- // 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; i < styles.length; i+=2)
- builder.addToken(builder, allText.slice(at, at = styles[i]), interpretTokenStyle(styles[i+1], builder.cm.options));
- return;
- }
+ // 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 }
+ // Ctrl-ScrollLock has keyCode 3, same as Ctrl-Pause,
+ // so we'll use event.code when available (Chrome 48+, FF 38+, Safari 10.1+)
+ if (event.keyCode == 3 && event.code) { name = event.code; }
+ return addModifierNames(name, event, noShift)
+ }
- 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 = [];
- for (var j = 0; j < spans.length; ++j) {
- var sp = spans[j], m = sp.marker;
- if (sp.from <= pos && (sp.to == null || sp.to > pos)) {
- if (sp.to != null && nextChange > sp.to) { nextChange = sp.to; spanEndStyle = ""; }
- if (m.className) spanStyle += " " + m.className;
- if (m.css) css = m.css;
- if (m.startStyle && sp.from == pos) spanStartStyle += " " + m.startStyle;
- if (m.endStyle && sp.to == nextChange) spanEndStyle += " " + m.endStyle;
- 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 (m.type == "bookmark" && sp.from == pos && m.widgetNode) foundBookmarks.push(m);
- }
- 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 && foundBookmarks.length) for (var j = 0; j < foundBookmarks.length; ++j)
- buildCollapsedSpan(builder, 0, foundBookmarks[j]);
- }
- if (pos >= len) break;
+ function getKeyMap(val) {
+ return typeof val == "string" ? keyMap[val] : val
+ }
- 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 = "";
+ // 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
}
- text = allText.slice(at, at = styles[i++]);
- style = interpretTokenStyle(styles[i++], builder.cm.options);
}
+ 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);
+ });
}
- // 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);
- }
+ 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) {
+ if (cm.doc.direction == "rtl") { dir = -dir; }
+ 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 || cm.doc.direction == "rtl") {
+ 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);
- // 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) {
- for (var i = start, result = []; i < end; ++i)
- result.push(new Line(text[i], spansFor(i), estimateHeight));
- return result;
+ 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)
+ }
}
- 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;
+ // Case 3: Could not move within this bidi part in this visual line, so leave
+ // the current bidi part
- // 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 = linesFor(1, text.length - 1);
- added.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);
- }
- } 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 = linesFor(1, text.length - 1);
- if (nlines > 1) doc.remove(from.line + 1, nlines - 1);
- doc.insert(from.line + 1, added);
- }
+ 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"); };
- signalLater(doc, "change", doc, change);
- }
+ 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) }
+ }
+ };
- // 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
+ // Case 3a: Look for other bidi parts on the same visual line
+ var res = searchInVisualLine(partPos + dir, dir, wrappedLineExtent);
+ if (res) { return res }
- function LeafChunk(lines) {
- this.lines = lines;
- this.parent = null;
- for (var i = 0, height = 0; i < lines.length; ++i) {
- lines[i].parent = this;
- height += lines[i].height;
+ // 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 }
}
- this.height = height;
+
+ // Case 4: Nowhere to move
+ return null
}
- LeafChunk.prototype = {
- chunkSize: function() { return this.lines.length; },
- // Remove the n lines at offset 'at'.
- removeInner: function(at, n) {
- for (var i = at, e = at + n; i < e; ++i) {
- var line = this.lines[i];
- this.height -= line.height;
- cleanUpLine(line);
- signalLater(line, "delete");
+ // 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, "codepoint"); },
+ 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));
}
- this.lines.splice(at, n);
- },
- // Helper used to collapse a small branch into a single leaf.
- collapse: function(lines) {
- lines.push.apply(lines, this.lines);
+ cm.replaceSelections(spaces);
},
- // Insert the given array of lines at offset 'at', count them as
- // having the given height.
- insertInner: function(at, lines, height) {
- 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;
+ defaultTab: function (cm) {
+ if (cm.somethingSelected()) { cm.indentSelection("add"); }
+ else { cm.execCommand("insertTab"); }
},
- // Used to iterate over a part of the tree.
- iterN: function(at, n, op) {
- for (var e = at + n; at < e; ++at)
- if (op(this.lines[at])) return true;
- }
+ // 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 BranchChunk(children) {
- 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;
+
+ 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(start.ch, line.text.search(/\S/));
+ var inWS = pos.line == start.line && pos.ch <= firstNonWS && pos.ch;
+ return Pos(start.line, inWS ? 0 : firstNonWS, start.sticky)
}
- this.size = size;
- this.height = height;
- this.parent = null;
+ return start
}
- BranchChunk.prototype = {
- chunkSize: function() { return this.size; },
- removeInner: function(at, n) {
- this.size -= n;
- for (var i = 0; i < this.children.length; ++i) {
- var child = this.children[i], sz = child.chunkSize();
- if (at < sz) {
- var rm = Math.min(n, sz - at), oldHeight = child.height;
- child.removeInner(at, rm);
- this.height -= oldHeight - child.height;
- if (sz == rm) { this.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(lines) {
- for (var i = 0; i < this.children.length; ++i) this.children[i].collapse(lines);
- },
- insertInner: function(at, lines, height) {
- this.size += lines.length;
- this.height += height;
- for (var i = 0; i < this.children.length; ++i) {
- var child = this.children[i], sz = child.chunkSize();
- if (at <= sz) {
- child.insertInner(at, lines, height);
- if (child.lines && child.lines.length > 50) {
- while (child.lines.length > 50) {
- var spilled = child.lines.splice(child.lines.length - 25, 25);
- var newleaf = new LeafChunk(spilled);
- child.height -= newleaf.height;
- this.children.splice(i + 1, 0, newleaf);
- newleaf.parent = this;
- }
- this.maybeSpill();
+ // 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" }
+ if (/\'$/.test(name))
+ { cm.state.keySeq = null; }
+ else
+ { stopSeq.set(50, function () {
+ if (cm.state.keySeq == seq) {
+ cm.state.keySeq = null;
+ cm.display.input.reset();
}
- break;
- }
- at -= sz;
- }
- },
- // When a node has grown, check whether it should be split.
- maybeSpill: function() {
- 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(at, n, op) {
- for (var i = 0; i < this.children.length; ++i) {
- var child = this.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;
+ }); }
+ if (dispatchKeyInner(cm, seq + " " + name, e, handle)) { return true }
+ }
+ return dispatchKeyInner(cm, name, e, handle)
+ }
+
+ function dispatchKeyInner(cm, name, e, handle) {
+ 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);
+ }
+
+ 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;
+ if (e.target && e.target != cm.display.input.getField()) { return }
+ 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"); }
+ }
+ if (gecko && !mac && !handled && code == 46 && e.shiftKey && !e.ctrlKey && document.execCommand)
+ { document.execCommand("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);
+ }
- var nextDocId = 0;
- var Doc = CodeMirror.Doc = function(text, mode, firstLine) {
- if (!(this instanceof Doc)) return new Doc(text, mode, firstLine);
- if (firstLine == null) firstLine = 0;
+ function onKeyUp(e) {
+ if (e.keyCode == 16) { this.doc.sel.shift = false; }
+ signalDOMEvent(this, e);
+ }
- BranchChunk.call(this, [new LeafChunk([new Line("", null)])]);
- this.first = firstLine;
- this.scrollTop = this.scrollLeft = 0;
- this.cantEdit = false;
- this.cleanGeneration = 1;
- this.frontier = firstLine;
- var start = Pos(firstLine, 0);
- this.sel = simpleSelection(start);
- this.history = new History(null);
- this.id = ++nextDocId;
- this.modeOption = mode;
+ function onKeyPress(e) {
+ var cm = this;
+ if (e.target && e.target != cm.display.input.getField()) { return }
+ 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);
+ }
- if (typeof text == "string") text = splitLines(text);
- updateDoc(this, {from: start, to: start, text: text});
- setSelection(this, simpleSelection(start), sel_dontScroll);
+ var DOUBLECLICK_DELAY = 400;
+
+ var PastClick = function(time, pos, button) {
+ this.time = time;
+ this.pos = pos;
+ this.button = button;
};
- 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);
- },
+ PastClick.prototype.compare = function (time, pos, button) {
+ return this.time + DOUBLECLICK_DELAY > time &&
+ cmp(pos, this.pos) == 0 && button == this.button
+ };
- // 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); },
+ var lastClick, 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"
+ }
+ }
- // From here, the methods are part of the public interface. Most
- // are also available from CodeMirror (editor) instances.
+ // 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;
- getValue: function(lineSep) {
- var lines = getLines(this, this.first, this.first + this.size);
- if (lineSep === false) return lines;
- return lines.join(lineSep || "\n");
- },
- 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: splitLines(code), origin: "setValue", full: true}, true);
- setSelection(this, simpleSelection(top));
- }),
- 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 || "\n");
- },
+ 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();
- getLine: function(line) {var l = this.getLineHandle(line); return l && l.text;},
+ // #3261: make sure, that we're not starting a second selection
+ if (button == 1 && cm.state.selectingText)
+ { cm.state.selectingText(e); }
- getLineHandle: function(line) {if (isLine(this, line)) return getLine(this, line);},
- getLineNumber: function(line) {return lineNo(line);},
+ if (pos && handleMappedButton(cm, button, pos, repeat, e)) { return }
- getLineHandleVisualStart: function(line) {
- if (typeof line == "number") line = getLine(this, line);
- return visualLine(line);
- },
+ 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) { cm.display.input.onContextMenu(e); }
+ else { delayBlurEvent(cm); }
+ }
+ }
- lineCount: function() {return this.size;},
- firstLine: function() {return this.first;},
- lastLine: function() {return this.first + this.size - 1;},
+ 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;
- clipPos: function(pos) {return clipPos(this, pos);},
+ 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
+ })
+ }
- 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();},
+ 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
+ }
- 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) {
- extendSelections(this, map(this.sel.ranges, f), options);
- }),
- setSelections: docMethodOp(function(ranges, primary, options) {
- if (!ranges.length) return;
- for (var i = 0, out = []; i < ranges.length; i++)
- out[i] = new Range(clipPos(this, ranges[i].anchor),
- clipPos(this, 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);
- }),
+ function leftButtonDown(cm, pos, repeat, event) {
+ if (ie) { setTimeout(bind(ensureFocus, cm), 0); }
+ else { cm.curOp.focus = activeElt(); }
- getSelection: function(lineSep) {
- var ranges = this.sel.ranges, lines;
- for (var i = 0; i < ranges.length; i++) {
- var sel = getBetween(this, ranges[i].from(), ranges[i].to());
- lines = lines ? lines.concat(sel) : sel;
- }
- if (lineSep === false) return lines;
- else return lines.join(lineSep || "\n");
- },
- getSelections: function(lineSep) {
- var parts = [], ranges = this.sel.ranges;
- for (var i = 0; i < ranges.length; i++) {
- var sel = getBetween(this, ranges[i].from(), ranges[i].to());
- if (lineSep !== false) sel = sel.join(lineSep || "\n");
- parts[i] = sel;
+ 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;
+ if (cm.state.delayingBlurEvent) {
+ if (cm.hasFocus()) { cm.state.delayingBlurEvent = false; }
+ else { delayBlurEvent(cm); }
}
- 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 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: splitLines(code[i]), origin: origin};
+ off(display.wrapper.ownerDocument, "mouseup", dragEnd);
+ off(display.wrapper.ownerDocument, "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 && !safari) || ie && ie_version == 9)
+ { setTimeout(function () {display.wrapper.ownerDocument.body.focus({preventScroll: true}); display.input.focus();}, 20); }
+ else
+ { display.input.focus(); }
}
- var newSel = collapse && collapse != "end" && computeReplacedSel(this, changes, collapse);
- for (var i = changes.length - 1; i >= 0; i--)
- makeChange(this, changes[i]);
- 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);}),
+ });
+ 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;
+ on(display.wrapper.ownerDocument, "mouseup", dragEnd);
+ on(display.wrapper.ownerDocument, "mousemove", mouseMove);
+ on(display.scroller, "dragstart", dragStart);
+ on(display.scroller, "drop", dragEnd);
- setExtending: function(val) {this.extend = val;},
- getExtending: function() {return this.extend;},
+ cm.state.delayingBlurEvent = true;
+ setTimeout(function () { return display.input.focus(); }, 20);
+ // IE's approach to draggable
+ if (display.scroller.dragDrop) { display.scroller.dragDrop(); }
+ }
- 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 = 0; i < hist.undone.length; i++) if (!hist.undone[i].ranges) ++undone;
- return {undo: done, redo: undone};
- },
- clearHistory: function() {this.history = new History(this.history.maxGeneration);},
+ 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)
+ }
- 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);
- },
+ // Normal selection, as opposed to text dragging.
+ function leftButtonSelect(cm, event, start, behavior) {
+ if (ie) { delayBlurEvent(cm); }
+ var display = cm.display, doc = cm.doc;
+ e_preventDefault(event);
- 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);
- },
+ 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;
+ }
- 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;
- });
- }),
+ 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(cm, ranges.concat([ourRange]), ourIndex),
+ {scroll: false, origin: "*mouse"});
+ } else if (ranges.length > 1 && ranges[ourIndex].empty() && behavior.unit == "char" && !behavior.extend) {
+ setSelection(doc, normalizeSelection(cm, 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;
- markText: function(from, to, options) {
- return markText(this, clipPos(this, from), clipPos(this, to), options, "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};
- 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 (!(lineNo == from.line && from.ch > span.to ||
- span.from == null && lineNo != from.line||
- lineNo == to.line && span.from > to.ch) &&
- (!filter || filter(span.marker)))
- found.push(span.marker.parent || span.marker);
+ 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)))); }
}
- ++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;
- },
+ if (!ranges.length) { ranges.push(new Range(start, start)); }
+ setSelection(doc, normalizeSelection(cm, 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] = bidiSimplify(cm, new Range(clipPos(doc, anchor), head));
+ setSelection(doc, normalizeSelection(cm, ranges$1, ourIndex), sel_mouse);
+ }
+ }
- posFromIndex: function(off) {
- var ch, lineNo = this.first;
- this.iter(function(line) {
- var sz = line.text.length + 1;
- 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;
- this.iter(this.first, coords.line, function (line) {
- index += line.text.length + 1;
- });
- return index;
- },
+ 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;
- copy: function(copyHistory) {
- var doc = new Doc(getLines(this, this.first, this.first + this.size), this.modeOption, this.first);
- 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());
+ 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); }
}
- 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);
- 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) {
- if (other instanceof CodeMirror) other = other.doc;
- if (this.linked) for (var i = 0; i < this.linked.length; ++i) {
- var link = this.linked[i];
- if (link.doc != other) continue;
- this.linked.splice(i, 1);
- other.unlinkDoc(this);
- detachSharedMarkers(findSharedMarkers(this));
- break;
- }
- // If the histories were shared, split them again
- if (other.history == this.history) {
- var splitIds = [other.id];
- linkedDocs(other, function(doc) {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);
+ function done(e) {
+ cm.state.selectingText = false;
+ counter = Infinity;
+ // If e is null or undefined we interpret this as someone trying
+ // to explicitly cancel the selection rather than the user
+ // letting go of the mouse button.
+ if (e) {
+ e_preventDefault(e);
+ display.input.focus();
}
- },
- iterLinkedDocs: function(f) {linkedDocs(this, f);},
+ off(display.wrapper.ownerDocument, "mousemove", move);
+ off(display.wrapper.ownerDocument, "mouseup", up);
+ doc.history.lastSelOrigin = null;
+ }
- getMode: function() {return this.mode;},
- getEditor: function() {return this.cm;}
- });
+ var move = operation(cm, function (e) {
+ if (e.buttons === 0 || !e_button(e)) { done(e); }
+ else { extend(e); }
+ });
+ var up = operation(cm, done);
+ cm.state.selectingText = up;
+ on(display.wrapper.ownerDocument, "mousemove", move);
+ on(display.wrapper.ownerDocument, "mouseup", up);
+ }
+
+ // Used when mouse-selecting to adjust the anchor to the proper side
+ // of a bidi jump depending on the visual position of the head.
+ function bidiSimplify(cm, range) {
+ var anchor = range.anchor;
+ var head = range.head;
+ var anchorLine = getLine(cm.doc, anchor.line);
+ if (cmp(anchor, head) == 0 && anchor.sticky == head.sticky) { return range }
+ var order = getOrder(anchorLine);
+ if (!order) { return range }
+ var index = getBidiPartAt(order, anchor.ch, anchor.sticky), part = order[index];
+ if (part.from != anchor.ch && part.to != anchor.ch) { return range }
+ var boundary = index + ((part.from == anchor.ch) == (part.level != 1) ? 0 : 1);
+ if (boundary == 0 || boundary == order.length) { return range }
+
+ // Compute the relative visual position of the head compared to the
+ // anchor (<0 is to the left, >0 to the right)
+ var leftSide;
+ if (head.line != anchor.line) {
+ leftSide = (head.line - anchor.line) * (cm.doc.direction == "ltr" ? 1 : -1) > 0;
+ } else {
+ var headIndex = getBidiPartAt(order, head.ch, head.sticky);
+ var dir = headIndex - index || (head.ch - anchor.ch) * (part.level == 1 ? -1 : 1);
+ if (headIndex == boundary - 1 || headIndex == boundary)
+ { leftSide = dir < 0; }
+ else
+ { leftSide = dir > 0; }
+ }
- // Public alias.
- Doc.prototype.eachLine = Doc.prototype.iter;
+ var usePart = order[boundary + (leftSide ? -1 : 0)];
+ var from = leftSide == (usePart.level == 1);
+ var ch = from ? usePart.from : usePart.to, sticky = from ? "after" : "before";
+ return anchor.ch == ch && anchor.sticky == sticky ? range : new Range(new Pos(anchor.line, ch, sticky), head)
+ }
- // Set up methods on CodeMirror's prototype to redirect to the editor's document.
- var dontDelegate = "iter insert remove copy getEditor".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);
+ // 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;
+ if (e.touches) {
+ mX = e.touches[0].clientX;
+ mY = e.touches[0].clientY;
+ } else {
+ try { mX = e.clientX; mY = e.clientY; }
+ catch(e$1) { return false }
+ }
+ if (mX >= Math.floor(cm.display.gutters.getBoundingClientRect().right)) { return false }
+ if (prevent) { e_preventDefault(e); }
- // 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);
+ 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.display.gutterSpecs.length; ++i) {
+ var g = display.gutters.childNodes[i];
+ if (g && g.getBoundingClientRect().right >= mX) {
+ var line = lineAtHeight(cm.doc, mY);
+ var gutter = cm.display.gutterSpecs[i];
+ signal(cm, type, cm, line, gutter.className, e);
+ return e_defaultPrevented(e)
}
}
- 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);
- if (!cm.options.lineWrapping) findMaxLine(cm);
- cm.options.mode = doc.modeOption;
- regChange(cm);
+ function clickInGutter(cm, e) {
+ return gutterEvent(cm, e, "gutterClick", true)
}
- // LINE UTILITIES
+ // CONTEXT MENU HANDLING
- // 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.");
- for (var chunk = doc; !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];
+ // 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 }
+ if (!captureRightClick) { cm.display.input.onContextMenu(e); }
}
- // 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;
+ function contextMenuInGutter(cm, e) {
+ if (!hasHandler(cm, "gutterContextMenu")) { return false }
+ return gutterEvent(cm, e, "gutterContextMenu", false)
}
- // 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); });
- return out;
+
+ 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);
}
- // 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;
+ 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\u200e\u200f\u2028\u2029\ufeff\ufff9-\ufffc]/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("autocorrect", false, function (cm, val) { return cm.getInputField().autocorrect = val; }, true);
+ option("autocapitalize", false, function (cm, val) { return cm.getInputField().autocapitalize = val; }, true);
+ option("rtlMoveVisually", !windows);
+ option("wholeLineUpdateBefore", true);
+
+ option("theme", "default", function (cm) {
+ themeChanged(cm);
+ updateGutters(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, val) {
+ cm.display.gutterSpecs = getGutters(val, cm.options.lineNumbers);
+ updateGutters(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, val) {
+ cm.display.gutterSpecs = getGutters(cm.options.gutters, val);
+ updateGutters(cm);
+ }, true);
+ option("firstLineNumber", 1, updateGutters, true);
+ option("lineNumberFormatter", function (integer) { return integer; }, updateGutters, true);
+ option("showCursorWhenSelecting", false, updateSelection, true);
+
+ option("resetSelectionOnContextMenu", true);
+ option("lineWiseCopyCut", true);
+ option("pasteLinesPerSelection", true);
+ option("selectionsMayTouch", false);
+
+ option("readOnly", false, function (cm, val) {
+ if (val == "nocursor") {
+ onBlur(cm);
+ cm.display.input.blur();
+ }
+ cm.display.input.readOnlyChanged(val);
+ });
+
+ option("screenReaderLabel", null, function (cm, val) {
+ val = (val === '') ? null : val;
+ cm.display.input.screenReaderLabelChanged(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);
+ option("phrases", null);
}
- // 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();
- }
+ 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);
}
- 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 = 0; i < chunk.children.length; ++i) {
- var child = chunk.children[i], ch = child.height;
- if (h < ch) { chunk = child; continue outer; }
- h -= ch;
- n += child.chunkSize();
- }
- return n;
- } while (!chunk.lines);
- for (var i = 0; i < chunk.lines.length; ++i) {
- var line = chunk.lines[i], lh = line.height;
- if (h < lh) break;
- h -= lh;
- }
- return n + i;
+ 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);
+
+ var doc = options.value;
+ if (typeof doc == "string") { doc = new Doc(doc, options.mode, null, options.lineSeparator, options.direction); }
+ else if (options.mode) { doc.modeOption = options.mode; }
+ this.doc = doc;
+
+ var input = new CodeMirror.inputStyles[options.inputStyle](this);
+ var display = this.display = new Display(place, doc, input, options);
+ display.wrapper.CodeMirror = 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: -1, cutIncoming: -1, // 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(function () {
+ if (this$1.hasFocus() && !this$1.state.focused) { onFocus(this$1); }
+ }, 20); }
+ else
+ { onBlur(this); }
+
+ for (var opt in optionHandlers) { if (optionHandlers.hasOwnProperty(opt))
+ { optionHandlers[opt](this, options[opt], Init); } }
+ maybeUpdateLineNumberWidth(this);
+ if (options.finishInit) { options.finishInit(this); }
+ for (var i = 0; i < initHooks.length; ++i) { initHooks[i](this); }
+ 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;
- // Find the height above the given line.
- function heightAtLine(lineObj) {
- lineObj = visualLine(lineObj);
+ // 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.
+ on(d.scroller, "contextmenu", function (e) { return onContextMenu(cm, e); });
+ on(d.input.getField(), "contextmenu", function (e) {
+ if (!d.scroller.contains(e.target)) { onContextMenu(cm, e); }
+ });
- 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 = 0; i < p.children.length; ++i) {
- var cur = p.children[i];
- if (cur == chunk) break;
- else h += cur.height;
+ // 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;
}
}
- return h;
- }
+ 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) && !clickInGutter(cm, 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);
- // 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) {
- var order = line.order;
- if (order == null) order = line.order = bidiOrdering(line.text);
- return order;
- }
+ // 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);
+ }
+ });
- // HISTORY
+ // 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); });
- 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;
- }
+ // 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); }}
+ };
- // 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) {attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1);}, true);
- return histChange;
+ 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); });
}
- // 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;
+ 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; }
}
- }
- // 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);
+ 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);
- // Register a change in the history. Merges changes that are within
- // a single operation, ore 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 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 ((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
- var 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));
- }
+ if (indentString != curSpaceString) {
+ replaceRange(doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input");
+ line.stateAfter = null;
+ return true
} 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();
+ // 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
+ }
}
}
- 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);
+ // 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;
}
- // 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;
+ function applyTextInput(cm, inserted, deleted, sel, origin) {
+ var doc = cm.doc;
+ cm.display.shift = false;
+ if (!sel) { sel = doc.sel; }
+
+ var recent = +new Date - 200;
+ var paste = origin == "paste" || cm.state.pasteIncoming > recent;
+ var textLines = splitLinesAuto(inserted), multiPaste = null;
+ // When pasting 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]; });
+ }
+ }
- // 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);
+ var updateInput = cm.curOp.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 (paste && lastCopied && lastCopied.lineWise && lastCopied.text.join("\n") == textLines.join("\n"))
+ { from = to = Pos(from.line, 0); }
+ }
+ var changeEvent = {from: from, to: to, text: multiPaste ? multiPaste[i$1 % multiPaste.length] : textLines,
+ origin: origin || (paste ? "paste" : cm.state.cutIncoming > recent ? "cut" : "+input")};
+ makeChange(cm.doc, changeEvent);
+ signalLater(cm, "inputRead", cm, changeEvent);
+ }
+ if (inserted && !paste)
+ { triggerElectric(cm, inserted); }
- hist.lastSelTime = +new Date;
- hist.lastSelOrigin = origin;
- hist.lastSelOp = opId;
- if (options && options.clearRedo !== false)
- clearSelectionEvents(hist.undone);
+ ensureCursorVisible(cm);
+ if (cm.curOp.updateInput < 2) { cm.curOp.updateInput = updateInput; }
+ cm.curOp.typing = true;
+ cm.state.pasteIncoming = cm.state.cutIncoming = -1;
}
- function pushSelectionToHistory(sel, dest) {
- var top = lst(dest);
- if (!(top && top.ranges && top.equals(sel)))
- dest.push(sel);
+ 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
+ }
}
- // 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;
- });
+ 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); }
+ }
}
- // 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;
- for (var i = 0, out; i < spans.length; ++i) {
- if (spans[i].marker.explicitlyCleared) { if (!out) out = spans.slice(0, i); }
- else if (out) out.push(spans[i]);
+ 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 !out ? spans : out.length ? out : null;
+ return {text: text, ranges: ranges}
}
- // 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;
- for (var i = 0, nw = []; i < change.text.length; ++i)
- nw.push(removeClearedSpans(found[i]));
- return nw;
+ function disableBrowserMagic(field, spellcheck, autocorrect, autocapitalize) {
+ field.setAttribute("autocorrect", autocorrect ? "" : "off");
+ field.setAttribute("autocapitalize", autocapitalize ? "" : "off");
+ field.setAttribute("spellcheck", !!spellcheck);
}
- // 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) {
- for (var i = 0, copy = []; 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;
- 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];
+ function hiddenTextarea() {
+ var te = elt("textarea", null, null, "position: absolute; bottom: -1em; padding: 0; width: 1px; height: 1em; min-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 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.state.modeGen++;
+ regChange(this);
+ return
}
}
- }
- }
- return copy;
- }
+ }),
- // Rebasing/resetting history to deal with externally-sourced changes
+ 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 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.lastLine(), to.line - (to.ch ? 0 : 1)) + 1;
+ for (var j = start; j < end; ++j)
+ { indentLine(this, j, how); }
+ var newRanges = this.doc.sel.ranges;
+ if (from.ch == 0 && ranges.length == newRanges.length && newRanges[i].from().ch > 0)
+ { replaceOneSelection(this.doc, i, new Range(from, newRanges[i].to()), sel_dontScroll); }
+ } else if (range.head.line > end) {
+ indentLine(this, range.head.line, how, true);
+ end = range.head.line;
+ if (i == this.doc.sel.primIndex) { ensureCursorVisible(this); }
+ }
+ }
+ }),
+
+ // 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 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) && 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 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.doc, cur, dir, unit, visually);
+ if (cur.hitSide) { break }
+ }
+ return cur
+ },
- function rebaseHistSelSingle(pos, from, to, diff) {
- if (to < pos.line) {
- pos.line += diff;
- } else if (from < pos.line) {
- pos.line = from;
- pos.ch = 0;
- }
+ 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 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, cur, "div");
+ if (x == null) { x = coords.left; }
+ else { coords.left = x; }
+ cur = findPosV(this, 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.display);
+ if (oldHeight == null || Math.abs(oldHeight - textHeight(this.display)) > .5 || this.options.lineWrapping)
+ { estimateLineHeights(this); }
+ signal(this, "refresh", this);
+ }),
+
+ swapDoc: methodOp(function(doc) {
+ var old = this.doc;
+ old.cm = null;
+ // Cancel the current text selection if any (#5821)
+ if (this.state.selectingText) { this.state.selectingText(); }
+ 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
+ }),
+
+ phrase: function(phraseText) {
+ var phrases = this.options.phrases;
+ return phrases && Object.prototype.hasOwnProperty.call(phrases, phraseText) ? phrases[phraseText] : phraseText
+ },
+
+ 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});
+ };
}
- // 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);
+ // Used for horizontal relative motion. Dir is -1 or 1 (left or
+ // right), unit can be "codepoint", "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);
+ var lineDir = visually && doc.direction == "rtl" ? -dir : dir;
+ function findNextLine() {
+ var l = pos.line + lineDir;
+ 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 (unit == "codepoint") {
+ var ch = lineObj.text.charCodeAt(pos.ch + (dir > 0 ? 0 : -1));
+ if (isNaN(ch)) {
+ next = null;
+ } else {
+ var astral = dir > 0 ? ch >= 0xD800 && ch < 0xDC00 : ch >= 0xDC00 && ch < 0xDFFF;
+ next = new Pos(pos.line, Math.max(0, Math.min(lineObj.text.length, pos.ch + dir * (astral ? 2 : 1))), -dir);
}
- continue;
+ } else if (visually) {
+ next = moveVisually(doc.cm, lineObj, pos, dir);
+ } else {
+ next = moveLogically(lineObj, pos, dir);
}
- for (var j = 0; j < sub.changes.length; ++j) {
- var cur = sub.changes[j];
- 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 (next == null) {
+ if (!boundToLine && findNextLine())
+ { pos = endOfLine(visually, doc.cm, lineObj, pos.line, lineDir); }
+ else
+ { return false }
+ } else {
+ pos = next;
}
- if (!ok) {
- array.splice(0, i + 1);
- i = 0;
+ return true
+ }
+
+ if (unit == "char" || unit == "codepoint") {
+ 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
}
- 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);
- }
+ // 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;
- // EVENT UTILITIES
+ } 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
+ }
- // Due to the fact that we still support jurassic IE versions, some
- // compatibility wrappers are needed.
+ // CONTENTEDITABLE INPUT STYLE
- var e_preventDefault = CodeMirror.e_preventDefault = function(e) {
- if (e.preventDefault) e.preventDefault();
- else e.returnValue = false;
- };
- var e_stopPropagation = CodeMirror.e_stopPropagation = function(e) {
- if (e.stopPropagation) e.stopPropagation();
- else e.cancelBubble = true;
+ 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;
};
- function e_defaultPrevented(e) {
- return e.defaultPrevented != null ? e.defaultPrevented : e.returnValue == false;
- }
- var e_stop = CodeMirror.e_stop = function(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;
+ ContentEditableInput.prototype.init = function (display) {
+ var this$1 = this;
+
+ var input = this, cm = input.cm;
+ var div = input.div = display.lineDiv;
+ div.contentEditable = true;
+ disableBrowserMagic(div, cm.options.spellcheck, cm.options.autocorrect, cm.options.autocapitalize);
+
+ function belongsToInput(e) {
+ for (var t = e.target; t; t = t.parentNode) {
+ if (t == div) { return true }
+ if (/\bCodeMirror-(?:line)?widget\b/.test(t.className)) { break }
+ }
+ return false
}
- if (mac && e.ctrlKey && b == 1) b = 3;
- return b;
- }
- // EVENT HANDLING
+ on(div, "paste", function (e) {
+ if (!belongsToInput(e) || 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); }
+ });
- // Lightweight event framework. on/off also work on DOM nodes,
- // registering native DOM handlers.
+ 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;
+ }
+ });
- var on = CodeMirror.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 = {});
- var arr = map[type] || (map[type] = []);
- arr.push(f);
- }
+ on(div, "touchstart", function () { return input.forceCompositionEnd(); });
+
+ on(div, "input", function () {
+ if (!this$1.composing) { this$1.readFromDOMSoon(); }
+ });
+
+ function onCopyCut(e) {
+ if (!belongsToInput(e) || 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 = activeElt();
+ 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);
};
- var off = CodeMirror.off = function(emitter, type, f) {
- if (emitter.removeEventListener)
- emitter.removeEventListener(type, f, false);
- else if (emitter.detachEvent)
- emitter.detachEvent("on" + type, f);
- else {
- var arr = emitter._handlers && emitter._handlers[type];
- if (!arr) return;
- for (var i = 0; i < arr.length; ++i)
- if (arr[i] == f) { arr.splice(i, 1); break; }
+ ContentEditableInput.prototype.screenReaderLabelChanged = function (label) {
+ // Label for screenreaders, accessibility
+ if(label) {
+ this.div.setAttribute('aria-label', label);
+ } else {
+ this.div.removeAttribute('aria-label');
}
};
- var signal = CodeMirror.signal = function(emitter, type /*, values...*/) {
- var arr = emitter._handlers && emitter._handlers[type];
- if (!arr) return;
- var args = Array.prototype.slice.call(arguments, 2);
- for (var i = 0; i < arr.length; ++i) arr[i].apply(null, args);
+ ContentEditableInput.prototype.prepareSelection = function () {
+ var result = prepareSelection(this.cm, false);
+ result.focus = activeElt() == this.div;
+ return result
};
- var orphanDelayedCallbacks = null;
+ ContentEditableInput.prototype.showSelection = function (info, takeFocus) {
+ if (!info || !this.cm.display.view.length) { return }
+ if (info.focus || takeFocus) { this.showPrimarySelection(); }
+ this.showMultipleSelections(info);
+ };
- // 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 = emitter._handlers && emitter._handlers[type];
- if (!arr) 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);
+ ContentEditableInput.prototype.getSelection = function () {
+ return this.cm.display.wrapper.ownerDocument.getSelection()
+ };
+
+ ContentEditableInput.prototype.showPrimarySelection = function () {
+ var sel = this.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
}
- function bnd(f) {return function(){f.apply(null, args);};};
- for (var i = 0; i < arr.length; ++i)
- list.push(bnd(arr[i]));
- }
- function fireOrphanDelayed() {
- var delayed = orphanDelayedCallbacks;
- orphanDelayedCallbacks = null;
- for (var i = 0; i < delayed.length; ++i) delayed[i]();
- }
+ 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 }
- // 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;
- }
+ 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();
+ };
- 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]);
- }
+ 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 = this.getSelection();
+ this.lastAnchorNode = sel.anchorNode; this.lastAnchorOffset = sel.anchorOffset;
+ this.lastFocusNode = sel.focusNode; this.lastFocusOffset = sel.focusOffset;
+ };
- function hasHandler(emitter, type) {
- var arr = emitter._handlers && emitter._handlers[type];
- return arr && arr.length > 0;
- }
+ ContentEditableInput.prototype.selectionInEditor = function () {
+ var sel = this.getSelection();
+ if (!sel.rangeCount) { return false }
+ var node = sel.getRangeAt(0).commonAncestorContainer;
+ return contains(this.div, node)
+ };
- // 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);};
- }
+ ContentEditableInput.prototype.focus = function () {
+ if (this.cm.options.readOnly != "nocursor") {
+ if (!this.selectionInEditor() || activeElt() != this.div)
+ { this.showSelection(this.prepareSelection(), true); }
+ this.div.focus();
+ }
+ };
+ ContentEditableInput.prototype.blur = function () { this.div.blur(); };
+ ContentEditableInput.prototype.getField = function () { return this.div };
- // MISC UTILITIES
+ ContentEditableInput.prototype.supportsTouch = function () { return true };
- // Number of pixels added to scroller and sizer to hide scrollbar
- var scrollerGap = 30;
+ ContentEditableInput.prototype.receivedFocus = function () {
+ var this$1 = this;
- // Returned or thrown by various protocols to signal 'I'm not
- // handling this'.
- var Pass = CodeMirror.Pass = {toString: function(){return "CodeMirror.Pass";}};
+ var input = this;
+ if (this.selectionInEditor())
+ { setTimeout(function () { return this$1.pollSelection(); }, 20); }
+ else
+ { runInOp(this.cm, function () { return input.cm.curOp.selectionChanged = true; }); }
- // Reused option objects for setSelection & friends
- var sel_dontScroll = {scroll: false}, sel_mouse = {origin: "*mouse"}, sel_move = {origin: "+move"};
+ 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);
+ };
- function Delayed() {this.id = null;}
- Delayed.prototype.set = function(ms, f) {
- clearTimeout(this.id);
- this.id = setTimeout(f, ms);
+ ContentEditableInput.prototype.selectionChanged = function () {
+ var sel = this.getSelection();
+ return sel.anchorNode != this.lastAnchorNode || sel.anchorOffset != this.lastAnchorOffset ||
+ sel.focusNode != this.lastFocusNode || sel.focusOffset != this.lastFocusOffset
};
- // Counts the column offset in a string, taking tabs into account.
- // Used mostly to find indentation.
- var countColumn = CodeMirror.countColumn = function(string, end, tabSize, startIndex, startValue) {
- if (end == null) {
- end = string.search(/[^\s\u00a0]/);
- if (end == -1) end = string.length;
+ ContentEditableInput.prototype.pollSelection = function () {
+ if (this.readDOMTimeout != null || this.gracePeriod || !this.selectionChanged()) { return }
+ var sel = this.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.display.gutterSpecs.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;
}
- 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 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
}
};
- // 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;
- }
- }
+ 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);
+ };
- var spaceStrs = [""];
- function spaceStr(n) {
- while (spaceStrs.length <= n)
- spaceStrs.push(lst(spaceStrs) + " ");
- return spaceStrs[n];
- }
+ ContentEditableInput.prototype.updateFromDOM = function () {
+ var this$1 = this;
- function lst(arr) { return arr[arr.length-1]; }
+ if (this.cm.isReadOnly() || !this.pollContent())
+ { runInOp(this.cm, function () { return regChange(this$1.cm); }); }
+ };
- 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) {} };
+ ContentEditableInput.prototype.setUneditable = function (node) {
+ node.contentEditable = "false";
+ };
- function indexOf(array, elt) {
- for (var i = 0; i < array.length; ++i)
- if (array[i] == elt) return i;
- return -1;
+ ContentEditableInput.prototype.onKeyPress = function (e) {
+ if (e.charCode == 0 || this.composing) { 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 map(array, f) {
- var out = [];
- for (var i = 0; i < array.length; i++) out[i] = f(array[i], i);
- return out;
+
+ function isInGutter(node) {
+ for (var scan = node; scan; scan = scan.parentNode)
+ { if (/CodeMirror-gutter-wrapper/.test(scan.className)) { return true } }
+ return false
}
- function nothing() {}
+ function badPos(pos, bad) { if (bad) { pos.bad = true; } return pos }
- function createObj(base, props) {
- var inst;
- if (Object.create) {
- inst = Object.create(base);
- } else {
- nothing.prototype = base;
- inst = new nothing();
+ function domTextBetween(cm, from, to, fromLine, toLine) {
+ var text = "", closing = false, lineSep = cm.doc.lineSeparator(), extraLinebreak = false;
+ function recognizeMarker(id) { return function (marker) { return marker.id == id; } }
+ function close() {
+ if (closing) {
+ text += lineSep;
+ if (extraLinebreak) { text += lineSep; }
+ closing = extraLinebreak = false;
+ }
}
- if (props) copyObj(props, inst);
- return inst;
- };
+ function addText(str) {
+ if (str) {
+ close();
+ text += str;
+ }
+ }
+ function walk(node) {
+ if (node.nodeType == 1) {
+ var cmText = node.getAttribute("cm-text");
+ if (cmText) {
+ addText(cmText);
+ 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(0)))
+ { addText(getBetween(cm.doc, range.from, range.to).join(lineSep)); }
+ return
+ }
+ if (node.getAttribute("contenteditable") == "false") { return }
+ var isBlock = /^(pre|div|p|li|table|br)$/i.test(node.nodeName);
+ if (!/^br$/i.test(node.nodeName) && node.textContent.length == 0) { return }
- 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;
- }
+ if (isBlock) { close(); }
+ for (var i = 0; i < node.childNodes.length; i++)
+ { walk(node.childNodes[i]); }
- function bind(f) {
- var args = Array.prototype.slice.call(arguments, 1);
- return function(){return f.apply(null, args);};
+ if (/^(pre|p)$/i.test(node.nodeName)) { extraLinebreak = true; }
+ if (isBlock) { closing = true; }
+ } else if (node.nodeType == 3) {
+ addText(node.nodeValue.replace(/\u200b/g, "").replace(/\u00a0/g, " "));
+ }
+ }
+ for (;;) {
+ walk(from);
+ if (from == to) { break }
+ from = from.nextSibling;
+ extraLinebreak = false;
+ }
+ return text
}
- var nonASCIISingleCaseWordChar = /[\u00df\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/;
- var isWordCharBasic = CodeMirror.isWordChar = function(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 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 isEmpty(obj) {
- for (var n in obj) if (obj.hasOwnProperty(n) && obj[n]) return false;
- return true;
- }
+ 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)
+ }
+ }
- // 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); }
+ 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;
- // DOM UTILITIES
+ 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) }
- 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;
+ // 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; }
+ }
}
- 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;
+ // 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;
};
- function removeChildren(e) {
- for (var count = e.childNodes.length; count > 0; --count)
- e.removeChild(e.firstChild);
- return e;
- }
+ TextareaInput.prototype.init = function (display) {
+ var this$1 = this;
- function removeChildrenAndAdd(parent, e) {
- return removeChildren(parent).appendChild(e);
- }
+ var input = this, cm = this.cm;
+ this.createField(display);
+ var te = this.textarea;
- var contains = CodeMirror.contains = function(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);
- };
+ display.wrapper.insertBefore(this.wrapper, display.wrapper.firstChild);
- function activeElt() { return document.activeElement; }
- // Older versions of IE throws unspecified error when touching
- // document.activeElement in some cases (during loading, in iframe)
- if (ie && ie_version < 11) activeElt = function() {
- try { return document.activeElement; }
- catch(e) { return document.body; }
- };
+ // 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"; }
- function classTest(cls) { return new RegExp("(^|\\s)" + cls + "(?:$|\\s)\\s*"); }
- var rmClass = CodeMirror.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 : "");
- }
- };
- var addClass = CodeMirror.addClass = function(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;
- }
+ on(te, "input", function () {
+ if (ie && ie_version >= 9 && this$1.hasSelection) { this$1.hasSelection = null; }
+ input.poll();
+ });
- // WINDOW-WIDE EVENTS
+ on(te, "paste", function (e) {
+ if (signalDOMEvent(cm, e) || handlePaste(e, cm)) { return }
- // These must be handled carefully, because naively registering a
- // handler for each editor will cause the editors to never be
- // garbage collected.
+ cm.state.pasteIncoming = +new Date;
+ input.fastPoll();
+ });
- function forEachCodeMirror(f) {
- if (!document.body.getElementsByClassName) return;
- var byClass = document.body.getElementsByClassName("CodeMirror");
- for (var i = 0; i < byClass.length; i++) {
- var cm = byClass[i].CodeMirror;
- if (cm) f(cm);
+ 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 = +new Date; }
}
- }
+ on(te, "cut", prepareCopyCut);
+ on(te, "copy", prepareCopyCut);
- 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);
+ on(display.scroller, "paste", function (e) {
+ if (eventInWidget(display, e) || signalDOMEvent(cm, e)) { return }
+ if (!te.dispatchEvent) {
+ cm.state.pasteIncoming = +new Date;
+ input.focus();
+ return
+ }
+
+ // Pass the `paste` event to the textarea so it's handled by its event listener.
+ var event = new Event("paste");
+ event.clipboardData = e.clipboardData;
+ te.dispatchEvent(event);
});
- // When the window loses focus, we want to show the editor as blurred
- on(window, "blur", function() {
- forEachCodeMirror(onBlur);
+
+ // Prevent normal selection in the editor (we handle our own)
+ on(display.lineSpace, "selectstart", function (e) {
+ if (!eventInWidget(display, e)) { e_preventDefault(e); }
});
- }
- // FEATURE DETECTION
+ 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;
+ }
+ });
+ };
- // 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;
- }();
+ TextareaInput.prototype.createField = function (_display) {
+ // Wraps and hides input textarea
+ this.wrapper = hiddenTextarea();
+ // The semihidden textarea that is focused when the editor is
+ // focused, and receives input.
+ this.textarea = this.wrapper.firstChild;
+ };
- 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);
+ TextareaInput.prototype.screenReaderLabelChanged = function (label) {
+ // Label for screenreaders, accessibility
+ if(label) {
+ this.textarea.setAttribute('aria-label', label);
+ } else {
+ this.textarea.removeAttribute('aria-label');
}
- 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();
- if (!r0 || r0.left == r0.right) return false; // Safari returns null in some cases (#2780)
- var r1 = range(txt, 1, 2).getBoundingClientRect();
- return badBidiRects = (r1.right - r0.right < 3);
- }
+ TextareaInput.prototype.prepareSelection = function () {
+ // Redraw the selection and/or cursor
+ var cm = this.cm, display = cm.display, doc = cm.doc;
+ var result = prepareSelection(cm);
- // See if "".split is the broken IE version, if so, provide an
- // alternative way to split lines.
- var splitLines = CodeMirror.splitLines = "\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;
- }
+ // 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;
- } : 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) {
- try {var range = te.ownerDocument.selection.createRange();}
- catch(e) {}
- if (!range || range.parentElement() != te) return false;
- return range.compareEndPoints("StartToEnd", range) != 0;
+ return result
};
- var hasCopyEvent = (function() {
- var e = elt("div");
- if ("oncopy" in e) return true;
- e.setAttribute("oncopy", "return;");
- return typeof e.oncopy == "function";
- })();
+ 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";
+ }
+ };
- 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;
- }
-
- // KEY NAMES
-
- 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", 107: "=", 109: "-", 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"};
- CodeMirror.keyNames = keyNames;
- (function() {
- // Number keys
- for (var i = 0; i < 10; i++) keyNames[i + 48] = keyNames[i + 96] = String(i);
- // Alphabetic keys
- for (var i = 65; i <= 90; i++) keyNames[i] = String.fromCharCode(i);
- // Function keys
- for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = "F" + i;
- })();
+ // 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; }
+ }
+ };
- // BIDI HELPERS
+ TextareaInput.prototype.getField = function () { return this.textarea };
- 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;
- }
+ 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
}
- if (!found) f(from, to, "ltr");
- }
+ };
- function bidiLeft(part) { return part.level % 2 ? part.to : part.from; }
- function bidiRight(part) { return part.level % 2 ? part.from : part.to; }
+ TextareaInput.prototype.blur = function () { this.textarea.blur(); };
- function lineLeft(line) { var order = getOrder(line); return order ? bidiLeft(order[0]) : 0; }
- function lineRight(line) {
- var order = getOrder(line);
- if (!order) return line.text.length;
- return bidiRight(lst(order));
- }
+ TextareaInput.prototype.resetPosition = function () {
+ this.wrapper.style.top = this.wrapper.style.left = 0;
+ };
- function lineStart(cm, lineN) {
- var line = getLine(cm.doc, lineN);
- var visual = visualLine(line);
- if (visual != line) lineN = lineNo(visual);
- var order = getOrder(visual);
- var ch = !order ? 0 : order[0].level % 2 ? lineRight(visual) : lineLeft(visual);
- return Pos(lineN, ch);
- }
- function lineEnd(cm, lineN) {
- var merged, line = getLine(cm.doc, lineN);
- while (merged = collapsedSpanAtEnd(line)) {
- line = merged.find(1, true).line;
- lineN = null;
- }
- var order = getOrder(line);
- var ch = !order ? line.text.length : order[0].level % 2 ? lineLeft(line) : lineRight(line);
- return Pos(lineN == null ? lineNo(line) : lineN, ch);
- }
- function lineStartSmart(cm, pos) {
- var start = lineStart(cm, pos.line);
- var line = getLine(cm.doc, start.line);
- var order = getOrder(line);
- 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);
- }
- return start;
- }
+ TextareaInput.prototype.receivedFocus = function () { this.slowPoll(); };
- function compareBidiLevel(order, a, b) {
- var linedir = order[0].level;
- if (a == linedir) return true;
- if (b == linedir) return false;
- return a < b;
- }
- var bidiOther;
- function getBidiPartAt(order, pos) {
- bidiOther = null;
- for (var i = 0, found; i < order.length; ++i) {
- var cur = order[i];
- if (cur.from < pos && cur.to > pos) return i;
- if ((cur.from == pos || cur.to == pos)) {
- if (found == null) {
- found = i;
- } else if (compareBidiLevel(order, cur.level, order[found].level)) {
- if (cur.from != cur.to) bidiOther = found;
- return i;
- } else {
- if (cur.from != cur.to) bidiOther = i;
- return found;
- }
+ // 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 found;
- }
+ });
+ return true
+ };
- function moveInLine(line, pos, dir, byUnit) {
- if (!byUnit) return pos + dir;
- do pos += dir;
- while (pos > 0 && isExtendingChar(line.text.charAt(pos)));
- return pos;
- }
+ TextareaInput.prototype.ensurePolled = function () {
+ if (this.pollingFast && this.poll()) { this.pollingFast = false; }
+ };
- // This is needed in order to move 'visually' through bi-directional
- // text -- i.e., pressing left should make the cursor go left, even
- // when in RTL text. The tricky part is the 'jumps', where RTL and
- // LTR text touch each other. This often requires the cursor offset
- // to move more than one unit, in order to visually move one unit.
- function moveVisually(line, start, dir, byUnit) {
- var bidi = getOrder(line);
- if (!bidi) return moveLogically(line, start, dir, byUnit);
- var pos = getBidiPartAt(bidi, start), part = bidi[pos];
- var target = moveInLine(line, start, part.level % 2 ? -dir : dir, byUnit);
+ TextareaInput.prototype.onKeyPress = function () {
+ if (ie && ie_version >= 9) { this.hasSelection = null; }
+ this.fastPoll();
+ };
- for (;;) {
- if (target > part.from && target < part.to) return target;
- if (target == part.from || target == part.to) {
- if (getBidiPartAt(bidi, target) == pos) return target;
- part = bidi[pos += dir];
- return (dir > 0) == part.level % 2 ? part.to : part.from;
- } else {
- part = bidi[pos += dir];
- if (!part) return null;
- if ((dir > 0) == part.level % 2)
- target = moveInLine(line, part.to, -1, byUnit);
- else
- target = moveInLine(line, part.from, 1, byUnit);
+ TextareaInput.prototype.onContextMenu = function (e) {
+ var input = this, cm = input.cm, display = cm.display, te = input.textarea;
+ if (input.contextMenuPending) { input.contextMenuPending(); }
+ 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;
+ var wrapperBox = input.wrapper.offsetParent.getBoundingClientRect();
+ input.wrapper.style.cssText = "position: static";
+ 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 = rehide;
+ 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() {
+ if (input.contextMenuPending != rehide) { return }
+ 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);
}
}
- }
- function moveLogically(line, start, dir, byUnit) {
- var target = start + dir;
- if (byUnit) while (target > 0 && isExtendingChar(line.text.charAt(target))) target += dir;
- return target < 0 || target > line.text.length ? null : target;
- }
+ 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);
+ }
+ };
- // Bidirectional ordering algorithm
- // See http://unicode.org/reports/tr9/tr9-13.html for the algorithm
- // that this (partially) implements.
+ TextareaInput.prototype.readOnlyChanged = function (val) {
+ if (!val) { this.reset(); }
+ this.textarea.disabled = val == "nocursor";
+ this.textarea.readOnly = !!val;
+ };
- // 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
+ TextareaInput.prototype.setUneditable = function () {};
- // 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 0x6ff
- var arabicTypes = "rrrrrrrrrrrr,rNNmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmrrrrrrrnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmNmmmm";
- function charType(code) {
- if (code <= 0xf7) return lowTypes.charAt(code);
- else if (0x590 <= code && code <= 0x5f4) return "R";
- else if (0x600 <= code && code <= 0x6ed) 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";
+ 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;
}
- var bidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/;
- var isNeutral = /[stwN]/, isStrong = /[LRr]/, countsAsLeft = /[Lb1n]/, countsAsNum = /[1n]/;
- // Browsers seem to always treat the boundaries of block elements as being L.
- var outerType = "L";
+ function save() {textarea.value = cm.getValue();}
- function BidiSpan(level, from, to) {
- this.level = level;
- this.from = from; this.to = to;
+ 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) {}
+ }
}
- return function(str) {
- if (!bidiRE.test(str)) return false;
- var len = str.length, types = [];
- for (var i = 0, type; i < len; ++i)
- types.push(type = charType(str.charCodeAt(i)));
+ 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 (!options.leaveSubmitMethodAlone && typeof textarea.form.submit == "function")
+ { textarea.form.submit = realSubmit; }
+ }
+ };
+ };
- // 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 = 0, prev = outerType; i < len; ++i) {
- var type = types[i];
- if (type == "m") types[i] = prev;
- else prev = type;
- }
+ 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;
+ }
- // 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 = 0, cur = outerType; i < len; ++i) {
- var type = types[i];
- if (type == "1" && cur == "r") types[i] = "n";
- else if (isStrong.test(type)) { cur = type; if (type == "r") types[i] = "R"; }
- }
+ // EDITOR CONSTRUCTOR
- // 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 = 1, prev = types[0]; i < len - 1; ++i) {
- var type = types[i];
- if (type == "+" && prev == "1" && types[i+1] == "1") types[i] = "1";
- else if (type == "," && prev == types[i+1] &&
- (prev == "1" || prev == "n")) types[i] = prev;
- prev = type;
- }
+ defineOptions(CodeMirror);
- // 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 = 0; i < len; ++i) {
- var type = types[i];
- if (type == ",") types[i] = "N";
- else if (type == "%") {
- for (var end = i + 1; end < len && types[end] == "%"; ++end) {}
- var replace = (i && types[i-1] == "!") || (end < len && types[end] == "1") ? "1" : "N";
- for (var j = i; j < end; ++j) types[j] = replace;
- i = end - 1;
- }
- }
+ addEditorMethods(CodeMirror);
- // 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 = 0, cur = outerType; i < len; ++i) {
- var type = types[i];
- if (cur == "L" && type == "1") types[i] = "L";
- else if (isStrong.test(type)) cur = type;
- }
+ // 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]); } }
- // 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 = 0; i < len; ++i) {
- if (isNeutral.test(types[i])) {
- for (var end = i + 1; end < len && isNeutral.test(types[end]); ++end) {}
- var before = (i ? types[i-1] : outerType) == "L";
- var after = (end < len ? types[end] : outerType) == "L";
- var replace = before || after ? "L" : "R";
- for (var j = i; j < end; ++j) types[j] = replace;
- i = end - 1;
- }
- }
+ eventMixin(Doc);
+ CodeMirror.inputStyles = {"textarea": TextareaInput, "contenteditable": ContentEditableInput};
- // 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 = 0; i < len;) {
- if (countsAsLeft.test(types[i])) {
- var start = i;
- for (++i; i < len && countsAsLeft.test(types[i]); ++i) {}
- order.push(new BidiSpan(0, start, i));
- } else {
- var pos = i, at = order.length;
- for (++i; i < len && types[i] != "L"; ++i) {}
- for (var j = pos; j < i;) {
- if (countsAsNum.test(types[j])) {
- if (pos < j) order.splice(at, 0, new BidiSpan(1, pos, j));
- var nstart = j;
- for (++j; j < i && countsAsNum.test(types[j]); ++j) {}
- order.splice(at, 0, new BidiSpan(2, nstart, j));
- pos = j;
- } else ++j;
- }
- if (pos < i) order.splice(at, 0, new BidiSpan(1, pos, i));
- }
- }
- 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));
- }
- if (order[0].level != lst(order).level)
- order.push(new BidiSpan(order[0].level, len, len));
+ // 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);
+ };
- return order;
- };
- })();
+ 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;
+ };
- // THE END
+ CodeMirror.fromTextArea = fromTextArea;
- CodeMirror.version = "5.0.0";
+ addLegacyProps(CodeMirror);
+
+ CodeMirror.version = "5.65.1";
return CodeMirror;
-});
+
+})));
diff --git a/static/editor.md/lib/codemirror/mode/apl/apl.js b/static/editor.md/lib/codemirror/mode/apl/apl.js
index 4357bed47..b1955f6c9 100644
--- a/static/editor.md/lib/codemirror/mode/apl/apl.js
+++ b/static/editor.md/lib/codemirror/mode/apl/apl.js
@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
@@ -102,7 +102,7 @@ CodeMirror.defineMode("apl", function() {
};
},
token: function(stream, state) {
- var ch, funcName, word;
+ var ch, funcName;
if (stream.eatSpace()) {
return null;
}
@@ -163,7 +163,6 @@ CodeMirror.defineMode("apl", function() {
return "function jot-dot";
}
stream.eatWhile(/[\w\$_]/);
- word = stream.current();
state.prev = true;
return "keyword";
}
diff --git a/static/editor.md/lib/codemirror/mode/apl/index.html b/static/editor.md/lib/codemirror/mode/apl/index.html
index 53dda6b58..81cef4915 100644
--- a/static/editor.md/lib/codemirror/mode/apl/index.html
+++ b/static/editor.md/lib/codemirror/mode/apl/index.html
@@ -12,7 +12,7 @@
.CodeMirror { border: 2px inset #dee; }
-
CodeMirror
+
CodeMirror
Home
diff --git a/static/editor.md/lib/codemirror/mode/asciiarmor/asciiarmor.js b/static/editor.md/lib/codemirror/mode/asciiarmor/asciiarmor.js
new file mode 100644
index 000000000..f560f4242
--- /dev/null
+++ b/static/editor.md/lib/codemirror/mode/asciiarmor/asciiarmor.js
@@ -0,0 +1,74 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: https://codemirror.net/LICENSE
+
+(function(mod) {
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
+ mod(require("../../lib/codemirror"));
+ else if (typeof define == "function" && define.amd) // AMD
+ define(["../../lib/codemirror"], mod);
+ else // Plain browser env
+ mod(CodeMirror);
+})(function(CodeMirror) {
+ "use strict";
+
+ function errorIfNotEmpty(stream) {
+ var nonWS = stream.match(/^\s*\S/);
+ stream.skipToEnd();
+ return nonWS ? "error" : null;
+ }
+
+ CodeMirror.defineMode("asciiarmor", function() {
+ return {
+ token: function(stream, state) {
+ var m;
+ if (state.state == "top") {
+ if (stream.sol() && (m = stream.match(/^-----BEGIN (.*)?-----\s*$/))) {
+ state.state = "headers";
+ state.type = m[1];
+ return "tag";
+ }
+ return errorIfNotEmpty(stream);
+ } else if (state.state == "headers") {
+ if (stream.sol() && stream.match(/^\w+:/)) {
+ state.state = "header";
+ return "atom";
+ } else {
+ var result = errorIfNotEmpty(stream);
+ if (result) state.state = "body";
+ return result;
+ }
+ } else if (state.state == "header") {
+ stream.skipToEnd();
+ state.state = "headers";
+ return "string";
+ } else if (state.state == "body") {
+ if (stream.sol() && (m = stream.match(/^-----END (.*)?-----\s*$/))) {
+ if (m[1] != state.type) return "error";
+ state.state = "end";
+ return "tag";
+ } else {
+ if (stream.eatWhile(/[A-Za-z0-9+\/=]/)) {
+ return null;
+ } else {
+ stream.next();
+ return "error";
+ }
+ }
+ } else if (state.state == "end") {
+ return errorIfNotEmpty(stream);
+ }
+ },
+ blankLine: function(state) {
+ if (state.state == "headers") state.state = "body";
+ },
+ startState: function() {
+ return {state: "top", type: null};
+ }
+ };
+ });
+
+ CodeMirror.defineMIME("application/pgp", "asciiarmor");
+ CodeMirror.defineMIME("application/pgp-encrypted", "asciiarmor");
+ CodeMirror.defineMIME("application/pgp-keys", "asciiarmor");
+ CodeMirror.defineMIME("application/pgp-signature", "asciiarmor");
+});
diff --git a/static/editor.md/lib/codemirror/mode/asciiarmor/index.html b/static/editor.md/lib/codemirror/mode/asciiarmor/index.html
new file mode 100644
index 000000000..2ca76cc96
--- /dev/null
+++ b/static/editor.md/lib/codemirror/mode/asciiarmor/index.html
@@ -0,0 +1,46 @@
+
+
+CodeMirror: ASCII Armor (PGP) mode
+
+
+
+
+
+
+
+
+
+
+ASCII Armor (PGP) mode
+
+
+
+
+MIME types
+defined: application/pgp, application/pgp-encrypted, application/pgp-keys, application/pgp-signature
+
+
diff --git a/static/editor.md/lib/codemirror/mode/asn.1/asn.1.js b/static/editor.md/lib/codemirror/mode/asn.1/asn.1.js
new file mode 100644
index 000000000..df1330b68
--- /dev/null
+++ b/static/editor.md/lib/codemirror/mode/asn.1/asn.1.js
@@ -0,0 +1,204 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: https://codemirror.net/LICENSE
+
+(function(mod) {
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
+ mod(require("../../lib/codemirror"));
+ else if (typeof define == "function" && define.amd) // AMD
+ define(["../../lib/codemirror"], mod);
+ else // Plain browser env
+ mod(CodeMirror);
+})(function(CodeMirror) {
+ "use strict";
+
+ CodeMirror.defineMode("asn.1", function(config, parserConfig) {
+ var indentUnit = config.indentUnit,
+ keywords = parserConfig.keywords || {},
+ cmipVerbs = parserConfig.cmipVerbs || {},
+ compareTypes = parserConfig.compareTypes || {},
+ status = parserConfig.status || {},
+ tags = parserConfig.tags || {},
+ storage = parserConfig.storage || {},
+ modifier = parserConfig.modifier || {},
+ accessTypes = parserConfig.accessTypes|| {},
+ multiLineStrings = parserConfig.multiLineStrings,
+ indentStatements = parserConfig.indentStatements !== false;
+ var isOperatorChar = /[\|\^]/;
+ var curPunc;
+
+ function tokenBase(stream, state) {
+ var ch = stream.next();
+ if (ch == '"' || ch == "'") {
+ state.tokenize = tokenString(ch);
+ return state.tokenize(stream, state);
+ }
+ if (/[\[\]\(\){}:=,;]/.test(ch)) {
+ curPunc = ch;
+ return "punctuation";
+ }
+ if (ch == "-"){
+ if (stream.eat("-")) {
+ stream.skipToEnd();
+ return "comment";
+ }
+ }
+ if (/\d/.test(ch)) {
+ stream.eatWhile(/[\w\.]/);
+ return "number";
+ }
+ if (isOperatorChar.test(ch)) {
+ stream.eatWhile(isOperatorChar);
+ return "operator";
+ }
+
+ stream.eatWhile(/[\w\-]/);
+ var cur = stream.current();
+ if (keywords.propertyIsEnumerable(cur)) return "keyword";
+ if (cmipVerbs.propertyIsEnumerable(cur)) return "variable cmipVerbs";
+ if (compareTypes.propertyIsEnumerable(cur)) return "atom compareTypes";
+ if (status.propertyIsEnumerable(cur)) return "comment status";
+ if (tags.propertyIsEnumerable(cur)) return "variable-3 tags";
+ if (storage.propertyIsEnumerable(cur)) return "builtin storage";
+ if (modifier.propertyIsEnumerable(cur)) return "string-2 modifier";
+ if (accessTypes.propertyIsEnumerable(cur)) return "atom accessTypes";
+
+ return "variable";
+ }
+
+ function tokenString(quote) {
+ return function(stream, state) {
+ var escaped = false, next, end = false;
+ while ((next = stream.next()) != null) {
+ if (next == quote && !escaped){
+ var afterNext = stream.peek();
+ //look if the character if the quote is like the B in '10100010'B
+ if (afterNext){
+ afterNext = afterNext.toLowerCase();
+ if(afterNext == "b" || afterNext == "h" || afterNext == "o")
+ stream.next();
+ }
+ end = true; break;
+ }
+ escaped = !escaped && next == "\\";
+ }
+ if (end || !(escaped || multiLineStrings))
+ state.tokenize = null;
+ return "string";
+ };
+ }
+
+ function Context(indented, column, type, align, prev) {
+ this.indented = indented;
+ this.column = column;
+ this.type = type;
+ this.align = align;
+ this.prev = prev;
+ }
+ function pushContext(state, col, type) {
+ var indent = state.indented;
+ if (state.context && state.context.type == "statement")
+ indent = state.context.indented;
+ return state.context = new Context(indent, col, type, null, state.context);
+ }
+ function popContext(state) {
+ var t = state.context.type;
+ if (t == ")" || t == "]" || t == "}")
+ state.indented = state.context.indented;
+ return state.context = state.context.prev;
+ }
+
+ //Interface
+ return {
+ startState: function(basecolumn) {
+ return {
+ tokenize: null,
+ context: new Context((basecolumn || 0) - indentUnit, 0, "top", false),
+ indented: 0,
+ startOfLine: true
+ };
+ },
+
+ token: function(stream, state) {
+ var ctx = state.context;
+ if (stream.sol()) {
+ if (ctx.align == null) ctx.align = false;
+ state.indented = stream.indentation();
+ state.startOfLine = true;
+ }
+ if (stream.eatSpace()) return null;
+ curPunc = null;
+ var style = (state.tokenize || tokenBase)(stream, state);
+ if (style == "comment") return style;
+ if (ctx.align == null) ctx.align = true;
+
+ if ((curPunc == ";" || curPunc == ":" || curPunc == ",")
+ && ctx.type == "statement"){
+ popContext(state);
+ }
+ else if (curPunc == "{") pushContext(state, stream.column(), "}");
+ else if (curPunc == "[") pushContext(state, stream.column(), "]");
+ else if (curPunc == "(") pushContext(state, stream.column(), ")");
+ else if (curPunc == "}") {
+ while (ctx.type == "statement") ctx = popContext(state);
+ if (ctx.type == "}") ctx = popContext(state);
+ while (ctx.type == "statement") ctx = popContext(state);
+ }
+ else if (curPunc == ctx.type) popContext(state);
+ else if (indentStatements && (((ctx.type == "}" || ctx.type == "top")
+ && curPunc != ';') || (ctx.type == "statement"
+ && curPunc == "newstatement")))
+ pushContext(state, stream.column(), "statement");
+
+ state.startOfLine = false;
+ return style;
+ },
+
+ electricChars: "{}",
+ lineComment: "--",
+ fold: "brace"
+ };
+ });
+
+ function words(str) {
+ var obj = {}, words = str.split(" ");
+ for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
+ return obj;
+ }
+
+ CodeMirror.defineMIME("text/x-ttcn-asn", {
+ name: "asn.1",
+ keywords: words("DEFINITIONS OBJECTS IF DERIVED INFORMATION ACTION" +
+ " REPLY ANY NAMED CHARACTERIZED BEHAVIOUR REGISTERED" +
+ " WITH AS IDENTIFIED CONSTRAINED BY PRESENT BEGIN" +
+ " IMPORTS FROM UNITS SYNTAX MIN-ACCESS MAX-ACCESS" +
+ " MINACCESS MAXACCESS REVISION STATUS DESCRIPTION" +
+ " SEQUENCE SET COMPONENTS OF CHOICE DistinguishedName" +
+ " ENUMERATED SIZE MODULE END INDEX AUGMENTS EXTENSIBILITY" +
+ " IMPLIED EXPORTS"),
+ cmipVerbs: words("ACTIONS ADD GET NOTIFICATIONS REPLACE REMOVE"),
+ compareTypes: words("OPTIONAL DEFAULT MANAGED MODULE-TYPE MODULE_IDENTITY" +
+ " MODULE-COMPLIANCE OBJECT-TYPE OBJECT-IDENTITY" +
+ " OBJECT-COMPLIANCE MODE CONFIRMED CONDITIONAL" +
+ " SUBORDINATE SUPERIOR CLASS TRUE FALSE NULL" +
+ " TEXTUAL-CONVENTION"),
+ status: words("current deprecated mandatory obsolete"),
+ tags: words("APPLICATION AUTOMATIC EXPLICIT IMPLICIT PRIVATE TAGS" +
+ " UNIVERSAL"),
+ storage: words("BOOLEAN INTEGER OBJECT IDENTIFIER BIT OCTET STRING" +
+ " UTCTime InterfaceIndex IANAifType CMIP-Attribute" +
+ " REAL PACKAGE PACKAGES IpAddress PhysAddress" +
+ " NetworkAddress BITS BMPString TimeStamp TimeTicks" +
+ " TruthValue RowStatus DisplayString GeneralString" +
+ " GraphicString IA5String NumericString" +
+ " PrintableString SnmpAdminString TeletexString" +
+ " UTF8String VideotexString VisibleString StringStore" +
+ " ISO646String T61String UniversalString Unsigned32" +
+ " Integer32 Gauge Gauge32 Counter Counter32 Counter64"),
+ modifier: words("ATTRIBUTE ATTRIBUTES MANDATORY-GROUP MANDATORY-GROUPS" +
+ " GROUP GROUPS ELEMENTS EQUALITY ORDERING SUBSTRINGS" +
+ " DEFINED"),
+ accessTypes: words("not-accessible accessible-for-notify read-only" +
+ " read-create read-write"),
+ multiLineStrings: true
+ });
+});
diff --git a/static/editor.md/lib/codemirror/mode/asn.1/index.html b/static/editor.md/lib/codemirror/mode/asn.1/index.html
new file mode 100644
index 000000000..c79f48956
--- /dev/null
+++ b/static/editor.md/lib/codemirror/mode/asn.1/index.html
@@ -0,0 +1,78 @@
+
+
+CodeMirror: ASN.1 mode
+
+
+
+
+
+
+
+
+
+
+ ASN.1 example
+
+
+ --
+ -- Sample ASN.1 Code
+ --
+ MyModule DEFINITIONS ::=
+ BEGIN
+
+ MyTypes ::= SEQUENCE {
+ myObjectId OBJECT IDENTIFIER,
+ mySeqOf SEQUENCE OF MyInt,
+ myBitString BIT STRING {
+ muxToken(0),
+ modemToken(1)
+ }
+ }
+
+ MyInt ::= INTEGER (0..65535)
+
+ END
+
+
+
+
+
+ Language: Abstract Syntax Notation One
+ (ASN.1 )
+
+ MIME types defined: text/x-ttcn-asn
+
+
+ The development of this mode has been sponsored by Ericsson
+ .
+ Coded by Asmelash Tsegay Gebretsadkan
+
+
diff --git a/static/editor.md/lib/codemirror/mode/asterisk/asterisk.js b/static/editor.md/lib/codemirror/mode/asterisk/asterisk.js
index a1ead1157..49a727019 100644
--- a/static/editor.md/lib/codemirror/mode/asterisk/asterisk.js
+++ b/static/editor.md/lib/codemirror/mode/asterisk/asterisk.js
@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
/*
* =====================================================================================
@@ -9,7 +9,7 @@
* Description: CodeMirror mode for Asterisk dialplan
*
* Created: 05/17/2012 09:20:25 PM
- * Revision: none
+ * Revision: 08/05/2019 AstLinux Project: Support block-comments
*
* Author: Stas Kobzar (stas@modulis.ca),
* Company: Modulis.ca Inc.
@@ -65,10 +65,28 @@ CodeMirror.defineMode("asterisk", function() {
function basicToken(stream,state){
var cur = '';
- var ch = '';
- ch = stream.next();
+ var ch = stream.next();
// comment
+ if (state.blockComment) {
+ if (ch == "-" && stream.match("-;", true)) {
+ state.blockComment = false;
+ } else if (stream.skipTo("--;")) {
+ stream.next();
+ stream.next();
+ stream.next();
+ state.blockComment = false;
+ } else {
+ stream.skipToEnd();
+ }
+ return "comment";
+ }
if(ch == ";") {
+ if (stream.match("--", true)) {
+ if (!stream.match("-", false)) { // Except ;--- is not a block comment
+ state.blockComment = true;
+ return "comment";
+ }
+ }
stream.skipToEnd();
return "comment";
}
@@ -125,6 +143,7 @@ CodeMirror.defineMode("asterisk", function() {
return {
startState: function() {
return {
+ blockComment: false,
extenStart: false,
extenSame: false,
extenInclude: false,
@@ -136,7 +155,6 @@ CodeMirror.defineMode("asterisk", function() {
token: function(stream, state) {
var cur = '';
- var ch = '';
if(stream.eatSpace()) return null;
// extension started
if(state.extenStart){
@@ -170,7 +188,7 @@ CodeMirror.defineMode("asterisk", function() {
} else if(state.extenPriority) {
state.extenPriority = false;
state.extenApplication = true;
- ch = stream.next(); // get comma
+ stream.next(); // get comma
if(state.extenSame) return null;
stream.eatWhile(/[^,]/);
return "number";
@@ -189,7 +207,11 @@ CodeMirror.defineMode("asterisk", function() {
}
return null;
- }
+ },
+
+ blockCommentStart: ";--",
+ blockCommentEnd: "--;",
+ lineComment: ";"
};
});
diff --git a/static/editor.md/lib/codemirror/mode/asterisk/index.html b/static/editor.md/lib/codemirror/mode/asterisk/index.html
index 257bd3987..de43e3448 100644
--- a/static/editor.md/lib/codemirror/mode/asterisk/index.html
+++ b/static/editor.md/lib/codemirror/mode/asterisk/index.html
@@ -6,13 +6,14 @@
+
-
CodeMirror
+
CodeMirror
Home
@@ -145,7 +146,7 @@ Asterisk dialplan mode
var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
mode: "text/x-asterisk",
matchBrackets: true,
- lineNumber: true
+ lineNumbers: true
});
diff --git a/static/editor.md/lib/codemirror/mode/brainfuck/brainfuck.js b/static/editor.md/lib/codemirror/mode/brainfuck/brainfuck.js
new file mode 100644
index 000000000..af6d889ae
--- /dev/null
+++ b/static/editor.md/lib/codemirror/mode/brainfuck/brainfuck.js
@@ -0,0 +1,85 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: https://codemirror.net/LICENSE
+
+// Brainfuck mode created by Michael Kaminsky https://github.com/mkaminsky11
+
+(function(mod) {
+ if (typeof exports == "object" && typeof module == "object")
+ mod(require("../../lib/codemirror"))
+ else if (typeof define == "function" && define.amd)
+ define(["../../lib/codemirror"], mod)
+ else
+ mod(CodeMirror)
+})(function(CodeMirror) {
+ "use strict"
+ var reserve = "><+-.,[]".split("");
+ /*
+ comments can be either:
+ placed behind lines
+
+ +++ this is a comment
+
+ where reserved characters cannot be used
+ or in a loop
+ [
+ this is ok to use [ ] and stuff
+ ]
+ or preceded by #
+ */
+ CodeMirror.defineMode("brainfuck", function() {
+ return {
+ startState: function() {
+ return {
+ commentLine: false,
+ left: 0,
+ right: 0,
+ commentLoop: false
+ }
+ },
+ token: function(stream, state) {
+ if (stream.eatSpace()) return null
+ if(stream.sol()){
+ state.commentLine = false;
+ }
+ var ch = stream.next().toString();
+ if(reserve.indexOf(ch) !== -1){
+ if(state.commentLine === true){
+ if(stream.eol()){
+ state.commentLine = false;
+ }
+ return "comment";
+ }
+ if(ch === "]" || ch === "["){
+ if(ch === "["){
+ state.left++;
+ }
+ else{
+ state.right++;
+ }
+ return "bracket";
+ }
+ else if(ch === "+" || ch === "-"){
+ return "keyword";
+ }
+ else if(ch === "<" || ch === ">"){
+ return "atom";
+ }
+ else if(ch === "." || ch === ","){
+ return "def";
+ }
+ }
+ else{
+ state.commentLine = true;
+ if(stream.eol()){
+ state.commentLine = false;
+ }
+ return "comment";
+ }
+ if(stream.eol()){
+ state.commentLine = false;
+ }
+ }
+ };
+ });
+CodeMirror.defineMIME("text/x-brainfuck","brainfuck")
+});
diff --git a/static/editor.md/lib/codemirror/mode/brainfuck/index.html b/static/editor.md/lib/codemirror/mode/brainfuck/index.html
new file mode 100644
index 000000000..7849e5968
--- /dev/null
+++ b/static/editor.md/lib/codemirror/mode/brainfuck/index.html
@@ -0,0 +1,85 @@
+
+
+CodeMirror: Brainfuck mode
+
+
+
+
+
+
+
+
+
+
+
+Brainfuck mode
+
+[ This program prints "Hello World!" and a newline to the screen, its
+ length is 106 active command characters [it is not the shortest.]
+
+ This loop is a "comment loop", it's a simple way of adding a comment
+ to a BF program such that you don't have to worry about any command
+ characters. Any ".", ",", "+", "-", "<" and ">" characters are simply
+ ignored, the "[" and "]" characters just have to be balanced.
+]
++++++ +++ Set Cell #0 to 8
+[
+ >++++ Add 4 to Cell #1; this will always set Cell #1 to 4
+ [ as the cell will be cleared by the loop
+ >++ Add 2 to Cell #2
+ >+++ Add 3 to Cell #3
+ >+++ Add 3 to Cell #4
+ >+ Add 1 to Cell #5
+ <<<<- Decrement the loop counter in Cell #1
+ ] Loop till Cell #1 is zero; number of iterations is 4
+ >+ Add 1 to Cell #2
+ >+ Add 1 to Cell #3
+ >- Subtract 1 from Cell #4
+ >>+ Add 1 to Cell #6
+ [<] Move back to the first zero cell you find; this will
+ be Cell #1 which was cleared by the previous loop
+ <- Decrement the loop Counter in Cell #0
+] Loop till Cell #0 is zero; number of iterations is 8
+
+The result of this is:
+Cell No : 0 1 2 3 4 5 6
+Contents: 0 0 72 104 88 32 8
+Pointer : ^
+
+>>. Cell #2 has value 72 which is 'H'
+>---. Subtract 3 from Cell #3 to get 101 which is 'e'
++++++++..+++. Likewise for 'llo' from Cell #3
+>>. Cell #5 is 32 for the space
+<-. Subtract 1 from Cell #4 for 87 to give a 'W'
+<. Cell #3 was set to 'o' from the end of 'Hello'
++++.------.--------. Cell #3 for 'rl' and 'd'
+>>+. Add 1 to Cell #5 gives us an exclamation point
+>++. And finally a newline from Cell #6
+
+
+
+
+ A mode for Brainfuck
+
+ MIME types defined: text/x-brainfuck
+
diff --git a/static/editor.md/lib/codemirror/mode/clike/clike.js b/static/editor.md/lib/codemirror/mode/clike/clike.js
index e2223ccd1..916aee619 100644
--- a/static/editor.md/lib/codemirror/mode/clike/clike.js
+++ b/static/editor.md/lib/codemirror/mode/clike/clike.js
@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
@@ -11,20 +11,66 @@
})(function(CodeMirror) {
"use strict";
+function Context(indented, column, type, info, align, prev) {
+ this.indented = indented;
+ this.column = column;
+ this.type = type;
+ this.info = info;
+ this.align = align;
+ this.prev = prev;
+}
+function pushContext(state, col, type, info) {
+ var indent = state.indented;
+ if (state.context && state.context.type == "statement" && type != "statement")
+ indent = state.context.indented;
+ return state.context = new Context(indent, col, type, info, null, state.context);
+}
+function popContext(state) {
+ var t = state.context.type;
+ if (t == ")" || t == "]" || t == "}")
+ state.indented = state.context.indented;
+ return state.context = state.context.prev;
+}
+
+function typeBefore(stream, state, pos) {
+ if (state.prevToken == "variable" || state.prevToken == "type") return true;
+ if (/\S(?:[^- ]>|[*\]])\s*$|\*$/.test(stream.string.slice(0, pos))) return true;
+ if (state.typeAtEndOfLine && stream.column() == stream.indentation()) return true;
+}
+
+function isTopScope(context) {
+ for (;;) {
+ if (!context || context.type == "top") return true;
+ if (context.type == "}" && context.prev.info != "namespace") return false;
+ context = context.prev;
+ }
+}
+
CodeMirror.defineMode("clike", function(config, parserConfig) {
var indentUnit = config.indentUnit,
statementIndentUnit = parserConfig.statementIndentUnit || indentUnit,
dontAlignCalls = parserConfig.dontAlignCalls,
keywords = parserConfig.keywords || {},
+ types = parserConfig.types || {},
builtin = parserConfig.builtin || {},
blockKeywords = parserConfig.blockKeywords || {},
+ defKeywords = parserConfig.defKeywords || {},
atoms = parserConfig.atoms || {},
hooks = parserConfig.hooks || {},
multiLineStrings = parserConfig.multiLineStrings,
- indentStatements = parserConfig.indentStatements !== false;
- var isOperatorChar = /[+\-*&%=<>!?|\/]/;
-
- var curPunc;
+ indentStatements = parserConfig.indentStatements !== false,
+ indentSwitch = parserConfig.indentSwitch !== false,
+ namespaceSeparator = parserConfig.namespaceSeparator,
+ isPunctuationChar = parserConfig.isPunctuationChar || /[\[\]{}\(\),;\:\.]/,
+ numberStart = parserConfig.numberStart || /[\d\.]/,
+ number = parserConfig.number || /^(?:0x[a-f\d]+|0b[01]+|(?:\d+\.?\d*|\.\d+)(?:e[-+]?\d+)?)(u|ll?|l|f)?/i,
+ isOperatorChar = parserConfig.isOperatorChar || /[+\-*&%=<>!?|\/]/,
+ isIdentifierChar = parserConfig.isIdentifierChar || /[\w\$_\xa1-\uffff]/,
+ // An optional function that takes a {string} token and returns true if it
+ // should be treated as a builtin.
+ isReservedIdentifier = parserConfig.isReservedIdentifier || false;
+
+ var curPunc, isDefKeyword;
function tokenBase(stream, state) {
var ch = stream.next();
@@ -36,14 +82,15 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
state.tokenize = tokenString(ch);
return state.tokenize(stream, state);
}
- if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
+ if (numberStart.test(ch)) {
+ stream.backUp(1)
+ if (stream.match(number)) return "number"
+ stream.next()
+ }
+ if (isPunctuationChar.test(ch)) {
curPunc = ch;
return null;
}
- if (/\d/.test(ch)) {
- stream.eatWhile(/[\w\.]/);
- return "number";
- }
if (ch == "/") {
if (stream.eat("*")) {
state.tokenize = tokenComment;
@@ -55,20 +102,26 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
}
}
if (isOperatorChar.test(ch)) {
- stream.eatWhile(isOperatorChar);
+ while (!stream.match(/^\/[\/*]/, false) && stream.eat(isOperatorChar)) {}
return "operator";
}
- stream.eatWhile(/[\w\$_\xa1-\uffff]/);
+ stream.eatWhile(isIdentifierChar);
+ if (namespaceSeparator) while (stream.match(namespaceSeparator))
+ stream.eatWhile(isIdentifierChar);
+
var cur = stream.current();
- if (keywords.propertyIsEnumerable(cur)) {
- if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement";
+ if (contains(keywords, cur)) {
+ if (contains(blockKeywords, cur)) curPunc = "newstatement";
+ if (contains(defKeywords, cur)) isDefKeyword = true;
return "keyword";
}
- if (builtin.propertyIsEnumerable(cur)) {
- if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement";
+ if (contains(types, cur)) return "type";
+ if (contains(builtin, cur)
+ || (isReservedIdentifier && isReservedIdentifier(cur))) {
+ if (contains(blockKeywords, cur)) curPunc = "newstatement";
return "builtin";
}
- if (atoms.propertyIsEnumerable(cur)) return "atom";
+ if (contains(atoms, cur)) return "atom";
return "variable";
}
@@ -97,24 +150,9 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
return "comment";
}
- function Context(indented, column, type, align, prev) {
- this.indented = indented;
- this.column = column;
- this.type = type;
- this.align = align;
- this.prev = prev;
- }
- function pushContext(state, col, type) {
- var indent = state.indented;
- if (state.context && state.context.type == "statement")
- indent = state.context.indented;
- return state.context = new Context(indent, col, type, null, state.context);
- }
- function popContext(state) {
- var t = state.context.type;
- if (t == ")" || t == "]" || t == "}")
- state.indented = state.context.indented;
- return state.context = state.context.prev;
+ function maybeEOL(stream, state) {
+ if (parserConfig.typeFirstDefinitions && stream.eol() && isTopScope(state.context))
+ state.typeAtEndOfLine = typeBefore(stream, state, stream.pos)
}
// Interface
@@ -123,9 +161,10 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
startState: function(basecolumn) {
return {
tokenize: null,
- context: new Context((basecolumn || 0) - indentUnit, 0, "top", false),
+ context: new Context((basecolumn || 0) - indentUnit, 0, "top", null, false),
indented: 0,
- startOfLine: true
+ startOfLine: true,
+ prevToken: null
};
},
@@ -136,13 +175,14 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
state.indented = stream.indentation();
state.startOfLine = true;
}
- if (stream.eatSpace()) return null;
- curPunc = null;
+ if (stream.eatSpace()) { maybeEOL(stream, state); return null; }
+ curPunc = isDefKeyword = null;
var style = (state.tokenize || tokenBase)(stream, state);
if (style == "comment" || style == "meta") return style;
if (ctx.align == null) ctx.align = true;
- if ((curPunc == ";" || curPunc == ":" || curPunc == ",") && ctx.type == "statement") popContext(state);
+ if (curPunc == ";" || curPunc == ":" || (curPunc == "," && stream.match(/^\s*(?:\/\/.*)?$/, false)))
+ while (state.context.type == "statement") popContext(state);
else if (curPunc == "{") pushContext(state, stream.column(), "}");
else if (curPunc == "[") pushContext(state, stream.column(), "]");
else if (curPunc == "(") pushContext(state, stream.column(), ")");
@@ -153,27 +193,62 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
}
else if (curPunc == ctx.type) popContext(state);
else if (indentStatements &&
- (((ctx.type == "}" || ctx.type == "top") && curPunc != ';') ||
- (ctx.type == "statement" && curPunc == "newstatement")))
- pushContext(state, stream.column(), "statement");
+ (((ctx.type == "}" || ctx.type == "top") && curPunc != ";") ||
+ (ctx.type == "statement" && curPunc == "newstatement"))) {
+ pushContext(state, stream.column(), "statement", stream.current());
+ }
+
+ if (style == "variable" &&
+ ((state.prevToken == "def" ||
+ (parserConfig.typeFirstDefinitions && typeBefore(stream, state, stream.start) &&
+ isTopScope(state.context) && stream.match(/^\s*\(/, false)))))
+ style = "def";
+
+ if (hooks.token) {
+ var result = hooks.token(stream, state, style);
+ if (result !== undefined) style = result;
+ }
+
+ if (style == "def" && parserConfig.styleDefs === false) style = "variable";
+
state.startOfLine = false;
+ state.prevToken = isDefKeyword ? "def" : style || curPunc;
+ maybeEOL(stream, state);
return style;
},
indent: function(state, textAfter) {
- if (state.tokenize != tokenBase && state.tokenize != null) return CodeMirror.Pass;
+ if (state.tokenize != tokenBase && state.tokenize != null || state.typeAtEndOfLine) return CodeMirror.Pass;
var ctx = state.context, firstChar = textAfter && textAfter.charAt(0);
- if (ctx.type == "statement" && firstChar == "}") ctx = ctx.prev;
var closing = firstChar == ctx.type;
- if (ctx.type == "statement") return ctx.indented + (firstChar == "{" ? 0 : statementIndentUnit);
- else if (ctx.align && (!dontAlignCalls || ctx.type != ")")) return ctx.column + (closing ? 0 : 1);
- else if (ctx.type == ")" && !closing) return ctx.indented + statementIndentUnit;
- else return ctx.indented + (closing ? 0 : indentUnit);
+ if (ctx.type == "statement" && firstChar == "}") ctx = ctx.prev;
+ if (parserConfig.dontIndentStatements)
+ while (ctx.type == "statement" && parserConfig.dontIndentStatements.test(ctx.info))
+ ctx = ctx.prev
+ if (hooks.indent) {
+ var hook = hooks.indent(state, ctx, textAfter, indentUnit);
+ if (typeof hook == "number") return hook
+ }
+ var switchBlock = ctx.prev && ctx.prev.info == "switch";
+ if (parserConfig.allmanIndentation && /[{(]/.test(firstChar)) {
+ while (ctx.type != "top" && ctx.type != "}") ctx = ctx.prev
+ return ctx.indented
+ }
+ if (ctx.type == "statement")
+ return ctx.indented + (firstChar == "{" ? 0 : statementIndentUnit);
+ if (ctx.align && (!dontAlignCalls || ctx.type != ")"))
+ return ctx.column + (closing ? 0 : 1);
+ if (ctx.type == ")" && !closing)
+ return ctx.indented + statementIndentUnit;
+
+ return ctx.indented + (closing ? 0 : indentUnit) +
+ (!closing && switchBlock && !/^(?:case|default)\b/.test(textAfter) ? indentUnit : 0);
},
- electricChars: "{}",
+ electricInput: indentSwitch ? /^\s*(?:case .*?:|default:|\{\}?|\})$/ : /^\s*[{}]$/,
blockCommentStart: "/*",
blockCommentEnd: "*/",
+ blockCommentContinue: " * ",
lineComment: "//",
fold: "brace"
};
@@ -184,33 +259,99 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
return obj;
}
- var cKeywords = "auto if break int case long char register continue return default short do sizeof " +
- "double static else struct entry switch extern typedef float union for unsigned " +
- "goto while enum void const signed volatile";
+ function contains(words, word) {
+ if (typeof words === "function") {
+ return words(word);
+ } else {
+ return words.propertyIsEnumerable(word);
+ }
+ }
+ var cKeywords = "auto if break case register continue return default do sizeof " +
+ "static else struct switch extern typedef union for goto while enum const " +
+ "volatile inline restrict asm fortran";
+
+ // Keywords from https://en.cppreference.com/w/cpp/keyword includes C++20.
+ var cppKeywords = "alignas alignof and and_eq audit axiom bitand bitor catch " +
+ "class compl concept constexpr const_cast decltype delete dynamic_cast " +
+ "explicit export final friend import module mutable namespace new noexcept " +
+ "not not_eq operator or or_eq override private protected public " +
+ "reinterpret_cast requires static_assert static_cast template this " +
+ "thread_local throw try typeid typename using virtual xor xor_eq";
+
+ var objCKeywords = "bycopy byref in inout oneway out self super atomic nonatomic retain copy " +
+ "readwrite readonly strong weak assign typeof nullable nonnull null_resettable _cmd " +
+ "@interface @implementation @end @protocol @encode @property @synthesize @dynamic @class " +
+ "@public @package @private @protected @required @optional @try @catch @finally @import " +
+ "@selector @encode @defs @synchronized @autoreleasepool @compatibility_alias @available";
+
+ var objCBuiltins = "FOUNDATION_EXPORT FOUNDATION_EXTERN NS_INLINE NS_FORMAT_FUNCTION " +
+ " NS_RETURNS_RETAINEDNS_ERROR_ENUM NS_RETURNS_NOT_RETAINED NS_RETURNS_INNER_POINTER " +
+ "NS_DESIGNATED_INITIALIZER NS_ENUM NS_OPTIONS NS_REQUIRES_NIL_TERMINATION " +
+ "NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_END NS_SWIFT_NAME NS_REFINED_FOR_SWIFT"
+
+ // Do not use this. Use the cTypes function below. This is global just to avoid
+ // excessive calls when cTypes is being called multiple times during a parse.
+ var basicCTypes = words("int long char short double float unsigned signed " +
+ "void bool");
+
+ // Do not use this. Use the objCTypes function below. This is global just to avoid
+ // excessive calls when objCTypes is being called multiple times during a parse.
+ var basicObjCTypes = words("SEL instancetype id Class Protocol BOOL");
+
+ // Returns true if identifier is a "C" type.
+ // C type is defined as those that are reserved by the compiler (basicTypes),
+ // and those that end in _t (Reserved by POSIX for types)
+ // http://www.gnu.org/software/libc/manual/html_node/Reserved-Names.html
+ function cTypes(identifier) {
+ return contains(basicCTypes, identifier) || /.+_t$/.test(identifier);
+ }
+
+ // Returns true if identifier is a "Objective C" type.
+ function objCTypes(identifier) {
+ return cTypes(identifier) || contains(basicObjCTypes, identifier);
+ }
+
+ var cBlockKeywords = "case do else for if switch while struct enum union";
+ var cDefKeywords = "struct enum union";
function cppHook(stream, state) {
- if (!state.startOfLine) return false;
- for (;;) {
- if (stream.skipTo("\\")) {
- stream.next();
- if (stream.eol()) {
- state.tokenize = cppHook;
- break;
- }
- } else {
- stream.skipToEnd();
- state.tokenize = null;
- break;
+ if (!state.startOfLine) return false
+ for (var ch, next = null; ch = stream.peek();) {
+ if (ch == "\\" && stream.match(/^.$/)) {
+ next = cppHook
+ break
+ } else if (ch == "/" && stream.match(/^\/[\/\*]/, false)) {
+ break
}
+ stream.next()
}
- return "meta";
+ state.tokenize = next
+ return "meta"
+ }
+
+ function pointerHook(_stream, state) {
+ if (state.prevToken == "type") return "type";
+ return false;
+ }
+
+ // For C and C++ (and ObjC): identifiers starting with __
+ // or _ followed by a capital letter are reserved for the compiler.
+ function cIsReservedIdentifier(token) {
+ if (!token || token.length < 2) return false;
+ if (token[0] != '_') return false;
+ return (token[1] == '_') || (token[1] !== token[1].toLowerCase());
+ }
+
+ function cpp14Literal(stream) {
+ stream.eatWhile(/[\w\.']/);
+ return "number";
}
function cpp11StringHook(stream, state) {
stream.backUp(1);
// Raw strings.
- if (stream.match(/(R|u8R|uR|UR|LR)/)) {
- var match = stream.match(/"([^\s\\()]{0,16})\(/);
+ if (stream.match(/^(?:R|u8R|uR|UR|LR)/)) {
+ var match = stream.match(/^"([^\s\\()]{0,16})\(/);
if (!match) {
return false;
}
@@ -219,8 +360,8 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
return tokenRawString(stream, state);
}
// Unicode strings/chars.
- if (stream.match(/(u8|u|U|L)/)) {
- if (stream.match(/["']/, /* eat */ false)) {
+ if (stream.match(/^(?:u8|u|U|L)/)) {
+ if (stream.match(/^["']/, /* eat */ false)) {
return "string";
}
return false;
@@ -230,6 +371,11 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
return false;
}
+ function cppLooksLikeConstructor(word) {
+ var lastTwo = /(\w+)::~?(\w+)$/.exec(word);
+ return lastTwo && lastTwo[1] == lastTwo[2];
+ }
+
// C#-style strings where "" escapes a quote.
function tokenAtString(stream, state) {
var next;
@@ -263,6 +409,7 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
words.push(prop);
}
add(mode.keywords);
+ add(mode.types);
add(mode.builtin);
add(mode.atoms);
if (words.length) {
@@ -277,42 +424,78 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
def(["text/x-csrc", "text/x-c", "text/x-chdr"], {
name: "clike",
keywords: words(cKeywords),
- blockKeywords: words("case do else for if switch while struct"),
- atoms: words("null"),
- hooks: {"#": cppHook},
+ types: cTypes,
+ blockKeywords: words(cBlockKeywords),
+ defKeywords: words(cDefKeywords),
+ typeFirstDefinitions: true,
+ atoms: words("NULL true false"),
+ isReservedIdentifier: cIsReservedIdentifier,
+ hooks: {
+ "#": cppHook,
+ "*": pointerHook,
+ },
modeProps: {fold: ["brace", "include"]}
});
def(["text/x-c++src", "text/x-c++hdr"], {
name: "clike",
- keywords: words(cKeywords + " asm dynamic_cast namespace reinterpret_cast try bool explicit new " +
- "static_cast typeid catch operator template typename class friend private " +
- "this using const_cast inline public throw virtual delete mutable protected " +
- "wchar_t alignas alignof constexpr decltype nullptr noexcept thread_local final " +
- "static_assert override"),
- blockKeywords: words("catch class do else finally for if struct switch try while"),
- atoms: words("true false null"),
+ keywords: words(cKeywords + " " + cppKeywords),
+ types: cTypes,
+ blockKeywords: words(cBlockKeywords + " class try catch"),
+ defKeywords: words(cDefKeywords + " class namespace"),
+ typeFirstDefinitions: true,
+ atoms: words("true false NULL nullptr"),
+ dontIndentStatements: /^template$/,
+ isIdentifierChar: /[\w\$_~\xa1-\uffff]/,
+ isReservedIdentifier: cIsReservedIdentifier,
hooks: {
"#": cppHook,
+ "*": pointerHook,
"u": cpp11StringHook,
"U": cpp11StringHook,
"L": cpp11StringHook,
- "R": cpp11StringHook
+ "R": cpp11StringHook,
+ "0": cpp14Literal,
+ "1": cpp14Literal,
+ "2": cpp14Literal,
+ "3": cpp14Literal,
+ "4": cpp14Literal,
+ "5": cpp14Literal,
+ "6": cpp14Literal,
+ "7": cpp14Literal,
+ "8": cpp14Literal,
+ "9": cpp14Literal,
+ token: function(stream, state, style) {
+ if (style == "variable" && stream.peek() == "(" &&
+ (state.prevToken == ";" || state.prevToken == null ||
+ state.prevToken == "}") &&
+ cppLooksLikeConstructor(stream.current()))
+ return "def";
+ }
},
+ namespaceSeparator: "::",
modeProps: {fold: ["brace", "include"]}
});
def("text/x-java", {
name: "clike",
- keywords: words("abstract assert boolean break byte case catch char class const continue default " +
- "do double else enum extends final finally float for goto if implements import " +
- "instanceof int interface long native new package private protected public " +
- "return short static strictfp super switch synchronized this throw throws transient " +
- "try void volatile while"),
+ keywords: words("abstract assert break case catch class const continue default " +
+ "do else enum extends final finally for goto if implements import " +
+ "instanceof interface native new package private protected public " +
+ "return static strictfp super switch synchronized this throw throws transient " +
+ "try volatile while @interface"),
+ types: words("var byte short int long float double boolean char void Boolean Byte Character Double Float " +
+ "Integer Long Number Object Short String StringBuffer StringBuilder Void"),
blockKeywords: words("catch class do else finally for if switch try while"),
+ defKeywords: words("class interface enum @interface"),
+ typeFirstDefinitions: true,
atoms: words("true false null"),
+ number: /^(?:0x[a-f\d_]+|0b[01_]+|(?:[\d_]+\.?\d*|\.\d+)(?:e[-+]?[\d_]+)?)(u|ll?|l|f)?/i,
hooks: {
"@": function(stream) {
+ // Don't match the @interface keyword.
+ if (stream.match('interface', false)) return false;
+
stream.eatWhile(/[\w\$_]/);
return "meta";
}
@@ -322,18 +505,20 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
def("text/x-csharp", {
name: "clike",
- keywords: words("abstract as base break case catch checked class const continue" +
+ keywords: words("abstract as async await base break case catch checked class const continue" +
" default delegate do else enum event explicit extern finally fixed for" +
" foreach goto if implicit in interface internal is lock namespace new" +
" operator out override params private protected public readonly ref return sealed" +
" sizeof stackalloc static struct switch this throw try typeof unchecked" +
" unsafe using virtual void volatile while add alias ascending descending dynamic from get" +
" global group into join let orderby partial remove select set value var yield"),
+ types: words("Action Boolean Byte Char DateTime DateTimeOffset Decimal Double Func" +
+ " Guid Int16 Int32 Int64 Object SByte Single String Task TimeSpan UInt16 UInt32" +
+ " UInt64 bool byte char decimal double short int long object" +
+ " sbyte float string ushort uint ulong"),
blockKeywords: words("catch class do else finally for foreach if struct switch try while"),
- builtin: words("Boolean Byte Char DateTime DateTimeOffset Decimal Double" +
- " Guid Int16 Int32 Int64 Object SByte Single String TimeSpan UInt16 UInt32" +
- " UInt64 bool byte char decimal double short int long object" +
- " sbyte float string ushort uint ulong"),
+ defKeywords: words("class interface namespace struct var"),
+ typeFirstDefinitions: true,
atoms: words("true false null"),
hooks: {
"@": function(stream, state) {
@@ -359,25 +544,45 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
return "string";
}
+ function tokenNestedComment(depth) {
+ return function (stream, state) {
+ var ch
+ while (ch = stream.next()) {
+ if (ch == "*" && stream.eat("/")) {
+ if (depth == 1) {
+ state.tokenize = null
+ break
+ } else {
+ state.tokenize = tokenNestedComment(depth - 1)
+ return state.tokenize(stream, state)
+ }
+ } else if (ch == "/" && stream.eat("*")) {
+ state.tokenize = tokenNestedComment(depth + 1)
+ return state.tokenize(stream, state)
+ }
+ }
+ return "comment"
+ }
+ }
+
def("text/x-scala", {
name: "clike",
keywords: words(
-
/* scala */
- "abstract case catch class def do else extends false final finally for forSome if " +
+ "abstract case catch class def do else extends final finally for forSome if " +
"implicit import lazy match new null object override package private protected return " +
- "sealed super this throw trait try trye type val var while with yield _ : = => <- <: " +
- "<% >: # @ " +
+ "sealed super this throw trait try type val var while with yield _ " +
/* package scala */
"assert assume require print println printf readLine readBoolean readByte readShort " +
- "readChar readInt readLong readFloat readDouble " +
-
+ "readChar readInt readLong readFloat readDouble"
+ ),
+ types: words(
"AnyVal App Application Array BufferedIterator BigDecimal BigInt Char Console Either " +
- "Enumeration Equiv Error Exception Fractional Function IndexedSeq Integral Iterable " +
+ "Enumeration Equiv Error Exception Fractional Function IndexedSeq Int Integral Iterable " +
"Iterator List Map Numeric Nil NotNull Option Ordered Ordering PartialFunction PartialOrdering " +
"Product Proxy Range Responder Seq Serializable Set Specializable Stream StringBuilder " +
- "StringContext Symbol Throwable Traversable TraversableOnce Tuple Unit Vector :: #:: " +
+ "StringContext Symbol Throwable Traversable TraversableOnce Tuple Unit Vector " +
/* package java.lang */
"Boolean Byte Character CharSequence Class ClassLoader Cloneable Comparable " +
@@ -386,9 +591,12 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
"StringBuffer System Thread ThreadGroup ThreadLocal Throwable Triple Void"
),
multiLineStrings: true,
- blockKeywords: words("catch class do else finally for forSome if match switch try while"),
+ blockKeywords: words("catch class enum do else finally for forSome if match switch try while"),
+ defKeywords: words("class enum def object package trait type val var"),
atoms: words("true false null"),
indentStatements: false,
+ indentSwitch: false,
+ isOperatorChar: /[+\-*&%=<>!?|\/#:@]/,
hooks: {
"@": function(stream) {
stream.eatWhile(/[\w\$_]/);
@@ -402,21 +610,116 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
"'": function(stream) {
stream.eatWhile(/[\w\$_\xa1-\uffff]/);
return "atom";
+ },
+ "=": function(stream, state) {
+ var cx = state.context
+ if (cx.type == "}" && cx.align && stream.eat(">")) {
+ state.context = new Context(cx.indented, cx.column, cx.type, cx.info, null, cx.prev)
+ return "operator"
+ } else {
+ return false
+ }
+ },
+
+ "/": function(stream, state) {
+ if (!stream.eat("*")) return false
+ state.tokenize = tokenNestedComment(1)
+ return state.tokenize(stream, state)
+ }
+ },
+ modeProps: {closeBrackets: {pairs: '()[]{}""', triples: '"'}}
+ });
+
+ function tokenKotlinString(tripleString){
+ return function (stream, state) {
+ var escaped = false, next, end = false;
+ while (!stream.eol()) {
+ if (!tripleString && !escaped && stream.match('"') ) {end = true; break;}
+ if (tripleString && stream.match('"""')) {end = true; break;}
+ next = stream.next();
+ if(!escaped && next == "$" && stream.match('{'))
+ stream.skipTo("}");
+ escaped = !escaped && next == "\\" && !tripleString;
}
+ if (end || !tripleString)
+ state.tokenize = null;
+ return "string";
}
+ }
+
+ def("text/x-kotlin", {
+ name: "clike",
+ keywords: words(
+ /*keywords*/
+ "package as typealias class interface this super val operator " +
+ "var fun for is in This throw return annotation " +
+ "break continue object if else while do try when !in !is as? " +
+
+ /*soft keywords*/
+ "file import where by get set abstract enum open inner override private public internal " +
+ "protected catch finally out final vararg reified dynamic companion constructor init " +
+ "sealed field property receiver param sparam lateinit data inline noinline tailrec " +
+ "external annotation crossinline const operator infix suspend actual expect setparam value"
+ ),
+ types: words(
+ /* package java.lang */
+ "Boolean Byte Character CharSequence Class ClassLoader Cloneable Comparable " +
+ "Compiler Double Exception Float Integer Long Math Number Object Package Pair Process " +
+ "Runtime Runnable SecurityManager Short StackTraceElement StrictMath String " +
+ "StringBuffer System Thread ThreadGroup ThreadLocal Throwable Triple Void Annotation Any BooleanArray " +
+ "ByteArray Char CharArray DeprecationLevel DoubleArray Enum FloatArray Function Int IntArray Lazy " +
+ "LazyThreadSafetyMode LongArray Nothing ShortArray Unit"
+ ),
+ intendSwitch: false,
+ indentStatements: false,
+ multiLineStrings: true,
+ number: /^(?:0x[a-f\d_]+|0b[01_]+|(?:[\d_]+(\.\d+)?|\.\d+)(?:e[-+]?[\d_]+)?)(u|ll?|l|f)?/i,
+ blockKeywords: words("catch class do else finally for if where try while enum"),
+ defKeywords: words("class val var object interface fun"),
+ atoms: words("true false null this"),
+ hooks: {
+ "@": function(stream) {
+ stream.eatWhile(/[\w\$_]/);
+ return "meta";
+ },
+ '*': function(_stream, state) {
+ return state.prevToken == '.' ? 'variable' : 'operator';
+ },
+ '"': function(stream, state) {
+ state.tokenize = tokenKotlinString(stream.match('""'));
+ return state.tokenize(stream, state);
+ },
+ "/": function(stream, state) {
+ if (!stream.eat("*")) return false;
+ state.tokenize = tokenNestedComment(1);
+ return state.tokenize(stream, state)
+ },
+ indent: function(state, ctx, textAfter, indentUnit) {
+ var firstChar = textAfter && textAfter.charAt(0);
+ if ((state.prevToken == "}" || state.prevToken == ")") && textAfter == "")
+ return state.indented;
+ if ((state.prevToken == "operator" && textAfter != "}" && state.context.type != "}") ||
+ state.prevToken == "variable" && firstChar == "." ||
+ (state.prevToken == "}" || state.prevToken == ")") && firstChar == ".")
+ return indentUnit * 2 + ctx.indented;
+ if (ctx.align && ctx.type == "}")
+ return ctx.indented + (state.context.type == (textAfter || "").charAt(0) ? 0 : indentUnit);
+ }
+ },
+ modeProps: {closeBrackets: {triples: '"'}}
});
def(["x-shader/x-vertex", "x-shader/x-fragment"], {
name: "clike",
- keywords: words("float int bool void " +
- "vec2 vec3 vec4 ivec2 ivec3 ivec4 bvec2 bvec3 bvec4 " +
- "mat2 mat3 mat4 " +
- "sampler1D sampler2D sampler3D samplerCube " +
+ keywords: words("sampler1D sampler2D sampler3D samplerCube " +
"sampler1DShadow sampler2DShadow " +
"const attribute uniform varying " +
"break continue discard return " +
"for while do if else struct " +
"in out inout"),
+ types: words("float int bool void " +
+ "vec2 vec3 vec4 ivec2 ivec3 ivec4 bvec2 bvec3 bvec4 " +
+ "mat2 mat3 mat4"),
blockKeywords: words("for while do if else struct"),
builtin: words("radians degrees sin cos tan asin acos atan " +
"pow exp log exp2 sqrt inversesqrt " +
@@ -446,7 +749,7 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
"gl_ModelViewMatrix gl_ProjectionMatrix gl_ModelViewProjectionMatrix " +
"gl_TextureMatrix gl_NormalMatrix gl_ModelViewMatrixInverse " +
"gl_ProjectionMatrixInverse gl_ModelViewProjectionMatrixInverse " +
- "gl_TexureMatrixTranspose gl_ModelViewMatrixInverseTranspose " +
+ "gl_TextureMatrixTranspose gl_ModelViewMatrixInverseTranspose " +
"gl_ProjectionMatrixInverseTranspose " +
"gl_ModelViewProjectionMatrixInverseTranspose " +
"gl_TextureMatrixInverseTranspose " +
@@ -460,34 +763,173 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
"gl_MaxVertexTextureImageUnits gl_MaxTextureImageUnits " +
"gl_MaxFragmentUniformComponents gl_MaxCombineTextureImageUnits " +
"gl_MaxDrawBuffers"),
+ indentSwitch: false,
hooks: {"#": cppHook},
modeProps: {fold: ["brace", "include"]}
});
def("text/x-nesc", {
name: "clike",
- keywords: words(cKeywords + "as atomic async call command component components configuration event generic " +
+ keywords: words(cKeywords + " as atomic async call command component components configuration event generic " +
"implementation includes interface module new norace nx_struct nx_union post provides " +
"signal task uses abstract extends"),
- blockKeywords: words("case do else for if switch while struct"),
- atoms: words("null"),
+ types: cTypes,
+ blockKeywords: words(cBlockKeywords),
+ atoms: words("null true false"),
hooks: {"#": cppHook},
modeProps: {fold: ["brace", "include"]}
});
def("text/x-objectivec", {
name: "clike",
- keywords: words(cKeywords + "inline restrict _Bool _Complex _Imaginery BOOL Class bycopy byref id IMP in " +
- "inout nil oneway out Protocol SEL self super atomic nonatomic retain copy readwrite readonly"),
- atoms: words("YES NO NULL NILL ON OFF"),
+ keywords: words(cKeywords + " " + objCKeywords),
+ types: objCTypes,
+ builtin: words(objCBuiltins),
+ blockKeywords: words(cBlockKeywords + " @synthesize @try @catch @finally @autoreleasepool @synchronized"),
+ defKeywords: words(cDefKeywords + " @interface @implementation @protocol @class"),
+ dontIndentStatements: /^@.*$/,
+ typeFirstDefinitions: true,
+ atoms: words("YES NO NULL Nil nil true false nullptr"),
+ isReservedIdentifier: cIsReservedIdentifier,
+ hooks: {
+ "#": cppHook,
+ "*": pointerHook,
+ },
+ modeProps: {fold: ["brace", "include"]}
+ });
+
+ def("text/x-objectivec++", {
+ name: "clike",
+ keywords: words(cKeywords + " " + objCKeywords + " " + cppKeywords),
+ types: objCTypes,
+ builtin: words(objCBuiltins),
+ blockKeywords: words(cBlockKeywords + " @synthesize @try @catch @finally @autoreleasepool @synchronized class try catch"),
+ defKeywords: words(cDefKeywords + " @interface @implementation @protocol @class class namespace"),
+ dontIndentStatements: /^@.*$|^template$/,
+ typeFirstDefinitions: true,
+ atoms: words("YES NO NULL Nil nil true false nullptr"),
+ isReservedIdentifier: cIsReservedIdentifier,
+ hooks: {
+ "#": cppHook,
+ "*": pointerHook,
+ "u": cpp11StringHook,
+ "U": cpp11StringHook,
+ "L": cpp11StringHook,
+ "R": cpp11StringHook,
+ "0": cpp14Literal,
+ "1": cpp14Literal,
+ "2": cpp14Literal,
+ "3": cpp14Literal,
+ "4": cpp14Literal,
+ "5": cpp14Literal,
+ "6": cpp14Literal,
+ "7": cpp14Literal,
+ "8": cpp14Literal,
+ "9": cpp14Literal,
+ token: function(stream, state, style) {
+ if (style == "variable" && stream.peek() == "(" &&
+ (state.prevToken == ";" || state.prevToken == null ||
+ state.prevToken == "}") &&
+ cppLooksLikeConstructor(stream.current()))
+ return "def";
+ }
+ },
+ namespaceSeparator: "::",
+ modeProps: {fold: ["brace", "include"]}
+ });
+
+ def("text/x-squirrel", {
+ name: "clike",
+ keywords: words("base break clone continue const default delete enum extends function in class" +
+ " foreach local resume return this throw typeof yield constructor instanceof static"),
+ types: cTypes,
+ blockKeywords: words("case catch class else for foreach if switch try while"),
+ defKeywords: words("function local class"),
+ typeFirstDefinitions: true,
+ atoms: words("true false null"),
+ hooks: {"#": cppHook},
+ modeProps: {fold: ["brace", "include"]}
+ });
+
+ // Ceylon Strings need to deal with interpolation
+ var stringTokenizer = null;
+ function tokenCeylonString(type) {
+ return function(stream, state) {
+ var escaped = false, next, end = false;
+ while (!stream.eol()) {
+ if (!escaped && stream.match('"') &&
+ (type == "single" || stream.match('""'))) {
+ end = true;
+ break;
+ }
+ if (!escaped && stream.match('``')) {
+ stringTokenizer = tokenCeylonString(type);
+ end = true;
+ break;
+ }
+ next = stream.next();
+ escaped = type == "single" && !escaped && next == "\\";
+ }
+ if (end)
+ state.tokenize = null;
+ return "string";
+ }
+ }
+
+ def("text/x-ceylon", {
+ name: "clike",
+ keywords: words("abstracts alias assembly assert assign break case catch class continue dynamic else" +
+ " exists extends finally for function given if import in interface is let module new" +
+ " nonempty object of out outer package return satisfies super switch then this throw" +
+ " try value void while"),
+ types: function(word) {
+ // In Ceylon all identifiers that start with an uppercase are types
+ var first = word.charAt(0);
+ return (first === first.toUpperCase() && first !== first.toLowerCase());
+ },
+ blockKeywords: words("case catch class dynamic else finally for function if interface module new object switch try while"),
+ defKeywords: words("class dynamic function interface module object package value"),
+ builtin: words("abstract actual aliased annotation by default deprecated doc final formal late license" +
+ " native optional sealed see serializable shared suppressWarnings tagged throws variable"),
+ isPunctuationChar: /[\[\]{}\(\),;\:\.`]/,
+ isOperatorChar: /[+\-*&%=<>!?|^~:\/]/,
+ numberStart: /[\d#$]/,
+ number: /^(?:#[\da-fA-F_]+|\$[01_]+|[\d_]+[kMGTPmunpf]?|[\d_]+\.[\d_]+(?:[eE][-+]?\d+|[kMGTPmunpf]|)|)/i,
+ multiLineStrings: true,
+ typeFirstDefinitions: true,
+ atoms: words("true false null larger smaller equal empty finished"),
+ indentSwitch: false,
+ styleDefs: false,
hooks: {
"@": function(stream) {
- stream.eatWhile(/[\w\$]/);
- return "keyword";
+ stream.eatWhile(/[\w\$_]/);
+ return "meta";
+ },
+ '"': function(stream, state) {
+ state.tokenize = tokenCeylonString(stream.match('""') ? "triple" : "single");
+ return state.tokenize(stream, state);
+ },
+ '`': function(stream, state) {
+ if (!stringTokenizer || !stream.match('`')) return false;
+ state.tokenize = stringTokenizer;
+ stringTokenizer = null;
+ return state.tokenize(stream, state);
+ },
+ "'": function(stream) {
+ stream.eatWhile(/[\w\$_\xa1-\uffff]/);
+ return "atom";
},
- "#": cppHook
+ token: function(_stream, state, style) {
+ if ((style == "variable" || style == "type") &&
+ state.prevToken == ".") {
+ return "variable-2";
+ }
+ }
},
- modeProps: {fold: "brace"}
+ modeProps: {
+ fold: ["brace", "import"],
+ closeBrackets: {triples: '"'}
+ }
});
});
diff --git a/static/editor.md/lib/codemirror/mode/clike/index.html b/static/editor.md/lib/codemirror/mode/clike/index.html
index 8b386d22e..b1c881904 100644
--- a/static/editor.md/lib/codemirror/mode/clike/index.html
+++ b/static/editor.md/lib/codemirror/mode/clike/index.html
@@ -12,7 +12,7 @@
-
CodeMirror
+
CodeMirror
Home
@@ -147,16 +147,36 @@ Objective-C example
That spans two lines
*/
-#import
+#import "MyClass.h"
+#import
+@import BFrameworkModule;
+
+NS_ENUM(SomeValues) {
+ aValue = 1;
+};
+
+// A Class Extension with some properties
+@interface MyClass ()
+@property(atomic, readwrite, assign) NSInteger anInt;
+@property(nonatomic, strong, nullable) NSString *aString;
+@end
+
@implementation YourAppDelegate
-// This is a one-line comment
+- (instancetype)initWithString:(NSString *)aStringVar {
+ if ((self = [super init])) {
+ aString = aStringVar;
+ }
+ return self;
+}
-- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
- char myString[] = "This is a C character array";
- int test = 5;
- return YES;
+- (BOOL)doSomething:(float)progress {
+ NSString *myString = @"This is a ObjC string %f ";
+ myString = [[NSString stringWithFormat:myString, progress] stringByAppendingString:self.aString];
+ return myString.length > 100 ? NO : YES;
}
+
+@end
Java example
@@ -206,6 +226,103 @@ Scala example
}
+Kotlin mode
+
+
+package org.wasabi.http
+
+import java.util.concurrent.Executors
+import java.net.InetSocketAddress
+import org.wasabi.app.AppConfiguration
+import io.netty.bootstrap.ServerBootstrap
+import io.netty.channel.nio.NioEventLoopGroup
+import io.netty.channel.socket.nio.NioServerSocketChannel
+import org.wasabi.app.AppServer
+
+public class HttpServer(private val appServer: AppServer) {
+
+ val bootstrap: ServerBootstrap
+ val primaryGroup: NioEventLoopGroup
+ val workerGroup: NioEventLoopGroup
+
+ init {
+ // Define worker groups
+ primaryGroup = NioEventLoopGroup()
+ workerGroup = NioEventLoopGroup()
+
+ // Initialize bootstrap of server
+ bootstrap = ServerBootstrap()
+
+ bootstrap.group(primaryGroup, workerGroup)
+ bootstrap.channel(javaClass())
+ bootstrap.childHandler(NettyPipelineInitializer(appServer))
+ }
+
+ public fun start(wait: Boolean = true) {
+ val channel = bootstrap.bind(appServer.configuration.port)?.sync()?.channel()
+
+ if (wait) {
+ channel?.closeFuture()?.sync()
+ }
+ }
+
+ public fun stop() {
+ // Shutdown all event loops
+ primaryGroup.shutdownGracefully()
+ workerGroup.shutdownGracefully()
+
+ // Wait till all threads are terminated
+ primaryGroup.terminationFuture().sync()
+ workerGroup.terminationFuture().sync()
+ }
+}
+
+
+Ceylon mode
+
+
+"Produces the [[stream|Iterable]] that results from repeated
+ application of the given [[function|next]] to the given
+ [[first]] element of the stream, until the function first
+ returns [[finished]]. If the given function never returns
+ `finished`, the resulting stream is infinite.
+
+ For example:
+
+ loop(0)(2.plus).takeWhile(10.largerThan)
+
+ produces the stream `{ 0, 2, 4, 6, 8 }`."
+tagged("Streams")
+shared {Element+} loop<Element>(
+ "The first element of the resulting stream."
+ Element first)(
+ "The function that produces the next element of the
+ stream, given the current element. The function may
+ return [[finished]] to indicate the end of the
+ stream."
+ Element|Finished next(Element element))
+ => let (start = first)
+ object satisfies {Element+} {
+ first => start;
+ empty => false;
+ function nextElement(Element element)
+ => next(element);
+ iterator()
+ => object satisfies Iterator<Element> {
+ variable Element|Finished current = start;
+ shared actual Element|Finished next() {
+ if (!is Finished result = current) {
+ current = nextElement(result);
+ return result;
+ }
+ else {
+ return finished;
+ }
+ }
+ };
+ };
+
+
@@ -247,5 +374,7 @@ Scala example
(Java), text/x-csharp (C#),
text/x-objectivec (Objective-C),
text/x-scala (Scala), text/x-vertex
- and x-shader/x-fragment (shader programs).
+ x-shader/x-fragment (shader programs),
+ text/x-squirrel (Squirrel) and
+ text/x-ceylon (Ceylon)
diff --git a/static/editor.md/lib/codemirror/mode/clike/scala.html b/static/editor.md/lib/codemirror/mode/clike/scala.html
index aa04cf0f0..3d2123c01 100644
--- a/static/editor.md/lib/codemirror/mode/clike/scala.html
+++ b/static/editor.md/lib/codemirror/mode/clike/scala.html
@@ -10,7 +10,7 @@
-
CodeMirror
+
CodeMirror
Home
diff --git a/static/editor.md/lib/codemirror/mode/clike/test.js b/static/editor.md/lib/codemirror/mode/clike/test.js
new file mode 100644
index 000000000..9441b9595
--- /dev/null
+++ b/static/editor.md/lib/codemirror/mode/clike/test.js
@@ -0,0 +1,165 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: https://codemirror.net/LICENSE
+
+(function() {
+ var mode = CodeMirror.getMode({indentUnit: 2}, "text/x-c");
+ function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); }
+
+ MT("indent",
+ "[type void] [def foo]([type void*] [variable a], [type int] [variable b]) {",
+ " [type int] [variable c] [operator =] [variable b] [operator +]",
+ " [number 1];",
+ " [keyword return] [operator *][variable a];",
+ "}");
+
+ MT("indent_switch",
+ "[keyword switch] ([variable x]) {",
+ " [keyword case] [number 10]:",
+ " [keyword return] [number 20];",
+ " [keyword default]:",
+ " [variable printf]([string \"foo %c\"], [variable x]);",
+ "}");
+
+ MT("def",
+ "[type void] [def foo]() {}",
+ "[keyword struct] [def bar]{}",
+ "[keyword enum] [def zot]{}",
+ "[keyword union] [def ugh]{}",
+ "[type int] [type *][def baz]() {}");
+
+ MT("def_new_line",
+ "::[variable std]::[variable SomeTerribleType][operator <][variable T][operator >]",
+ "[def SomeLongMethodNameThatDoesntFitIntoOneLine]([keyword const] [variable MyType][operator &] [variable param]) {}")
+
+ MT("double_block",
+ "[keyword for] (;;)",
+ " [keyword for] (;;)",
+ " [variable x][operator ++];",
+ "[keyword return];");
+
+ MT("preprocessor",
+ "[meta #define FOO 3]",
+ "[type int] [variable foo];",
+ "[meta #define BAR\\]",
+ "[meta 4]",
+ "[type unsigned] [type int] [variable bar] [operator =] [number 8];",
+ "[meta #include ][comment // comment]")
+
+ MT("c_underscores",
+ "[builtin __FOO];",
+ "[builtin _Complex];",
+ "[builtin __aName];",
+ "[variable _aName];");
+
+ MT("c_types",
+ "[type int];",
+ "[type long];",
+ "[type char];",
+ "[type short];",
+ "[type double];",
+ "[type float];",
+ "[type unsigned];",
+ "[type signed];",
+ "[type void];",
+ "[type bool];",
+ "[type foo_t];",
+ "[variable foo_T];",
+ "[variable _t];");
+
+ var mode_cpp = CodeMirror.getMode({indentUnit: 2}, "text/x-c++src");
+ function MTCPP(name) { test.mode(name, mode_cpp, Array.prototype.slice.call(arguments, 1)); }
+
+ MTCPP("cpp14_literal",
+ "[number 10'000];",
+ "[number 0b10'000];",
+ "[number 0x10'000];",
+ "[string '100000'];");
+
+ MTCPP("ctor_dtor",
+ "[def Foo::Foo]() {}",
+ "[def Foo::~Foo]() {}");
+
+ MTCPP("cpp_underscores",
+ "[builtin __FOO];",
+ "[builtin _Complex];",
+ "[builtin __aName];",
+ "[variable _aName];");
+
+ var mode_objc = CodeMirror.getMode({indentUnit: 2}, "text/x-objectivec");
+ function MTOBJC(name) { test.mode(name, mode_objc, Array.prototype.slice.call(arguments, 1)); }
+
+ MTOBJC("objc_underscores",
+ "[builtin __FOO];",
+ "[builtin _Complex];",
+ "[builtin __aName];",
+ "[variable _aName];");
+
+ MTOBJC("objc_interface",
+ "[keyword @interface] [def foo] {",
+ " [type int] [variable bar];",
+ "}",
+ "[keyword @property] ([keyword atomic], [keyword nullable]) [variable NSString][operator *] [variable a];",
+ "[keyword @property] ([keyword nonatomic], [keyword assign]) [type int] [variable b];",
+ "[operator -]([type instancetype])[variable initWithFoo]:([type int])[variable a] " +
+ "[builtin NS_DESIGNATED_INITIALIZER];",
+ "[keyword @end]");
+
+ MTOBJC("objc_implementation",
+ "[keyword @implementation] [def foo] {",
+ " [type int] [variable bar];",
+ "}",
+ "[keyword @property] ([keyword readwrite]) [type SEL] [variable a];",
+ "[operator -]([type instancetype])[variable initWithFoo]:([type int])[variable a] {",
+ " [keyword if](([keyword self] [operator =] [[[keyword super] [variable init] ]])) {}",
+ " [keyword return] [keyword self];",
+ "}",
+ "[keyword @end]");
+
+ MTOBJC("objc_types",
+ "[type int];",
+ "[type foo_t];",
+ "[variable foo_T];",
+ "[type id];",
+ "[type SEL];",
+ "[type instancetype];",
+ "[type Class];",
+ "[type Protocol];",
+ "[type BOOL];"
+ );
+
+ var mode_scala = CodeMirror.getMode({indentUnit: 2}, "text/x-scala");
+ function MTSCALA(name) { test.mode("scala_" + name, mode_scala, Array.prototype.slice.call(arguments, 1)); }
+ MTSCALA("nested_comments",
+ "[comment /*]",
+ "[comment But wait /* this is a nested comment */ for real]",
+ "[comment /**** let * me * show * you ****/]",
+ "[comment ///// let / me / show / you /////]",
+ "[comment */]");
+
+ var mode_java = CodeMirror.getMode({indentUnit: 2}, "text/x-java");
+ function MTJAVA(name) { test.mode("java_" + name, mode_java, Array.prototype.slice.call(arguments, 1)); }
+ MTJAVA("types",
+ "[type byte];",
+ "[type short];",
+ "[type int];",
+ "[type long];",
+ "[type float];",
+ "[type double];",
+ "[type boolean];",
+ "[type char];",
+ "[type void];",
+ "[type Boolean];",
+ "[type Byte];",
+ "[type Character];",
+ "[type Double];",
+ "[type Float];",
+ "[type Integer];",
+ "[type Long];",
+ "[type Number];",
+ "[type Object];",
+ "[type Short];",
+ "[type String];",
+ "[type StringBuffer];",
+ "[type StringBuilder];",
+ "[type Void];");
+})();
diff --git a/static/editor.md/lib/codemirror/mode/clojure/clojure.js b/static/editor.md/lib/codemirror/mode/clojure/clojure.js
index c334de730..0b9d6acc3 100644
--- a/static/editor.md/lib/codemirror/mode/clojure/clojure.js
+++ b/static/editor.md/lib/codemirror/mode/clojure/clojure.js
@@ -1,15 +1,10 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-/**
- * Author: Hans Engel
- * Branched from CodeMirror's Scheme mode (by Koh Zi Han, based on implementation by Koh Zi Chun)
- */
+// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
- if (typeof exports == "object" && typeof module == "object") // CommonJS
+ if (typeof exports === "object" && typeof module === "object") // CommonJS
mod(require("../../lib/codemirror"));
- else if (typeof define == "function" && define.amd) // AMD
+ else if (typeof define === "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
@@ -17,227 +12,281 @@
"use strict";
CodeMirror.defineMode("clojure", function (options) {
- var BUILTIN = "builtin", COMMENT = "comment", STRING = "string", CHARACTER = "string-2",
- ATOM = "atom", NUMBER = "number", BRACKET = "bracket", KEYWORD = "keyword", VAR = "variable";
- var INDENT_WORD_SKIP = options.indentUnit || 2;
- var NORMAL_INDENT_UNIT = options.indentUnit || 2;
-
- function makeKeywords(str) {
- var obj = {}, words = str.split(" ");
- for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
- return obj;
+ var atoms = ["false", "nil", "true"];
+ var specialForms = [".", "catch", "def", "do", "if", "monitor-enter",
+ "monitor-exit", "new", "quote", "recur", "set!", "throw", "try", "var"];
+ var coreSymbols = ["*", "*'", "*1", "*2", "*3", "*agent*",
+ "*allow-unresolved-vars*", "*assert*", "*clojure-version*",
+ "*command-line-args*", "*compile-files*", "*compile-path*",
+ "*compiler-options*", "*data-readers*", "*default-data-reader-fn*", "*e",
+ "*err*", "*file*", "*flush-on-newline*", "*fn-loader*", "*in*",
+ "*math-context*", "*ns*", "*out*", "*print-dup*", "*print-length*",
+ "*print-level*", "*print-meta*", "*print-namespace-maps*",
+ "*print-readably*", "*read-eval*", "*reader-resolver*", "*source-path*",
+ "*suppress-read*", "*unchecked-math*", "*use-context-classloader*",
+ "*verbose-defrecords*", "*warn-on-reflection*", "+", "+'", "-", "-'",
+ "->", "->>", "->ArrayChunk", "->Eduction", "->Vec", "->VecNode",
+ "->VecSeq", "-cache-protocol-fn", "-reset-methods", "..", "/", "<", "<=",
+ "=", "==", ">", ">=", "EMPTY-NODE", "Inst", "StackTraceElement->vec",
+ "Throwable->map", "accessor", "aclone", "add-classpath", "add-watch",
+ "agent", "agent-error", "agent-errors", "aget", "alength", "alias",
+ "all-ns", "alter", "alter-meta!", "alter-var-root", "amap", "ancestors",
+ "and", "any?", "apply", "areduce", "array-map", "as->", "aset",
+ "aset-boolean", "aset-byte", "aset-char", "aset-double", "aset-float",
+ "aset-int", "aset-long", "aset-short", "assert", "assoc", "assoc!",
+ "assoc-in", "associative?", "atom", "await", "await-for", "await1",
+ "bases", "bean", "bigdec", "bigint", "biginteger", "binding", "bit-and",
+ "bit-and-not", "bit-clear", "bit-flip", "bit-not", "bit-or", "bit-set",
+ "bit-shift-left", "bit-shift-right", "bit-test", "bit-xor", "boolean",
+ "boolean-array", "boolean?", "booleans", "bound-fn", "bound-fn*",
+ "bound?", "bounded-count", "butlast", "byte", "byte-array", "bytes",
+ "bytes?", "case", "cast", "cat", "char", "char-array",
+ "char-escape-string", "char-name-string", "char?", "chars", "chunk",
+ "chunk-append", "chunk-buffer", "chunk-cons", "chunk-first", "chunk-next",
+ "chunk-rest", "chunked-seq?", "class", "class?", "clear-agent-errors",
+ "clojure-version", "coll?", "comment", "commute", "comp", "comparator",
+ "compare", "compare-and-set!", "compile", "complement", "completing",
+ "concat", "cond", "cond->", "cond->>", "condp", "conj", "conj!", "cons",
+ "constantly", "construct-proxy", "contains?", "count", "counted?",
+ "create-ns", "create-struct", "cycle", "dec", "dec'", "decimal?",
+ "declare", "dedupe", "default-data-readers", "definline", "definterface",
+ "defmacro", "defmethod", "defmulti", "defn", "defn-", "defonce",
+ "defprotocol", "defrecord", "defstruct", "deftype", "delay", "delay?",
+ "deliver", "denominator", "deref", "derive", "descendants", "destructure",
+ "disj", "disj!", "dissoc", "dissoc!", "distinct", "distinct?", "doall",
+ "dorun", "doseq", "dosync", "dotimes", "doto", "double", "double-array",
+ "double?", "doubles", "drop", "drop-last", "drop-while", "eduction",
+ "empty", "empty?", "ensure", "ensure-reduced", "enumeration-seq",
+ "error-handler", "error-mode", "eval", "even?", "every-pred", "every?",
+ "ex-data", "ex-info", "extend", "extend-protocol", "extend-type",
+ "extenders", "extends?", "false?", "ffirst", "file-seq", "filter",
+ "filterv", "find", "find-keyword", "find-ns", "find-protocol-impl",
+ "find-protocol-method", "find-var", "first", "flatten", "float",
+ "float-array", "float?", "floats", "flush", "fn", "fn?", "fnext", "fnil",
+ "for", "force", "format", "frequencies", "future", "future-call",
+ "future-cancel", "future-cancelled?", "future-done?", "future?",
+ "gen-class", "gen-interface", "gensym", "get", "get-in", "get-method",
+ "get-proxy-class", "get-thread-bindings", "get-validator", "group-by",
+ "halt-when", "hash", "hash-combine", "hash-map", "hash-ordered-coll",
+ "hash-set", "hash-unordered-coll", "ident?", "identical?", "identity",
+ "if-let", "if-not", "if-some", "ifn?", "import", "in-ns", "inc", "inc'",
+ "indexed?", "init-proxy", "inst-ms", "inst-ms*", "inst?", "instance?",
+ "int", "int-array", "int?", "integer?", "interleave", "intern",
+ "interpose", "into", "into-array", "ints", "io!", "isa?", "iterate",
+ "iterator-seq", "juxt", "keep", "keep-indexed", "key", "keys", "keyword",
+ "keyword?", "last", "lazy-cat", "lazy-seq", "let", "letfn", "line-seq",
+ "list", "list*", "list?", "load", "load-file", "load-reader",
+ "load-string", "loaded-libs", "locking", "long", "long-array", "longs",
+ "loop", "macroexpand", "macroexpand-1", "make-array", "make-hierarchy",
+ "map", "map-entry?", "map-indexed", "map?", "mapcat", "mapv", "max",
+ "max-key", "memfn", "memoize", "merge", "merge-with", "meta",
+ "method-sig", "methods", "min", "min-key", "mix-collection-hash", "mod",
+ "munge", "name", "namespace", "namespace-munge", "nat-int?", "neg-int?",
+ "neg?", "newline", "next", "nfirst", "nil?", "nnext", "not", "not-any?",
+ "not-empty", "not-every?", "not=", "ns", "ns-aliases", "ns-imports",
+ "ns-interns", "ns-map", "ns-name", "ns-publics", "ns-refers",
+ "ns-resolve", "ns-unalias", "ns-unmap", "nth", "nthnext", "nthrest",
+ "num", "number?", "numerator", "object-array", "odd?", "or", "parents",
+ "partial", "partition", "partition-all", "partition-by", "pcalls", "peek",
+ "persistent!", "pmap", "pop", "pop!", "pop-thread-bindings", "pos-int?",
+ "pos?", "pr", "pr-str", "prefer-method", "prefers",
+ "primitives-classnames", "print", "print-ctor", "print-dup",
+ "print-method", "print-simple", "print-str", "printf", "println",
+ "println-str", "prn", "prn-str", "promise", "proxy",
+ "proxy-call-with-super", "proxy-mappings", "proxy-name", "proxy-super",
+ "push-thread-bindings", "pvalues", "qualified-ident?",
+ "qualified-keyword?", "qualified-symbol?", "quot", "rand", "rand-int",
+ "rand-nth", "random-sample", "range", "ratio?", "rational?",
+ "rationalize", "re-find", "re-groups", "re-matcher", "re-matches",
+ "re-pattern", "re-seq", "read", "read-line", "read-string",
+ "reader-conditional", "reader-conditional?", "realized?", "record?",
+ "reduce", "reduce-kv", "reduced", "reduced?", "reductions", "ref",
+ "ref-history-count", "ref-max-history", "ref-min-history", "ref-set",
+ "refer", "refer-clojure", "reify", "release-pending-sends", "rem",
+ "remove", "remove-all-methods", "remove-method", "remove-ns",
+ "remove-watch", "repeat", "repeatedly", "replace", "replicate", "require",
+ "reset!", "reset-meta!", "reset-vals!", "resolve", "rest",
+ "restart-agent", "resultset-seq", "reverse", "reversible?", "rseq",
+ "rsubseq", "run!", "satisfies?", "second", "select-keys", "send",
+ "send-off", "send-via", "seq", "seq?", "seqable?", "seque", "sequence",
+ "sequential?", "set", "set-agent-send-executor!",
+ "set-agent-send-off-executor!", "set-error-handler!", "set-error-mode!",
+ "set-validator!", "set?", "short", "short-array", "shorts", "shuffle",
+ "shutdown-agents", "simple-ident?", "simple-keyword?", "simple-symbol?",
+ "slurp", "some", "some->", "some->>", "some-fn", "some?", "sort",
+ "sort-by", "sorted-map", "sorted-map-by", "sorted-set", "sorted-set-by",
+ "sorted?", "special-symbol?", "spit", "split-at", "split-with", "str",
+ "string?", "struct", "struct-map", "subs", "subseq", "subvec", "supers",
+ "swap!", "swap-vals!", "symbol", "symbol?", "sync", "tagged-literal",
+ "tagged-literal?", "take", "take-last", "take-nth", "take-while", "test",
+ "the-ns", "thread-bound?", "time", "to-array", "to-array-2d",
+ "trampoline", "transduce", "transient", "tree-seq", "true?", "type",
+ "unchecked-add", "unchecked-add-int", "unchecked-byte", "unchecked-char",
+ "unchecked-dec", "unchecked-dec-int", "unchecked-divide-int",
+ "unchecked-double", "unchecked-float", "unchecked-inc",
+ "unchecked-inc-int", "unchecked-int", "unchecked-long",
+ "unchecked-multiply", "unchecked-multiply-int", "unchecked-negate",
+ "unchecked-negate-int", "unchecked-remainder-int", "unchecked-short",
+ "unchecked-subtract", "unchecked-subtract-int", "underive", "unquote",
+ "unquote-splicing", "unreduced", "unsigned-bit-shift-right", "update",
+ "update-in", "update-proxy", "uri?", "use", "uuid?", "val", "vals",
+ "var-get", "var-set", "var?", "vary-meta", "vec", "vector", "vector-of",
+ "vector?", "volatile!", "volatile?", "vreset!", "vswap!", "when",
+ "when-first", "when-let", "when-not", "when-some", "while",
+ "with-bindings", "with-bindings*", "with-in-str", "with-loading-context",
+ "with-local-vars", "with-meta", "with-open", "with-out-str",
+ "with-precision", "with-redefs", "with-redefs-fn", "xml-seq", "zero?",
+ "zipmap"];
+ var haveBodyParameter = [
+ "->", "->>", "as->", "binding", "bound-fn", "case", "catch", "comment",
+ "cond", "cond->", "cond->>", "condp", "def", "definterface", "defmethod",
+ "defn", "defmacro", "defprotocol", "defrecord", "defstruct", "deftype",
+ "do", "doseq", "dotimes", "doto", "extend", "extend-protocol",
+ "extend-type", "fn", "for", "future", "if", "if-let", "if-not", "if-some",
+ "let", "letfn", "locking", "loop", "ns", "proxy", "reify", "struct-map",
+ "some->", "some->>", "try", "when", "when-first", "when-let", "when-not",
+ "when-some", "while", "with-bindings", "with-bindings*", "with-in-str",
+ "with-loading-context", "with-local-vars", "with-meta", "with-open",
+ "with-out-str", "with-precision", "with-redefs", "with-redefs-fn"];
+
+ CodeMirror.registerHelper("hintWords", "clojure",
+ [].concat(atoms, specialForms, coreSymbols));
+
+ var atom = createLookupMap(atoms);
+ var specialForm = createLookupMap(specialForms);
+ var coreSymbol = createLookupMap(coreSymbols);
+ var hasBodyParameter = createLookupMap(haveBodyParameter);
+ var delimiter = /^(?:[\\\[\]\s"(),;@^`{}~]|$)/;
+ var numberLiteral = /^(?:[+\-]?\d+(?:(?:N|(?:[eE][+\-]?\d+))|(?:\.?\d*(?:M|(?:[eE][+\-]?\d+))?)|\/\d+|[xX][0-9a-fA-F]+|r[0-9a-zA-Z]+)?(?=[\\\[\]\s"#'(),;@^`{}~]|$))/;
+ var characterLiteral = /^(?:\\(?:backspace|formfeed|newline|return|space|tab|o[0-7]{3}|u[0-9A-Fa-f]{4}|x[0-9A-Fa-f]{4}|.)?(?=[\\\[\]\s"(),;@^`{}~]|$))/;
+
+ // simple-namespace := /^[^\\\/\[\]\d\s"#'(),;@^`{}~.][^\\\[\]\s"(),;@^`{}~.\/]*/
+ // simple-symbol := /^(?:\/|[^\\\/\[\]\d\s"#'(),;@^`{}~][^\\\[\]\s"(),;@^`{}~]*)/
+ // qualified-symbol := ((<.>)*>)?
+ var qualifiedSymbol = /^(?:(?:[^\\\/\[\]\d\s"#'(),;@^`{}~.][^\\\[\]\s"(),;@^`{}~.\/]*(?:\.[^\\\/\[\]\d\s"#'(),;@^`{}~.][^\\\[\]\s"(),;@^`{}~.\/]*)*\/)?(?:\/|[^\\\/\[\]\d\s"#'(),;@^`{}~][^\\\[\]\s"(),;@^`{}~]*)*(?=[\\\[\]\s"(),;@^`{}~]|$))/;
+
+ function base(stream, state) {
+ if (stream.eatSpace() || stream.eat(",")) return ["space", null];
+ if (stream.match(numberLiteral)) return [null, "number"];
+ if (stream.match(characterLiteral)) return [null, "string-2"];
+ if (stream.eat(/^"/)) return (state.tokenize = inString)(stream, state);
+ if (stream.eat(/^[(\[{]/)) return ["open", "bracket"];
+ if (stream.eat(/^[)\]}]/)) return ["close", "bracket"];
+ if (stream.eat(/^;/)) {stream.skipToEnd(); return ["space", "comment"];}
+ if (stream.eat(/^[#'@^`~]/)) return [null, "meta"];
+
+ var matches = stream.match(qualifiedSymbol);
+ var symbol = matches && matches[0];
+
+ if (!symbol) {
+ // advance stream by at least one character so we don't get stuck.
+ stream.next();
+ stream.eatWhile(function (c) {return !is(c, delimiter);});
+ return [null, "error"];
}
- var atoms = makeKeywords("true false nil");
-
- var keywords = makeKeywords(
- "defn defn- def def- defonce defmulti defmethod defmacro defstruct deftype defprotocol defrecord defproject deftest slice defalias defhinted defmacro- defn-memo defnk defnk defonce- defunbound defunbound- defvar defvar- let letfn do case cond condp for loop recur when when-not when-let when-first if if-let if-not . .. -> ->> doto and or dosync doseq dotimes dorun doall load import unimport ns in-ns refer try catch finally throw with-open with-local-vars binding gen-class gen-and-load-class gen-and-save-class handler-case handle");
-
- var builtins = makeKeywords(
- "* *' *1 *2 *3 *agent* *allow-unresolved-vars* *assert* *clojure-version* *command-line-args* *compile-files* *compile-path* *compiler-options* *data-readers* *e *err* *file* *flush-on-newline* *fn-loader* *in* *math-context* *ns* *out* *print-dup* *print-length* *print-level* *print-meta* *print-readably* *read-eval* *source-path* *unchecked-math* *use-context-classloader* *verbose-defrecords* *warn-on-reflection* + +' - -' -> ->> ->ArrayChunk ->Vec ->VecNode ->VecSeq -cache-protocol-fn -reset-methods .. / < <= = == > >= EMPTY-NODE accessor aclone add-classpath add-watch agent agent-error agent-errors aget alength alias all-ns alter alter-meta! alter-var-root amap ancestors and apply areduce array-map aset aset-boolean aset-byte aset-char aset-double aset-float aset-int aset-long aset-short assert assoc assoc! assoc-in associative? atom await await-for await1 bases bean bigdec bigint biginteger binding bit-and bit-and-not bit-clear bit-flip bit-not bit-or bit-set bit-shift-left bit-shift-right bit-test bit-xor boolean boolean-array booleans bound-fn bound-fn* bound? butlast byte byte-array bytes case cast char char-array char-escape-string char-name-string char? chars chunk chunk-append chunk-buffer chunk-cons chunk-first chunk-next chunk-rest chunked-seq? class class? clear-agent-errors clojure-version coll? comment commute comp comparator compare compare-and-set! compile complement concat cond condp conj conj! cons constantly construct-proxy contains? count counted? create-ns create-struct cycle dec dec' decimal? declare default-data-readers definline definterface defmacro defmethod defmulti defn defn- defonce defprotocol defrecord defstruct deftype delay delay? deliver denominator deref derive descendants destructure disj disj! dissoc dissoc! distinct distinct? doall dorun doseq dosync dotimes doto double double-array doubles drop drop-last drop-while empty empty? ensure enumeration-seq error-handler error-mode eval even? every-pred every? ex-data ex-info extend extend-protocol extend-type extenders extends? false? ffirst file-seq filter filterv find find-keyword find-ns find-protocol-impl find-protocol-method find-var first flatten float float-array float? floats flush fn fn? fnext fnil for force format frequencies future future-call future-cancel future-cancelled? future-done? future? gen-class gen-interface gensym get get-in get-method get-proxy-class get-thread-bindings get-validator group-by hash hash-combine hash-map hash-set identical? identity if-let if-not ifn? import in-ns inc inc' init-proxy instance? int int-array integer? interleave intern interpose into into-array ints io! isa? iterate iterator-seq juxt keep keep-indexed key keys keyword keyword? last lazy-cat lazy-seq let letfn line-seq list list* list? load load-file load-reader load-string loaded-libs locking long long-array longs loop macroexpand macroexpand-1 make-array make-hierarchy map map-indexed map? mapcat mapv max max-key memfn memoize merge merge-with meta method-sig methods min min-key mod munge name namespace namespace-munge neg? newline next nfirst nil? nnext not not-any? not-empty not-every? not= ns ns-aliases ns-imports ns-interns ns-map ns-name ns-publics ns-refers ns-resolve ns-unalias ns-unmap nth nthnext nthrest num number? numerator object-array odd? or parents partial partition partition-all partition-by pcalls peek persistent! pmap pop pop! pop-thread-bindings pos? pr pr-str prefer-method prefers primitives-classnames print print-ctor print-dup print-method print-simple print-str printf println println-str prn prn-str promise proxy proxy-call-with-super proxy-mappings proxy-name proxy-super push-thread-bindings pvalues quot rand rand-int rand-nth range ratio? rational? rationalize re-find re-groups re-matcher re-matches re-pattern re-seq read read-line read-string realized? reduce reduce-kv reductions ref ref-history-count ref-max-history ref-min-history ref-set refer refer-clojure reify release-pending-sends rem remove remove-all-methods remove-method remove-ns remove-watch repeat repeatedly replace replicate require reset! reset-meta! resolve rest restart-agent resultset-seq reverse reversible? rseq rsubseq satisfies? second select-keys send send-off seq seq? seque sequence sequential? set set-error-handler! set-error-mode! set-validator! set? short short-array shorts shuffle shutdown-agents slurp some some-fn sort sort-by sorted-map sorted-map-by sorted-set sorted-set-by sorted? special-symbol? spit split-at split-with str string? struct struct-map subs subseq subvec supers swap! symbol symbol? sync take take-last take-nth take-while test the-ns thread-bound? time to-array to-array-2d trampoline transient tree-seq true? type unchecked-add unchecked-add-int unchecked-byte unchecked-char unchecked-dec unchecked-dec-int unchecked-divide-int unchecked-double unchecked-float unchecked-inc unchecked-inc-int unchecked-int unchecked-long unchecked-multiply unchecked-multiply-int unchecked-negate unchecked-negate-int unchecked-remainder-int unchecked-short unchecked-subtract unchecked-subtract-int underive unquote unquote-splicing update-in update-proxy use val vals var-get var-set var? vary-meta vec vector vector-of vector? when when-first when-let when-not while with-bindings with-bindings* with-in-str with-loading-context with-local-vars with-meta with-open with-out-str with-precision with-redefs with-redefs-fn xml-seq zero? zipmap *default-data-reader-fn* as-> cond-> cond->> reduced reduced? send-via set-agent-send-executor! set-agent-send-off-executor! some-> some->>");
-
- var indentKeys = makeKeywords(
- // Built-ins
- "ns fn def defn defmethod bound-fn if if-not case condp when while when-not when-first do future comment doto locking proxy with-open with-precision reify deftype defrecord defprotocol extend extend-protocol extend-type try catch " +
-
- // Binding forms
- "let letfn binding loop for doseq dotimes when-let if-let " +
+ if (symbol === "comment" && state.lastToken === "(")
+ return (state.tokenize = inComment)(stream, state);
+ if (is(symbol, atom) || symbol.charAt(0) === ":") return ["symbol", "atom"];
+ if (is(symbol, specialForm) || is(symbol, coreSymbol)) return ["symbol", "keyword"];
+ if (state.lastToken === "(") return ["symbol", "builtin"]; // other operator
- // Data structures
- "defstruct struct-map assoc " +
+ return ["symbol", "variable"];
+ }
- // clojure.test
- "testing deftest " +
-
- // contrib
- "handler-case handle dotrace deftrace");
-
- var tests = {
- digit: /\d/,
- digit_or_colon: /[\d:]/,
- hex: /[0-9a-f]/i,
- sign: /[+-]/,
- exponent: /e/i,
- keyword_char: /[^\s\(\[\;\)\]]/,
- symbol: /[\w*+!\-\._?:<>\/\xa1-\uffff]/
- };
-
- function stateStack(indent, type, prev) { // represents a state stack object
- this.indent = indent;
- this.type = type;
- this.prev = prev;
- }
+ function inString(stream, state) {
+ var escaped = false, next;
- function pushStack(state, indent, type) {
- state.indentStack = new stateStack(indent, type, state.indentStack);
+ while (next = stream.next()) {
+ if (next === "\"" && !escaped) {state.tokenize = base; break;}
+ escaped = !escaped && next === "\\";
}
- function popStack(state) {
- state.indentStack = state.indentStack.prev;
+ return [null, "string"];
+ }
+
+ function inComment(stream, state) {
+ var parenthesisCount = 1;
+ var next;
+
+ while (next = stream.next()) {
+ if (next === ")") parenthesisCount--;
+ if (next === "(") parenthesisCount++;
+ if (parenthesisCount === 0) {
+ stream.backUp(1);
+ state.tokenize = base;
+ break;
+ }
}
- function isNumber(ch, stream){
- // hex
- if ( ch === '0' && stream.eat(/x/i) ) {
- stream.eatWhile(tests.hex);
- return true;
+ return ["space", "comment"];
+ }
+
+ function createLookupMap(words) {
+ var obj = {};
+
+ for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
+
+ return obj;
+ }
+
+ function is(value, test) {
+ if (test instanceof RegExp) return test.test(value);
+ if (test instanceof Object) return test.propertyIsEnumerable(value);
+ }
+
+ return {
+ startState: function () {
+ return {
+ ctx: {prev: null, start: 0, indentTo: 0},
+ lastToken: null,
+ tokenize: base
+ };
+ },
+
+ token: function (stream, state) {
+ if (stream.sol() && (typeof state.ctx.indentTo !== "number"))
+ state.ctx.indentTo = state.ctx.start + 1;
+
+ var typeStylePair = state.tokenize(stream, state);
+ var type = typeStylePair[0];
+ var style = typeStylePair[1];
+ var current = stream.current();
+
+ if (type !== "space") {
+ if (state.lastToken === "(" && state.ctx.indentTo === null) {
+ if (type === "symbol" && is(current, hasBodyParameter))
+ state.ctx.indentTo = state.ctx.start + options.indentUnit;
+ else state.ctx.indentTo = "next";
+ } else if (state.ctx.indentTo === "next") {
+ state.ctx.indentTo = stream.column();
}
- // leading sign
- if ( ( ch == '+' || ch == '-' ) && ( tests.digit.test(stream.peek()) ) ) {
- stream.eat(tests.sign);
- ch = stream.next();
- }
-
- if ( tests.digit.test(ch) ) {
- stream.eat(ch);
- stream.eatWhile(tests.digit);
+ state.lastToken = current;
+ }
- if ( '.' == stream.peek() ) {
- stream.eat('.');
- stream.eatWhile(tests.digit);
- }
+ if (type === "open")
+ state.ctx = {prev: state.ctx, start: stream.column(), indentTo: null};
+ else if (type === "close") state.ctx = state.ctx.prev || state.ctx;
- if ( stream.eat(tests.exponent) ) {
- stream.eat(tests.sign);
- stream.eatWhile(tests.digit);
- }
-
- return true;
- }
+ return style;
+ },
- return false;
- }
+ indent: function (state) {
+ var i = state.ctx.indentTo;
- // Eat character that starts after backslash \
- function eatCharacter(stream) {
- var first = stream.next();
- // Read special literals: backspace, newline, space, return.
- // Just read all lowercase letters.
- if (first && first.match(/[a-z]/) && stream.match(/[a-z]+/, true)) {
- return;
- }
- // Read unicode character: \u1000 \uA0a1
- if (first === "u") {
- stream.match(/[0-9a-z]{4}/i, true);
- }
- }
+ return (typeof i === "number") ?
+ i :
+ state.ctx.start + 1;
+ },
- return {
- startState: function () {
- return {
- indentStack: null,
- indentation: 0,
- mode: false
- };
- },
-
- token: function (stream, state) {
- if (state.indentStack == null && stream.sol()) {
- // update indentation, but only if indentStack is empty
- state.indentation = stream.indentation();
- }
-
- // skip spaces
- if (stream.eatSpace()) {
- return null;
- }
- var returnType = null;
-
- switch(state.mode){
- case "string": // multi-line string parsing mode
- var next, escaped = false;
- while ((next = stream.next()) != null) {
- if (next == "\"" && !escaped) {
-
- state.mode = false;
- break;
- }
- escaped = !escaped && next == "\\";
- }
- returnType = STRING; // continue on in string mode
- break;
- default: // default parsing mode
- var ch = stream.next();
-
- if (ch == "\"") {
- state.mode = "string";
- returnType = STRING;
- } else if (ch == "\\") {
- eatCharacter(stream);
- returnType = CHARACTER;
- } else if (ch == "'" && !( tests.digit_or_colon.test(stream.peek()) )) {
- returnType = ATOM;
- } else if (ch == ";") { // comment
- stream.skipToEnd(); // rest of the line is a comment
- returnType = COMMENT;
- } else if (isNumber(ch,stream)){
- returnType = NUMBER;
- } else if (ch == "(" || ch == "[" || ch == "{" ) {
- var keyWord = '', indentTemp = stream.column(), letter;
- /**
- Either
- (indent-word ..
- (non-indent-word ..
- (;something else, bracket, etc.
- */
-
- if (ch == "(") while ((letter = stream.eat(tests.keyword_char)) != null) {
- keyWord += letter;
- }
-
- if (keyWord.length > 0 && (indentKeys.propertyIsEnumerable(keyWord) ||
- /^(?:def|with)/.test(keyWord))) { // indent-word
- pushStack(state, indentTemp + INDENT_WORD_SKIP, ch);
- } else { // non-indent word
- // we continue eating the spaces
- stream.eatSpace();
- if (stream.eol() || stream.peek() == ";") {
- // nothing significant after
- // we restart indentation the user defined spaces after
- pushStack(state, indentTemp + NORMAL_INDENT_UNIT, ch);
- } else {
- pushStack(state, indentTemp + stream.current().length, ch); // else we match
- }
- }
- stream.backUp(stream.current().length - 1); // undo all the eating
-
- returnType = BRACKET;
- } else if (ch == ")" || ch == "]" || ch == "}") {
- returnType = BRACKET;
- if (state.indentStack != null && state.indentStack.type == (ch == ")" ? "(" : (ch == "]" ? "[" :"{"))) {
- popStack(state);
- }
- } else if ( ch == ":" ) {
- stream.eatWhile(tests.symbol);
- return ATOM;
- } else {
- stream.eatWhile(tests.symbol);
-
- if (keywords && keywords.propertyIsEnumerable(stream.current())) {
- returnType = KEYWORD;
- } else if (builtins && builtins.propertyIsEnumerable(stream.current())) {
- returnType = BUILTIN;
- } else if (atoms && atoms.propertyIsEnumerable(stream.current())) {
- returnType = ATOM;
- } else {
- returnType = VAR;
- }
- }
- }
-
- return returnType;
- },
-
- indent: function (state) {
- if (state.indentStack == null) return state.indentation;
- return state.indentStack.indent;
- },
-
- lineComment: ";;"
- };
+ closeBrackets: {pairs: "()[]{}\"\""},
+ lineComment: ";;"
+ };
});
CodeMirror.defineMIME("text/x-clojure", "clojure");
+CodeMirror.defineMIME("text/x-clojurescript", "clojure");
+CodeMirror.defineMIME("application/edn", "clojure");
});
diff --git a/static/editor.md/lib/codemirror/mode/clojure/index.html b/static/editor.md/lib/codemirror/mode/clojure/index.html
index 3ecf4c486..d0634344a 100644
--- a/static/editor.md/lib/codemirror/mode/clojure/index.html
+++ b/static/editor.md/lib/codemirror/mode/clojure/index.html
@@ -6,10 +6,12 @@
+
+
-
CodeMirror
+
CodeMirror
Home
@@ -25,62 +27,67 @@
Clojure mode
-; Conway's Game of Life, based on the work of:
-;; Laurent Petit https://gist.github.com/1200343
-;; Christophe Grand http://clj-me.cgrand.net/2011/08/19/conways-game-of-life
+(ns game-of-life
+ "Conway's Game of Life, based on the work of
+ Christophe Grand (http://clj-me.cgrand.net/2011/08/19/conways-game-of-life)
+ and Laurent Petit (https://gist.github.com/1200343).")
-(ns ^{:doc "Conway's Game of Life."}
- game-of-life)
+;;; Core game of life's algorithm functions
-;; Core game of life's algorithm functions
-
-(defn neighbours
- "Given a cell's coordinates, returns the coordinates of its neighbours."
+(defn neighbors
+ "Given a cell's coordinates `[x y]`, returns the coordinates of its
+ neighbors."
[[x y]]
- (for [dx [-1 0 1] dy (if (zero? dx) [-1 1] [-1 0 1])]
+ (for [dx [-1 0 1]
+ dy (if (zero? dx)
+ [-1 1]
+ [-1 0 1])]
[(+ dx x) (+ dy y)]))
(defn step
- "Given a set of living cells, computes the new set of living cells."
+ "Given a set of living `cells`, computes the new set of living cells."
[cells]
- (set (for [[cell n] (frequencies (mapcat neighbours cells))
- :when (or (= n 3) (and (= n 2) (cells cell)))]
+ (set (for [[cell n] (frequencies (mapcat neighbors cells))
+ :when (or (= n 3)
+ (and (= n 2)
+ (cells cell)))]
cell)))
-;; Utility methods for displaying game on a text terminal
-
-(defn print-board
- "Prints a board on *out*, representing a step in the game."
- [board w h]
- (doseq [x (range (inc w)) y (range (inc h))]
- (if (= y 0) (print "\n"))
- (print (if (board [x y]) "[X]" " . "))))
-
-(defn display-grids
- "Prints a squence of boards on *out*, representing several steps."
+;;; Utility methods for displaying game on a text terminal
+
+(defn print-grid
+ "Prints a `grid` of `w` columns and `h` rows, on *out*, representing a
+ step in the game."
+ [grid w h]
+ (doseq [x (range (inc w))
+ y (range (inc h))]
+ (when (= y 0) (println))
+ (print (if (grid [x y])
+ "[X]"
+ " . "))))
+
+(defn print-grids
+ "Prints a sequence of `grids` of `w` columns and `h` rows on *out*,
+ representing several steps."
[grids w h]
- (doseq [board grids]
- (print-board board w h)
- (print "\n")))
-
-;; Launches an example board
-
-(def
- ^{:doc "board represents the initial set of living cells"}
- board #{[2 1] [2 2] [2 3]})
+ (doseq [grid grids]
+ (print-grid grid w h)
+ (println)))
-(display-grids (take 3 (iterate step board)) 5 5)
+;;; Launches an example grid
-;; Let's play with characters
-(println \1 \a \# \\
- \" \( \newline
- \} \" \space
- \tab \return \backspace
- \u1000 \uAaAa \u9F9F)
+(def grid
+ "`grid` represents the initial set of living cells"
+ #{[2 1] [2 2] [2 3]})
-
+(print-grids (take 3 (iterate step grid)) 5 5)
MIME types defined: text/x-clojure.
diff --git a/static/editor.md/lib/codemirror/mode/clojure/test.js b/static/editor.md/lib/codemirror/mode/clojure/test.js
new file mode 100644
index 000000000..642473b28
--- /dev/null
+++ b/static/editor.md/lib/codemirror/mode/clojure/test.js
@@ -0,0 +1,384 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: https://codemirror.net/LICENSE
+
+(function () {
+ var mode = CodeMirror.getMode({indentUnit: 2}, "clojure");
+
+ function MT(name) {
+ test.mode(name, mode, Array.prototype.slice.call(arguments, 1));
+ }
+
+ MT("atoms",
+ "[atom false]",
+ "[atom nil]",
+ "[atom true]"
+ );
+
+ MT("keywords",
+ "[atom :foo]",
+ "[atom ::bar]",
+ "[atom :foo/bar]",
+ "[atom :foo.bar/baz]"
+ );
+
+ MT("numbers",
+ "[number 42] [number +42] [number -421]",
+ "[number 42N] [number +42N] [number -42N]",
+ "[number 0.42] [number +0.42] [number -0.42]",
+ "[number 42M] [number +42M] [number -42M]",
+ "[number 42.42M] [number +42.42M] [number -42.42M]",
+ "[number 1/42] [number +1/42] [number -1/42]",
+ "[number 0x42af] [number +0x42af] [number -0x42af]",
+ "[number 0x42AF] [number +0x42AF] [number -0x42AF]",
+ "[number 1e2] [number 1e+2] [number 1e-2]",
+ "[number +1e2] [number +1e+2] [number +1e-2]",
+ "[number -1e2] [number -1e+2] [number -1e-2]",
+ "[number -1.0e2] [number -0.1e+2] [number -1.01e-2]",
+ "[number 1E2] [number 1E+2] [number 1E-2]",
+ "[number +1E2] [number +1E+2] [number +1E-2]",
+ "[number -1E2] [number -1E+2] [number -1E-2]",
+ "[number -1.0E2] [number -0.1E+2] [number -1.01E-2]",
+ "[number 2r101010] [number +2r101010] [number -2r101010]",
+ "[number 2r101010] [number +2r101010] [number -2r101010]",
+ "[number 8r52] [number +8r52] [number -8r52]",
+ "[number 36rhello] [number +36rhello] [number -36rhello]",
+ "[number 36rz] [number +36rz] [number -36rz]",
+ "[number 36rZ] [number +36rZ] [number -36rZ]",
+
+ // invalid numbers
+ "[error 42foo]",
+ "[error 42Nfoo]",
+ "[error 42Mfoo]",
+ "[error 42.42Mfoo]",
+ "[error 42.42M!]",
+ "[error 42!]",
+ "[error 0x42afm]"
+ );
+
+ MT("characters",
+ "[string-2 \\1]",
+ "[string-2 \\a]",
+ "[string-2 \\a\\b\\c]",
+ "[string-2 \\#]",
+ "[string-2 \\\\]",
+ "[string-2 \\\"]",
+ "[string-2 \\(]",
+ "[string-2 \\A]",
+ "[string-2 \\backspace]",
+ "[string-2 \\formfeed]",
+ "[string-2 \\newline]",
+ "[string-2 \\space]",
+ "[string-2 \\return]",
+ "[string-2 \\tab]",
+ "[string-2 \\u1000]",
+ "[string-2 \\uAaAa]",
+ "[string-2 \\u9F9F]",
+ "[string-2 \\o123]",
+ "[string-2 \\符]",
+ "[string-2 \\シ]",
+ "[string-2 \\ۇ]",
+ // FIXME
+ // "[string-2 \\🙂]",
+
+ // invalid character literals
+ "[error \\abc]",
+ "[error \\a123]",
+ "[error \\a!]",
+ "[error \\newlines]",
+ "[error \\NEWLINE]",
+ "[error \\u9F9FF]",
+ "[error \\o1234]"
+ );
+
+ MT("strings",
+ "[string \"I'm a teapot.\"]",
+ "[string \"I'm a \\\"teapot\\\".\"]",
+ "[string \"I'm]", // this is
+ "[string a]", // a multi-line
+ "[string teapot.\"]" // string
+
+ // TODO unterminated (multi-line) strings?
+ );
+
+ MT("comments",
+ "[comment ; this is an in-line comment.]",
+ "[comment ;; this is a line comment.]",
+ "[keyword comment]",
+ "[bracket (][comment comment (foo 1 2 3)][bracket )]"
+ );
+
+ MT("reader macro characters",
+ "[meta #][variable _]",
+ "[meta #][variable -Inf]",
+ "[meta ##][variable Inf]",
+ "[meta ##][variable NaN]",
+ "[meta @][variable x]",
+ "[meta ^][bracket {][atom :tag] [variable String][bracket }]",
+ "[meta `][bracket (][builtin f] [variable x][bracket )]",
+ "[meta ~][variable foo#]",
+ "[meta '][number 1]",
+ "[meta '][atom :foo]",
+ "[meta '][string \"foo\"]",
+ "[meta '][variable x]",
+ "[meta '][bracket (][builtin a] [variable b] [variable c][bracket )]",
+ "[meta '][bracket [[][variable a] [variable b] [variable c][bracket ]]]",
+ "[meta '][bracket {][variable a] [number 1] [atom :foo] [number 2] [variable c] [number 3][bracket }]",
+ "[meta '#][bracket {][variable a] [number 1] [atom :foo][bracket }]"
+ );
+
+ MT("symbols",
+ "[variable foo!]",
+ "[variable foo#]",
+ "[variable foo$]",
+ "[variable foo&]",
+ "[variable foo']",
+ "[variable foo*]",
+ "[variable foo+]",
+ "[variable foo-]",
+ "[variable foo.]",
+ "[variable foo/bar]",
+ "[variable foo:bar]",
+ "[variable foo<]",
+ "[variable foo=]",
+ "[variable foo>]",
+ "[variable foo?]",
+ "[variable foo_]",
+ "[variable foo|]",
+ "[variable foobarBaz]",
+ "[variable foo¡]",
+ "[variable 符号]",
+ "[variable シンボル]",
+ "[variable ئۇيغۇر]",
+ "[variable 🙂❤🇺🇸]",
+
+ // invalid symbols
+ "[error 3foo]",
+ "[error 3+]",
+ "[error 3|]",
+ "[error 3_]"
+ );
+
+ MT("numbers and other forms",
+ "[number 42][bracket (][builtin foo][bracket )]",
+ "[number 42][bracket [[][variable foo][bracket ]]]",
+ "[number 42][meta #][bracket {][variable foo][bracket }]",
+ "[number 42][bracket {][atom :foo] [variable bar][bracket }]",
+ "[number 42][meta `][variable foo]",
+ "[number 42][meta ~][variable foo]",
+ "[number 42][meta #][variable foo]"
+ );
+
+ var specialForms = [".", "catch", "def", "do", "if", "monitor-enter",
+ "monitor-exit", "new", "quote", "recur", "set!", "throw", "try", "var"];
+
+ MT("should highlight special forms as keywords",
+ typeTokenPairs("keyword", specialForms)
+ );
+
+ var coreSymbols1 = [
+ "*", "*'", "*1", "*2", "*3", "*agent*", "*allow-unresolved-vars*", "*assert*",
+ "*clojure-version*", "*command-line-args*", "*compile-files*", "*compile-path*", "*compiler-options*",
+ "*data-readers*", "*default-data-reader-fn*", "*e", "*err*", "*file*", "*flush-on-newline*", "*fn-loader*",
+ "*in*", "*math-context*", "*ns*", "*out*", "*print-dup*", "*print-length*", "*print-level*", "*print-meta*",
+ "*print-namespace-maps*", "*print-readably*", "*read-eval*", "*reader-resolver*", "*source-path*",
+ "*suppress-read*", "*unchecked-math*", "*use-context-classloader*", "*verbose-defrecords*",
+ "*warn-on-reflection*", "+", "+'", "-", "-'", "->", "->>", "->ArrayChunk", "->Eduction", "->Vec", "->VecNode",
+ "->VecSeq", "-cache-protocol-fn", "-reset-methods", "..", "/", "<", "<=", "=", "==", ">", ">=",
+ "EMPTY-NODE", "Inst", "StackTraceElement->vec", "Throwable->map", "accessor", "aclone", "add-classpath",
+ "add-watch", "agent", "agent-error", "agent-errors", "aget", "alength", "alias", "all-ns", "alter",
+ "alter-meta!", "alter-var-root", "amap", "ancestors", "and", "any?", "apply", "areduce", "array-map",
+ "as->", "aset", "aset-boolean", "aset-byte", "aset-char", "aset-double", "aset-float", "aset-int",
+ "aset-long", "aset-short", "assert", "assoc", "assoc!", "assoc-in", "associative?", "atom", "await",
+ "await-for", "await1", "bases", "bean", "bigdec", "bigint", "biginteger", "binding", "bit-and", "bit-and-not",
+ "bit-clear", "bit-flip", "bit-not", "bit-or", "bit-set", "bit-shift-left", "bit-shift-right", "bit-test",
+ "bit-xor", "boolean", "boolean-array", "boolean?", "booleans", "bound-fn", "bound-fn*", "bound?",
+ "bounded-count", "butlast", "byte", "byte-array", "bytes", "bytes?", "case", "cast", "cat", "char",
+ "char-array", "char-escape-string", "char-name-string", "char?", "chars", "chunk", "chunk-append",
+ "chunk-buffer", "chunk-cons", "chunk-first", "chunk-next", "chunk-rest", "chunked-seq?", "class", "class?",
+ "clear-agent-errors", "clojure-version", "coll?", "comment", "commute", "comp", "comparator", "compare",
+ "compare-and-set!", "compile", "complement", "completing", "concat", "cond", "cond->", "cond->>", "condp",
+ "conj", "conj!", "cons", "constantly", "construct-proxy", "contains?", "count", "counted?", "create-ns",
+ "create-struct", "cycle", "dec", "dec'", "decimal?", "declare", "dedupe", "default-data-readers", "definline",
+ "definterface", "defmacro", "defmethod", "defmulti", "defn", "defn-", "defonce", "defprotocol", "defrecord",
+ "defstruct", "deftype", "delay", "delay?", "deliver", "denominator", "deref", "derive", "descendants",
+ "destructure", "disj", "disj!", "dissoc", "dissoc!", "distinct", "distinct?", "doall", "dorun", "doseq",
+ "dosync", "dotimes", "doto", "double", "double-array", "double?", "doubles", "drop", "drop-last", "drop-while",
+ "eduction", "empty", "empty?", "ensure", "ensure-reduced", "enumeration-seq", "error-handler", "error-mode",
+ "eval", "even?", "every-pred", "every?", "ex-data", "ex-info", "extend", "extend-protocol", "extend-type",
+ "extenders", "extends?", "false?", "ffirst", "file-seq", "filter", "filterv", "find", "find-keyword", "find-ns",
+ "find-protocol-impl", "find-protocol-method", "find-var", "first", "flatten", "float", "float-array", "float?",
+ "floats", "flush", "fn", "fn?", "fnext", "fnil", "for", "force", "format", "frequencies", "future", "future-call",
+ "future-cancel", "future-cancelled?", "future-done?", "future?", "gen-class", "gen-interface", "gensym", "get",
+ "get-in", "get-method", "get-proxy-class", "get-thread-bindings", "get-validator", "group-by", "halt-when", "hash",
+ "hash-combine", "hash-map", "hash-ordered-coll", "hash-set", "hash-unordered-coll", "ident?", "identical?",
+ "identity", "if-let", "if-not", "if-some", "ifn?", "import", "in-ns", "inc", "inc'", "indexed?", "init-proxy",
+ "inst-ms", "inst-ms*", "inst?", "instance?", "int", "int-array", "int?", "integer?", "interleave", "intern",
+ "interpose", "into", "into-array", "ints", "io!", "isa?", "iterate", "iterator-seq", "juxt", "keep", "keep-indexed",
+ "key", "keys", "keyword", "keyword?", "last", "lazy-cat", "lazy-seq", "let", "letfn", "line-seq", "list", "list*",
+ "list?", "load", "load-file", "load-reader", "load-string", "loaded-libs", "locking", "long", "long-array", "longs",
+ "loop", "macroexpand", "macroexpand-1", "make-array", "make-hierarchy", "map", "map-entry?", "map-indexed", "map?",
+ "mapcat", "mapv", "max", "max-key", "memfn", "memoize", "merge", "merge-with", "meta", "method-sig", "methods"];
+
+ var coreSymbols2 = [
+ "min", "min-key", "mix-collection-hash", "mod", "munge", "name", "namespace", "namespace-munge", "nat-int?",
+ "neg-int?", "neg?", "newline", "next", "nfirst", "nil?", "nnext", "not", "not-any?", "not-empty", "not-every?",
+ "not=", "ns", "ns-aliases", "ns-imports", "ns-interns", "ns-map", "ns-name", "ns-publics", "ns-refers", "ns-resolve",
+ "ns-unalias", "ns-unmap", "nth", "nthnext", "nthrest", "num", "number?", "numerator", "object-array", "odd?", "or",
+ "parents", "partial", "partition", "partition-all", "partition-by", "pcalls", "peek", "persistent!", "pmap", "pop",
+ "pop!", "pop-thread-bindings", "pos-int?", "pos?", "pr", "pr-str", "prefer-method", "prefers",
+ "primitives-classnames", "print", "print-ctor", "print-dup", "print-method", "print-simple", "print-str", "printf",
+ "println", "println-str", "prn", "prn-str", "promise", "proxy", "proxy-call-with-super", "proxy-mappings",
+ "proxy-name", "proxy-super", "push-thread-bindings", "pvalues", "qualified-ident?", "qualified-keyword?",
+ "qualified-symbol?", "quot", "rand", "rand-int", "rand-nth", "random-sample", "range", "ratio?", "rational?",
+ "rationalize", "re-find", "re-groups", "re-matcher", "re-matches", "re-pattern", "re-seq", "read", "read-line",
+ "read-string", "reader-conditional", "reader-conditional?", "realized?", "record?", "reduce", "reduce-kv", "reduced",
+ "reduced?", "reductions", "ref", "ref-history-count", "ref-max-history", "ref-min-history", "ref-set", "refer",
+ "refer-clojure", "reify", "release-pending-sends", "rem", "remove", "remove-all-methods", "remove-method", "remove-ns",
+ "remove-watch", "repeat", "repeatedly", "replace", "replicate", "require", "reset!", "reset-meta!", "reset-vals!",
+ "resolve", "rest", "restart-agent", "resultset-seq", "reverse", "reversible?", "rseq", "rsubseq", "run!", "satisfies?",
+ "second", "select-keys", "send", "send-off", "send-via", "seq", "seq?", "seqable?", "seque", "sequence", "sequential?",
+ "set", "set-agent-send-executor!", "set-agent-send-off-executor!", "set-error-handler!", "set-error-mode!",
+ "set-validator!", "set?", "short", "short-array", "shorts", "shuffle", "shutdown-agents", "simple-ident?",
+ "simple-keyword?", "simple-symbol?", "slurp", "some", "some->", "some->>", "some-fn", "some?", "sort", "sort-by",
+ "sorted-map", "sorted-map-by", "sorted-set", "sorted-set-by", "sorted?", "special-symbol?", "spit", "split-at",
+ "split-with", "str", "string?", "struct", "struct-map", "subs", "subseq", "subvec", "supers", "swap!", "swap-vals!",
+ "symbol", "symbol?", "sync", "tagged-literal", "tagged-literal?", "take", "take-last", "take-nth", "take-while", "test",
+ "the-ns", "thread-bound?", "time", "to-array", "to-array-2d", "trampoline", "transduce", "transient", "tree-seq",
+ "true?", "type", "unchecked-add", "unchecked-add-int", "unchecked-byte", "unchecked-char", "unchecked-dec",
+ "unchecked-dec-int", "unchecked-divide-int", "unchecked-double", "unchecked-float", "unchecked-inc", "unchecked-inc-int",
+ "unchecked-int", "unchecked-long", "unchecked-multiply", "unchecked-multiply-int", "unchecked-negate",
+ "unchecked-negate-int", "unchecked-remainder-int", "unchecked-short", "unchecked-subtract", "unchecked-subtract-int",
+ "underive", "unquote", "unquote-splicing", "unreduced", "unsigned-bit-shift-right", "update", "update-in",
+ "update-proxy", "uri?", "use", "uuid?", "val", "vals", "var-get", "var-set", "var?", "vary-meta", "vec", "vector",
+ "vector-of", "vector?", "volatile!", "volatile?", "vreset!", "vswap!", "when", "when-first", "when-let", "when-not",
+ "when-some", "while", "with-bindings", "with-bindings*", "with-in-str", "with-loading-context", "with-local-vars",
+ "with-meta", "with-open", "with-out-str", "with-precision", "with-redefs", "with-redefs-fn", "xml-seq", "zero?",
+ "zipmap"
+ ];
+
+ MT("should highlight core symbols as keywords (part 1/2)",
+ typeTokenPairs("keyword", coreSymbols1)
+ );
+
+ MT("should highlight core symbols as keywords (part 2/2)",
+ typeTokenPairs("keyword", coreSymbols2)
+ );
+
+ MT("should properly indent forms in list literals",
+ "[bracket (][builtin foo] [atom :a] [number 1] [atom true] [atom nil][bracket )]",
+ "",
+ "[bracket (][builtin foo] [atom :a]",
+ " [number 1]",
+ " [atom true]",
+ " [atom nil][bracket )]",
+ "",
+ "[bracket (][builtin foo] [atom :a] [number 1]",
+ " [atom true]",
+ " [atom nil][bracket )]",
+ "",
+ "[bracket (]",
+ " [builtin foo]",
+ " [atom :a]",
+ " [number 1]",
+ " [atom true]",
+ " [atom nil][bracket )]",
+ "",
+ "[bracket (][builtin foo] [bracket [[][atom :a][bracket ]]]",
+ " [number 1]",
+ " [atom true]",
+ " [atom nil][bracket )]"
+ );
+
+ MT("should properly indent forms in vector literals",
+ "[bracket [[][atom :a] [number 1] [atom true] [atom nil][bracket ]]]",
+ "",
+ "[bracket [[][atom :a]",
+ " [number 1]",
+ " [atom true]",
+ " [atom nil][bracket ]]]",
+ "",
+ "[bracket [[][atom :a] [number 1]",
+ " [atom true]",
+ " [atom nil][bracket ]]]",
+ "",
+ "[bracket [[]",
+ " [variable foo]",
+ " [atom :a]",
+ " [number 1]",
+ " [atom true]",
+ " [atom nil][bracket ]]]"
+ );
+
+ MT("should properly indent forms in map literals",
+ "[bracket {][atom :a] [atom :a] [atom :b] [number 1] [atom :c] [atom true] [atom :d] [atom nil] [bracket }]",
+ "",
+ "[bracket {][atom :a] [atom :a]",
+ " [atom :b] [number 1]",
+ " [atom :c] [atom true]",
+ " [atom :d] [atom nil][bracket }]",
+ "",
+ "[bracket {][atom :a]",
+ " [atom :a]",
+ " [atom :b]",
+ " [number 1]",
+ " [atom :c]",
+ " [atom true]",
+ " [atom :d]",
+ " [atom nil][bracket }]",
+ "",
+ "[bracket {]",
+ " [atom :a] [atom :a]",
+ " [atom :b] [number 1]",
+ " [atom :c] [atom true]",
+ " [atom :d] [atom nil][bracket }]"
+ );
+
+ MT("should properly indent forms in set literals",
+ "[meta #][bracket {][atom :a] [number 1] [atom true] [atom nil] [bracket }]",
+ "",
+ "[meta #][bracket {][atom :a]",
+ " [number 1]",
+ " [atom true]",
+ " [atom nil][bracket }]",
+ "",
+ "[meta #][bracket {]",
+ " [atom :a]",
+ " [number 1]",
+ " [atom true]",
+ " [atom nil][bracket }]"
+ );
+
+ var haveBodyParameter = [
+ "->", "->>", "as->", "binding", "bound-fn", "case", "catch", "cond",
+ "cond->", "cond->>", "condp", "def", "definterface", "defmethod", "defn",
+ "defmacro", "defprotocol", "defrecord", "defstruct", "deftype", "do",
+ "doseq", "dotimes", "doto", "extend", "extend-protocol", "extend-type",
+ "fn", "for", "future", "if", "if-let", "if-not", "if-some", "let",
+ "letfn", "locking", "loop", "ns", "proxy", "reify", "some->", "some->>",
+ "struct-map", "try", "when", "when-first", "when-let", "when-not",
+ "when-some", "while", "with-bindings", "with-bindings*", "with-in-str",
+ "with-loading-context", "with-local-vars", "with-meta", "with-open",
+ "with-out-str", "with-precision", "with-redefs", "with-redefs-fn"];
+
+ function testFormsThatHaveBodyParameter(forms) {
+ for (var i = 0; i < forms.length; i++) {
+ MT("should indent body argument of `" + forms[i] + "` by `options.indentUnit` spaces",
+ "[bracket (][keyword " + forms[i] + "] [variable foo] [variable bar]",
+ " [variable baz]",
+ " [variable qux][bracket )]"
+ );
+ }
+ }
+
+ testFormsThatHaveBodyParameter(haveBodyParameter);
+
+ MT("should indent body argument of `comment` by `options.indentUnit` spaces",
+ "[bracket (][comment comment foo bar]",
+ "[comment baz]",
+ "[comment qux][bracket )]"
+ );
+
+ function typeTokenPairs(type, tokens) {
+ return "[" + type + " " + tokens.join("] [" + type + " ") + "]";
+ }
+})();
diff --git a/static/editor.md/lib/codemirror/mode/cmake/cmake.js b/static/editor.md/lib/codemirror/mode/cmake/cmake.js
new file mode 100644
index 000000000..496c71d1f
--- /dev/null
+++ b/static/editor.md/lib/codemirror/mode/cmake/cmake.js
@@ -0,0 +1,97 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: https://codemirror.net/LICENSE
+
+(function(mod) {
+ if (typeof exports == "object" && typeof module == "object")
+ mod(require("../../lib/codemirror"));
+ else if (typeof define == "function" && define.amd)
+ define(["../../lib/codemirror"], mod);
+ else
+ mod(CodeMirror);
+})(function(CodeMirror) {
+"use strict";
+
+CodeMirror.defineMode("cmake", function () {
+ var variable_regex = /({)?[a-zA-Z0-9_]+(})?/;
+
+ function tokenString(stream, state) {
+ var current, prev, found_var = false;
+ while (!stream.eol() && (current = stream.next()) != state.pending) {
+ if (current === '$' && prev != '\\' && state.pending == '"') {
+ found_var = true;
+ break;
+ }
+ prev = current;
+ }
+ if (found_var) {
+ stream.backUp(1);
+ }
+ if (current == state.pending) {
+ state.continueString = false;
+ } else {
+ state.continueString = true;
+ }
+ return "string";
+ }
+
+ function tokenize(stream, state) {
+ var ch = stream.next();
+
+ // Have we found a variable?
+ if (ch === '$') {
+ if (stream.match(variable_regex)) {
+ return 'variable-2';
+ }
+ return 'variable';
+ }
+ // Should we still be looking for the end of a string?
+ if (state.continueString) {
+ // If so, go through the loop again
+ stream.backUp(1);
+ return tokenString(stream, state);
+ }
+ // Do we just have a function on our hands?
+ // In 'cmake_minimum_required (VERSION 2.8.8)', 'cmake_minimum_required' is matched
+ if (stream.match(/(\s+)?\w+\(/) || stream.match(/(\s+)?\w+\ \(/)) {
+ stream.backUp(1);
+ return 'def';
+ }
+ if (ch == "#") {
+ stream.skipToEnd();
+ return "comment";
+ }
+ // Have we found a string?
+ if (ch == "'" || ch == '"') {
+ // Store the type (single or double)
+ state.pending = ch;
+ // Perform the looping function to find the end
+ return tokenString(stream, state);
+ }
+ if (ch == '(' || ch == ')') {
+ return 'bracket';
+ }
+ if (ch.match(/[0-9]/)) {
+ return 'number';
+ }
+ stream.eatWhile(/[\w-]/);
+ return null;
+ }
+ return {
+ startState: function () {
+ var state = {};
+ state.inDefinition = false;
+ state.inInclude = false;
+ state.continueString = false;
+ state.pending = false;
+ return state;
+ },
+ token: function (stream, state) {
+ if (stream.eatSpace()) return null;
+ return tokenize(stream, state);
+ }
+ };
+});
+
+CodeMirror.defineMIME("text/x-cmake", "cmake");
+
+});
diff --git a/static/editor.md/lib/codemirror/mode/cmake/index.html b/static/editor.md/lib/codemirror/mode/cmake/index.html
new file mode 100644
index 000000000..edd6838a0
--- /dev/null
+++ b/static/editor.md/lib/codemirror/mode/cmake/index.html
@@ -0,0 +1,129 @@
+
+
+CodeMirror: CMake mode
+
+
+
+
+
+
+
+
+
+
+
+CMake mode
+
+# vim: syntax=cmake
+if(NOT CMAKE_BUILD_TYPE)
+ # default to Release build for GCC builds
+ set(CMAKE_BUILD_TYPE Release CACHE STRING
+ "Choose the type of build, options are: None(CMAKE_CXX_FLAGS or CMAKE_C_FLAGS used) Debug Release RelWithDebInfo MinSizeRel."
+ FORCE)
+endif()
+message(STATUS "cmake version ${CMAKE_VERSION}")
+if(POLICY CMP0025)
+ cmake_policy(SET CMP0025 OLD) # report Apple's Clang as just Clang
+endif()
+if(POLICY CMP0042)
+ cmake_policy(SET CMP0042 NEW) # MACOSX_RPATH
+endif()
+
+project (x265)
+cmake_minimum_required (VERSION 2.8.8) # OBJECT libraries require 2.8.8
+include(CheckIncludeFiles)
+include(CheckFunctionExists)
+include(CheckSymbolExists)
+include(CheckCXXCompilerFlag)
+
+# X265_BUILD must be incremented each time the public API is changed
+set(X265_BUILD 48)
+configure_file("${PROJECT_SOURCE_DIR}/x265.def.in"
+ "${PROJECT_BINARY_DIR}/x265.def")
+configure_file("${PROJECT_SOURCE_DIR}/x265_config.h.in"
+ "${PROJECT_BINARY_DIR}/x265_config.h")
+
+SET(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake" "${CMAKE_MODULE_PATH}")
+
+# System architecture detection
+string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR}" SYSPROC)
+set(X86_ALIASES x86 i386 i686 x86_64 amd64)
+list(FIND X86_ALIASES "${SYSPROC}" X86MATCH)
+if("${SYSPROC}" STREQUAL "" OR X86MATCH GREATER "-1")
+ message(STATUS "Detected x86 target processor")
+ set(X86 1)
+ add_definitions(-DX265_ARCH_X86=1)
+ if("${CMAKE_SIZEOF_VOID_P}" MATCHES 8)
+ set(X64 1)
+ add_definitions(-DX86_64=1)
+ endif()
+elseif(${SYSPROC} STREQUAL "armv6l")
+ message(STATUS "Detected ARM target processor")
+ set(ARM 1)
+ add_definitions(-DX265_ARCH_ARM=1 -DHAVE_ARMV6=1)
+else()
+ message(STATUS "CMAKE_SYSTEM_PROCESSOR value `${CMAKE_SYSTEM_PROCESSOR}` is unknown")
+ message(STATUS "Please add this value near ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE}")
+endif()
+
+if(UNIX)
+ list(APPEND PLATFORM_LIBS pthread)
+ find_library(LIBRT rt)
+ if(LIBRT)
+ list(APPEND PLATFORM_LIBS rt)
+ endif()
+ find_package(Numa)
+ if(NUMA_FOUND)
+ list(APPEND CMAKE_REQUIRED_LIBRARIES ${NUMA_LIBRARY})
+ check_symbol_exists(numa_node_of_cpu numa.h NUMA_V2)
+ if(NUMA_V2)
+ add_definitions(-DHAVE_LIBNUMA)
+ message(STATUS "libnuma found, building with support for NUMA nodes")
+ list(APPEND PLATFORM_LIBS ${NUMA_LIBRARY})
+ link_directories(${NUMA_LIBRARY_DIR})
+ include_directories(${NUMA_INCLUDE_DIR})
+ endif()
+ endif()
+ mark_as_advanced(LIBRT NUMA_FOUND)
+endif(UNIX)
+
+if(X64 AND NOT WIN32)
+ option(ENABLE_PIC "Enable Position Independent Code" ON)
+else()
+ option(ENABLE_PIC "Enable Position Independent Code" OFF)
+endif(X64 AND NOT WIN32)
+
+# Compiler detection
+if(CMAKE_GENERATOR STREQUAL "Xcode")
+ set(XCODE 1)
+endif()
+if (APPLE)
+ add_definitions(-DMACOS)
+endif()
+
+
+
+ MIME types defined: text/x-cmake.
+
+
diff --git a/static/editor.md/lib/codemirror/mode/cobol/cobol.js b/static/editor.md/lib/codemirror/mode/cobol/cobol.js
index 897022b18..a7aeafa03 100644
--- a/static/editor.md/lib/codemirror/mode/cobol/cobol.js
+++ b/static/editor.md/lib/codemirror/mode/cobol/cobol.js
@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
/**
* Author: Gautam Mehta
@@ -195,7 +195,7 @@ CodeMirror.defineMode("cobol", function () {
case "string": // multi-line string parsing mode
var next = false;
while ((next = stream.next()) != null) {
- if (next == "\"" || next == "\'") {
+ if ((next == "\"" || next == "\'") && !stream.match(/['"]/, false)) {
state.mode = false;
break;
}
diff --git a/static/editor.md/lib/codemirror/mode/cobol/index.html b/static/editor.md/lib/codemirror/mode/cobol/index.html
index 4352419a0..40feb0014 100644
--- a/static/editor.md/lib/codemirror/mode/cobol/index.html
+++ b/static/editor.md/lib/codemirror/mode/cobol/index.html
@@ -39,7 +39,7 @@
.CodeMirror-activeline-background {background: #555555 !important;}
-
CodeMirror
+
CodeMirror
Home
diff --git a/static/editor.md/lib/codemirror/mode/coffeescript/coffeescript.js b/static/editor.md/lib/codemirror/mode/coffeescript/coffeescript.js
index da0eb2d51..a54e9d5ed 100644
--- a/static/editor.md/lib/codemirror/mode/coffeescript/coffeescript.js
+++ b/static/editor.md/lib/codemirror/mode/coffeescript/coffeescript.js
@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/LICENSE
/**
* Link to the project's GitHub page:
@@ -25,7 +25,7 @@ CodeMirror.defineMode("coffeescript", function(conf, parserConf) {
var operators = /^(?:->|=>|\+[+=]?|-[\-=]?|\*[\*=]?|\/[\/=]?|[=!]=|<[><]?=?|>>?=?|%=?|&=?|\|=?|\^=?|\~|!|\?|(or|and|\|\||&&|\?)=)/;
var delimiters = /^(?:[()\[\]{},:`=;]|\.\.?\.?)/;
var identifiers = /^[_A-Za-z$][_A-Za-z$0-9]*/;
- var properties = /^(@|this\.)[_A-Za-z$][_A-Za-z$0-9]*/;
+ var atProp = /^@[_A-Za-z$][_A-Za-z$0-9]*/;
var wordOperators = wordRegexp(["and", "or", "not",
"is", "isnt", "in",
@@ -145,6 +145,8 @@ CodeMirror.defineMode("coffeescript", function(conf, parserConf) {
}
}
+
+
// Handle operators and delimiters
if (stream.match(operators) || stream.match(wordOperators)) {
return "operator";
@@ -157,6 +159,10 @@ CodeMirror.defineMode("coffeescript", function(conf, parserConf) {
return "atom";
}
+ if (stream.match(atProp) || state.prop && stream.match(identifiers)) {
+ return "property";
+ }
+
if (stream.match(keywords)) {
return "keyword";
}
@@ -165,10 +171,6 @@ CodeMirror.defineMode("coffeescript", function(conf, parserConf) {
return "variable";
}
- if (stream.match(properties)) {
- return "property";
- }
-
// Handle non-detected items
stream.next();
return ERRORCLASS;
@@ -265,24 +267,11 @@ CodeMirror.defineMode("coffeescript", function(conf, parserConf) {
var style = state.tokenize(stream, state);
var current = stream.current();
- // Handle "." connected identifiers
- if (current === ".") {
- style = state.tokenize(stream, state);
- current = stream.current();
- if (/^\.[\w$]+$/.test(current)) {
- return "variable";
- } else {
- return ERRORCLASS;
- }
- }
-
// Handle scope changes.
if (current === "return") {
state.dedent = true;
}
- if (((current === "->" || current === "=>") &&
- !state.lambda &&
- !stream.peek())
+ if (((current === "->" || current === "=>") && stream.eol())
|| style === "indent") {
indent(stream, state);
}
@@ -324,8 +313,7 @@ CodeMirror.defineMode("coffeescript", function(conf, parserConf) {
return {
tokenize: tokenBase,
scope: {offset:basecolumn || 0, type:"coffee", prev: null, align: false},
- lastToken: null,
- lambda: false,
+ prop: false,
dedent: 0
};
},
@@ -335,12 +323,9 @@ CodeMirror.defineMode("coffeescript", function(conf, parserConf) {
if (fillAlign && stream.sol()) fillAlign.align = false;
var style = tokenLexer(stream, state);
- if (fillAlign && style && style != "comment") fillAlign.align = true;
-
- state.lastToken = {style:style, content: stream.current()};
-
- if (stream.eol() && stream.lambda) {
- state.lambda = false;
+ if (style && style != "comment") {
+ if (fillAlign) fillAlign.align = true;
+ state.prop = style == "punctuation" && stream.current() == "."
}
return style;
@@ -364,6 +349,11 @@ CodeMirror.defineMode("coffeescript", function(conf, parserConf) {
return external;
});
+// IANA registered media type
+// https://www.iana.org/assignments/media-types/
+CodeMirror.defineMIME("application/vnd.coffeescript", "coffeescript");
+
CodeMirror.defineMIME("text/x-coffeescript", "coffeescript");
+CodeMirror.defineMIME("text/coffeescript", "coffeescript");
});
diff --git a/static/editor.md/lib/codemirror/mode/coffeescript/index.html b/static/editor.md/lib/codemirror/mode/coffeescript/index.html
index 93a5f4f30..6d51aab3d 100644
--- a/static/editor.md/lib/codemirror/mode/coffeescript/index.html
+++ b/static/editor.md/lib/codemirror/mode/coffeescript/index.html
@@ -9,7 +9,7 @@