Gerald 12 лет назад
Родитель
Сommit
2363429ccc

+ 9 - 9
_locales/cs/messages.json

@@ -79,6 +79,14 @@
 		"message": "Skript URL: $1",
 		"description": "URL of the script to be installed on confirm page."
 	},
+	"msgErrorLoadingJS": {
+		"message": "Chyba při načítání skript.",
+		"description": "Message shown on confirm page when the script to be installed cannot be loaded."
+	},
+	"msgLoadedJS": {
+		"message": "Skript načten.",
+		"description": "Message shown in the confirm page when a javascript file to be installed is loaded."
+	},
 	"msgLoadingJS": {
 		"message": "Nahrávám skript...",
 		"description": "Message shown on confirm page when the script to be installed is loading."
@@ -91,14 +99,6 @@
 		"message": "Chyba při načítání požadavky.",
 		"description": "Message shown when not all requirements are loaded successfully."
 	},
-	"msgLoadedJS": {
-		"message": "Skript načten.",
-		"description": "Message shown in the confirm page when a javascript file to be installed is loaded."
-	},
-	"msgErrorLoadingJS": {
-		"message": "Chyba při načítání skript.",
-		"description": "Message shown on confirm page when the script to be installed cannot be loaded."
-	},
 	"labelOptions": {
 		"message": "Možnosti",
 		"description": "Options of this extension."
@@ -363,4 +363,4 @@
 		"message": "Vše",
 		"description": "Button to replace all matches."
 	}
-}
+}

+ 8 - 8
_locales/en/messages.json

@@ -79,6 +79,14 @@
 		"message": "Script URL: $1",
 		"description": "URL of the script to be installed on confirm page."
 	},
+	"msgErrorLoadingJS": {
+		"message": "Error loading script.",
+		"description": "Message shown on confirm page when the script to be installed cannot be loaded."
+	},
+	"msgLoadedJS": {
+		"message": "Script loaded.",
+		"description": "Message shown in the confirm page when a javascript file to be installed is loaded."
+	},
 	"msgLoadingJS": {
 		"message": "Loading script...",
 		"description": "Message shown on confirm page when the script to be installed is loading."
@@ -91,14 +99,6 @@
 		"message": "Error loading requirements.",
 		"description": "Message shown when not all requirements are loaded successfully."
 	},
-	"msgLoadedJS": {
-		"message": "Script loaded.",
-		"description": "Message shown in the confirm page when a javascript file to be installed is loaded."
-	},
-	"msgErrorLoadingJS": {
-		"message": "Error loading script.",
-		"description": "Message shown on confirm page when the script to be installed cannot be loaded."
-	},
 	"labelOptions": {
 		"message": "Options",
 		"description": "Options of this extension."

+ 9 - 9
_locales/pl/messages.json

@@ -79,6 +79,14 @@
 		"message": "Skrypt URL: $1",
 		"description": "URL of the script to be installed on confirm page."
 	},
+	"msgErrorLoadingJS": {
+		"message": "Błąd wczytywania skryptu.",
+		"description": "Message shown on confirm page when the script to be installed cannot be loaded."
+	},
+	"msgLoadedJS": {
+		"message": "Skrypt załadowany.",
+		"description": "Message shown in the confirm page when a javascript file to be installed is loaded."
+	},
 	"msgLoadingJS": {
 		"message": "Skrypt ładowania...",
 		"description": "Message shown on confirm page when the script to be installed is loading."
@@ -91,14 +99,6 @@
 		"message": "Błąd podczas ładowania wymagania.",
 		"description": "Message shown when not all requirements are loaded successfully."
 	},
-	"msgLoadedJS": {
-		"message": "Skrypt załadowany.",
-		"description": "Message shown in the confirm page when a javascript file to be installed is loaded."
-	},
-	"msgErrorLoadingJS": {
-		"message": "Błąd wczytywania skryptu.",
-		"description": "Message shown on confirm page when the script to be installed cannot be loaded."
-	},
 	"labelOptions": {
 		"message": "Opcje",
 		"description": "Options of this extension."
@@ -363,4 +363,4 @@
 		"message": "Wszystko",
 		"description": "Button to replace all matches."
 	}
-}
+}

+ 46 - 46
_locales/ru/messages.json

@@ -4,7 +4,7 @@
 		"description": "Name of this extension."
 	},
 	"extDescription": {
-		"message": "Userscript support for Opera.",
+		"message": "Поддержка пользовательских скриптов для Opera.",
 		"description": "Description for this extension."
 	},
 	"extTranslator": {
@@ -16,7 +16,7 @@
 		"description": "Message shown when a script is updated/reinstalled."
 	},
 	"msgErrorFetchingScript": {
-		"message": "Ошибка чтения скрипта!",
+		"message": "Ошибка загрузки скрипта!",
 		"description": "Message shown when Violentmonkey fails fetching a new version of the script."
 	},
 	"msgInstalled": {
@@ -32,7 +32,7 @@
 		"description": "Message shown when a new version of script is being fetched."
 	},
 	"msgNewVersion": {
-		"message": "Найдена новая версия.",
+		"message": "Доступна новая версия.",
 		"description": "Message shown when a new version of script is found by @updateURL, but no @downloadURL is provided."
 	},
 	"msgCheckingForUpdate": {
@@ -40,7 +40,7 @@
 		"description": "Message shown when a script is being checked for updates by version numbers."
 	},
 	"msgErrorFetchingUpdateInfo": {
-		"message": "Не удается найти обновления.",
+		"message": "Не удается проверить обновления.",
 		"description": "Message shown when Violentmonkey fails fetching version data of the script."
 	},
 	"msgNoUpdate": {
@@ -52,7 +52,7 @@
 		"description": "Additional CSS for all pages."
 	},
 	"labelInstall": {
-		"message": "Установка сценария",
+		"message": "Установка скрипта",
 		"description": "Shown in the title of the confirm page while trying to install a script."
 	},
 	"optionClose": {
@@ -60,7 +60,7 @@
 		"description": "Option to close confirm window after installation."
 	},
 	"buttonConfirmInstallation": {
-		"message": "Подтвердите установку",
+		"message": "Установить",
 		"description": "Button to confirm installation of a script."
 	},
 	"buttonClose": {
@@ -79,6 +79,14 @@
 		"message": "URL скрипта: $1",
 		"description": "URL of the script to be installed on confirm page."
 	},
+	"msgErrorLoadingJS": {
+		"message": "Ошибка загрузки сценария.",
+		"description": "Message shown on confirm page when the script to be installed cannot be loaded."
+	},
+	"msgLoadedJS": {
+		"message": "Загружен скрипт: $1",
+		"description": "Message shown in the confirm page when a javascript file to be installed is loaded."
+	},
 	"msgLoadingJS": {
 		"message": "Загрузка сценарий...",
 		"description": "Message shown on confirm page when the script to be installed is loading."
@@ -91,24 +99,16 @@
 		"message": "Требования к погрузке Ошибка.",
 		"description": "Message shown when not all requirements are loaded successfully."
 	},
-	"msgLoadedJS": {
-		"message": "Скрипт загружается.",
-		"description": "Message shown in the confirm page when a javascript file to be installed is loaded."
-	},
-	"msgErrorLoadingJS": {
-		"message": "Ошибка загрузки сценария.",
-		"description": "Message shown on confirm page when the script to be installed cannot be loaded."
-	},
 	"labelOptions": {
-		"message": "Опции",
+		"message": "Настройки",
 		"description": "Options of this extension."
 	},
 	"buttonNew": {
-		"message": "Создать",
+		"message": "Новый",
 		"description": "Button to create a new script."
 	},
 	"anchorGetMoreScripts": {
-		"message": "<a href=http://userscripts.org target=_blank>Скачать скрипты с userscripts.org</a>",
+		"message": "<a href=http://userscripts.org target=_blank>Загрузить скрипты с userscripts.org</a>",
 		"description": "Link to get more scripts."
 	},
 	"anchorUpdateAll": {
@@ -116,7 +116,7 @@
 		"description": "Check all scripts for updates."
 	},
 	"buttonAdvanced": {
-		"message": "Дополнительные опции",
+		"message": "Дополнительно",
 		"description": "Button to show the advanced options."
 	},
 	"labelInstalledScripts": {
@@ -124,11 +124,11 @@
 		"description": "Label of the list of installed scripts."
 	},
 	"labelShowDetails": {
-		"message": "Показать детали",
+		"message": "Подробно",
 		"description": "Option to show the details of scripts, such as author, description, etc."
 	},
 	"msgLoading": {
-		"message": "Загрузка ...",
+		"message": "Загрузка...",
 		"description": "Message shown in the options page before script list is loaded."
 	},
 	"labelScriptEditor": {
@@ -152,11 +152,11 @@
 		"description": "Button to save modifications of a script and then close the editing page."
 	},
 	"labelName": {
-		"message": "Имя:",
+		"message": "Название:",
 		"description": "Label of script name."
 	},
 	"labelRunAt": {
-		"message": "Запуск:",
+		"message": "Выполнить:",
 		"description": "Label of script @run-at properties in custom meta data."
 	},
 	"labelRunAtDefault": {
@@ -168,55 +168,55 @@
 		"description": "Label of script @homepage in custom meta data."
 	},
 	"labelUpdateURL": {
-		"message": "URL обновления:",
+		"message": "Адрес обновления:",
 		"description": "Label of script @updateURL in custom meta data."
 	},
 	"labelDownloadURL": {
-		"message": "URL загрузки:",
+		"message": "Источник загрузки:",
 		"description": "Label of script @downloadURL in custom meta data."
 	},
 	"labelInclude": {
-		"message": "Включения",
+		"message": "Задействовать на (правила включений include):",
 		"description": "Label of @include rules."
 	},
 	"labelKeepInclude": {
-		"message": "Сохранять оригинальные правила include",
+		"message": "Использовать оригинальные правила",
 		"description": "Option to keep the original @include rules."
 	},
 	"labelCustomInclude": {
-		"message": "Пользовательские правила include: <em>(По одному на строку)</em>",
+		"message": "Пользовательские правила (одно на строку):",
 		"description": "Label of custom @include rules."
 	},
 	"labelMatch": {
-		"message": "Соответствия",
+		"message": "Задействовать на (правила совпадений match):",
 		"description": "Label of @match rules."
 	},
 	"labelKeepMatch": {
-		"message": "Сохранять оригинальные правила match",
+		"message": "Использовать оригинальные правила",
 		"description": "Option to keep the original @match rules."
 	},
 	"labelCustomMatch": {
-		"message": "Пользовательские правила match: <em>(По одному на строку)</em>",
+		"message": "Пользовательские правила (одно на строку):",
 		"description": "Label of custom @match rules."
 	},
 	"labelExclude": {
-		"message": "Исключения",
+		"message": "Не задействовать на (правила исключений exclude):",
 		"description": "Label of @exclude rules."
 	},
 	"labelKeepExclude": {
-		"message": "Сохранять оригинальные правила exclude",
+		"message": "Использовать оригинальные правила",
 		"description": "Option to keep the original @exclude rules."
 	},
 	"labelCustomExclude": {
-		"message": "Пользовательские правила exclude: <em>(По одному на строку)</em>",
+		"message": "Пользовательские правила (одно на строку):",
 		"description": "Label of custom @exclude rules."
 	},
 	"buttonOK": {
-		"message": "ОК",
+		"message": "Готово",
 		"description": "Button to confirm modifications."
 	},
 	"buttonCancel": {
-		"message": "Отменить",
+		"message": "Отмена",
 		"description": "Button to cancel modifications."
 	},
 	"labelExport": {
@@ -228,11 +228,11 @@
 		"description": "Options of export."
 	},
 	"labelExportScriptData": {
-		"message": "Экспортировать данные",
+		"message": "Настройки скрипта",
 		"description": "Option to export script data along with scripts."
 	},
 	"buttonAllNone": {
-		"message": "Всё/Ничего",
+		"message": "Выбрать Всё/Ничего",
 		"description": "Button to select all scripts or none."
 	},
 	"labelAdvanced": {
@@ -244,7 +244,7 @@
 		"description": "Option to allow automatically checking scripts for updates every 24 hours."
 	},
 	"labelSearchLink": {
-		"message": "Поисковый URL: ",
+		"message": "Поисковый адрес: ",
 		"description": "Label of URL for searching scripts for a site."
 	},
 	"buttonDefault": {
@@ -288,7 +288,7 @@
 		"description": "Button to remove a script."
 	},
 	"labelNoName": {
-		"message": "Без названия",
+		"message": "Новый скрипт",
 		"description": "Text as the name of a script when no @name is assigned."
 	},
 	"labelAuthor": {
@@ -296,23 +296,23 @@
 		"description": "Label of author shown in the details of a script."
 	},
 	"confirmNotSaved": {
-		"message": "Изменения не сохранены!\nНажмите OK, чтобы продолжить или Отменить, чтобы вернуться.",
+		"message": "Изменения не сохранены!\nНажмите OK, чтобы выйти или Отмена, чтобы вернуться.",
 		"description": "Confirm message shown when there are unsaved script modifications."
 	},
 	"hintSearchLink": {
-		"message": "Поисковый URL должен включать *, где * - ключевые слова генерируемые Violentmonkey",
+		"message": "Поисковый адрес должен включать *, где * - ключевые слова генерируемые Violentmonkey",
 		"description": "Hint for the search URL."
 	},
 	"msgImported": {
-		"message": "$1 пункт(ов) импортировано.",
+		"message": "$1 скрипт(ов) импортировано.",
 		"description": "Message shown after import. There is an argument referring to the count of scripts imported."
 	},
 	"buttonVacuuming": {
-		"message": "Уборка пылесосом данных...",
+		"message": "Очистка данных кэша...",
 		"description": "Message shown when data vacuum is in progress."
 	},
 	"hintVacuumData": {
-		"message": "Сбросить избыточность и попробовать перезагрузить недостающие ресурсы в кэше",
+		"message": "Сбросить избыточность кэша и попробовать подгрузить недостающие ресурсы",
 		"description": "Hint for vacuuming data."
 	},
 	"buttonExport": {
@@ -344,7 +344,7 @@
 		"description": "Menu item to go back to main menu from script commands."
 	},
 	"menuCommands": {
-		"message": "Команды сценария...",
+		"message": "Команды скриптов...",
 		"description": "Menu item to list script commands."
 	},
 	"labelSearch": {
@@ -363,4 +363,4 @@
 		"message": "Все",
 		"description": "Button to replace all matches."
 	}
-}
+}

+ 9 - 9
_locales/zh/messages.json

@@ -79,6 +79,14 @@
 		"message": "脚本URL:$1",
 		"description": "URL of the script to be installed on confirm page."
 	},
+	"msgErrorLoadingJS": {
+		"message": "加载脚本发生错误。",
+		"description": "Message shown on confirm page when the script to be installed cannot be loaded."
+	},
+	"msgLoadedJS": {
+		"message": "脚本已加载。",
+		"description": "Message shown in the confirm page when a javascript file to be installed is loaded."
+	},
 	"msgLoadingJS": {
 		"message": "正在加载脚本...",
 		"description": "Message shown on confirm page when the script to be installed is loading."
@@ -91,14 +99,6 @@
 		"message": "加载依赖脚本发生错误。",
 		"description": "Message shown when not all requirements are loaded successfully."
 	},
-	"msgLoadedJS": {
-		"message": "脚本已加载。",
-		"description": "Message shown in the confirm page when a javascript file to be installed is loaded."
-	},
-	"msgErrorLoadingJS": {
-		"message": "加载脚本发生错误。",
-		"description": "Message shown on confirm page when the script to be installed cannot be loaded."
-	},
 	"labelOptions": {
 		"message": "选项",
 		"description": "Options of this extension."
@@ -363,4 +363,4 @@
 		"message": "所有",
 		"description": "Button to replace all matches."
 	}
-}
+}

+ 21 - 20
confirm.js

@@ -35,6 +35,8 @@ initEditor(function(o){
 		i.replace(/^([^=]*)=(.*)$/,function(r,g1,g2){data[g1]=decodeURIComponent(g2);});
 	});
 	U.innerHTML=_('msgScriptURL',[data.url||'-']);
+	function error(){showMsg(_('msgErrorLoadingJS'));}
+	function loaded(){showMsg(_('msgLoadedJS'));I.disabled=false;}
 	if(data.url) {
 		U.setAttribute('title',data.url);
 		showMsg(_('msgLoadingJS'));
@@ -45,29 +47,28 @@ initEditor(function(o){
 				T.setValueAndFocus(this.responseText);
 				chrome.runtime.sendMessage({cmd:'ParseMeta',data:this.responseText},function(o){
 					var i=0,l=o.require.length,err=[];
-					showMsg(_('msgLoadingRequirements',[i,l]));
-					data.require={};
-					o.require.forEach(function(u){
-						var x=new XMLHttpRequest();
-						x.open('GET',u,true);
-						x.onloadend=function(){
-							i++;
-							if(this.status==200) data.require[u]=this.responseText;
-							else err.push(u);
-							if(i>=l) {
-								if(err.length) showMsg(_('msgErrorLoadingRequirements'),err.join('\n'));
-								else {
-									showMsg(_('msgLoadedJS'));
-									I.disabled=false;
+					if(l) {
+						showMsg(_('msgLoadingRequirements',[i,l]));
+						data.require={};
+						o.require.forEach(function(u){
+							var x=new XMLHttpRequest();
+							x.open('GET',u,true);
+							x.onloadend=function(){
+								i++;
+								if(this.status==200) data.require[u]=this.responseText;
+								else err.push(u);
+								if(i>=l) {
+									if(err.length) showMsg(_('msgErrorLoadingRequirements'),err.join('\n'));
+									else loaded();
 								}
-							}
-						};
-						x.send();
-					});
+							};
+							x.send();
+						});
+					} else loaded();
 				});
-			} else showMsg(_('msgErrorLoadingJS'));
+			} else error();
 		};
 		x.send();
-	}
+	} else error();
 },{exit:B.onclick,readonly:true});
 initCSS();initI18n();

+ 1 - 1
lib/CodeMirror/addon/comment/continuecomment.js

@@ -5,7 +5,7 @@
 
   function continueComment(cm) {
     var pos = cm.getCursor(), token = cm.getTokenAt(pos);
-    if (token.type != "comment") return CodeMirror.Pass;
+    if (token.type != "comment" || cm.getOption("disableInput")) return CodeMirror.Pass;
     var mode = CodeMirror.innerMode(cm.getMode(), token.state).mode;
 
     var insert;

+ 5 - 3
lib/CodeMirror/addon/edit/closebrackets.js

@@ -28,7 +28,7 @@
     var map = {
       name : "autoCloseBrackets",
       Backspace: function(cm) {
-        if (cm.somethingSelected()) return CodeMirror.Pass;
+        if (cm.somethingSelected() || cm.getOption("disableInput")) return CodeMirror.Pass;
         var cur = cm.getCursor(), around = charsAround(cm, cur);
         if (around && pairs.indexOf(around) % 2 == 0)
           cm.replaceRange("", CodeMirror.Pos(cur.line, cur.ch - 1), CodeMirror.Pos(cur.line, cur.ch + 1));
@@ -49,7 +49,8 @@
         else cm.execCommand("goCharRight");
       }
       map["'" + left + "'"] = function(cm) {
-        if (left == "'" && cm.getTokenAt(cm.getCursor()).type == "comment")
+        if (left == "'" && cm.getTokenAt(cm.getCursor()).type == "comment" ||
+            cm.getOption("disableInput"))
           return CodeMirror.Pass;
         if (cm.somethingSelected()) return surround(cm);
         if (left == right && maybeOverwrite(cm) != CodeMirror.Pass) return;
@@ -70,7 +71,8 @@
   function buildExplodeHandler(pairs) {
     return function(cm) {
       var cur = cm.getCursor(), around = charsAround(cm, cur);
-      if (!around || pairs.indexOf(around) % 2 != 0) return CodeMirror.Pass;
+      if (!around || pairs.indexOf(around) % 2 != 0 || cm.getOption("disableInput"))
+        return CodeMirror.Pass;
       cm.operation(function() {
         var newPos = CodeMirror.Pos(cur.line + 1, 0);
         cm.replaceSelection("\n\n", {anchor: newPos, head: newPos}, "+input");

+ 1 - 1
lib/CodeMirror/addon/edit/matchbrackets.js

@@ -54,7 +54,7 @@
     var one = cm.markText(found.from, Pos(found.from.line, found.from.ch + 1), {className: style});
     var two = found.to && cm.markText(found.to, Pos(found.to.line, found.to.ch + 1), {className: style});
     // 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.display.input.focus();
     var clear = function() {
       cm.operation(function() { one.clear(); two && two.clear(); });

+ 3 - 1
lib/CodeMirror/addon/fold/comment-fold.js

@@ -1,4 +1,6 @@
-CodeMirror.registerHelper("fold", "comment", function(cm, start) {
+CodeMirror.registerGlobalHelper("fold", "comment", function(mode) {
+  return mode.blockCommentStart && mode.blockCommentEnd;
+}, function(cm, start) {
   var mode = cm.getModeAt(start), startToken = mode.blockCommentStart, endToken = mode.blockCommentEnd;
   if (!startToken || !endToken) return;
   var line = start.line, lineText = cm.getLine(line);

+ 13 - 2
lib/CodeMirror/addon/fold/foldcode.js

@@ -3,8 +3,7 @@
 
   function doFold(cm, pos, options, force) {
     var finder = options && (options.call ? options : options.rangeFinder);
-    if (!finder) finder = cm.getHelper(pos, "fold");
-    if (!finder) return;
+    if (!finder) finder = CodeMirror.fold.auto;
     if (typeof pos == "number") pos = CodeMirror.Pos(pos, 0);
     var minSize = options && options.minFoldSize || 0;
 
@@ -63,6 +62,10 @@
     doFold(this, pos, options, force);
   });
 
+  CodeMirror.commands.fold = function(cm) {
+    cm.foldCode(cm.getCursor());
+  };
+
   CodeMirror.registerHelper("fold", "combine", function() {
     var funcs = Array.prototype.slice.call(arguments, 0);
     return function(cm, start) {
@@ -72,4 +75,12 @@
       }
     };
   });
+
+  CodeMirror.registerHelper("fold", "auto", function(cm, start) {
+    var helpers = cm.getHelpers(start, "fold");
+    for (var i = 0; i < helpers.length; i++) {
+      var cur = helpers[i](cm, start);
+      if (cur) return cur;
+    }
+  });
 })();

+ 1 - 1
lib/CodeMirror/addon/fold/foldgutter.js

@@ -62,7 +62,7 @@
       if (isFolded(cm, cur)) {
         mark = marker(opts.indicatorFolded);
       } else {
-        var pos = Pos(cur, 0), func = opts.rangeFinder || cm.getHelper(pos, "fold");
+        var pos = Pos(cur, 0), func = opts.rangeFinder || CodeMirror.fold.auto;
         var range = func && func(cm, pos);
         if (range && range.from.line + 1 < range.to.line)
           mark = marker(opts.indicatorOpen);

+ 1 - 1
lib/CodeMirror/addon/selection/active-line.js

@@ -15,7 +15,7 @@
       updateActiveLine(cm, cm.getCursor().line);
       cm.on("beforeSelectionChange", selectionChange);
     } else if (!val && prev) {
-      cm.off("beforeSelecionChange", selectionChange);
+      cm.off("beforeSelectionChange", selectionChange);
       clearActiveLine(cm);
       delete cm.state.activeLine;
     }

+ 179 - 97
lib/CodeMirror/lib/codemirror.js

@@ -10,10 +10,11 @@ window.CodeMirror = (function() {
   // IE11 currently doesn't count as 'ie', since it has almost none of
   // the same bugs as earlier versions. Use ie_gt10 to handle
   // incompatibilities in that version.
-  var ie = /MSIE \d/.test(navigator.userAgent);
-  var ie_lt8 = ie && (document.documentMode == null || document.documentMode < 8);
-  var ie_lt9 = ie && (document.documentMode == null || document.documentMode < 9);
+  var old_ie = /MSIE \d/.test(navigator.userAgent);
+  var ie_lt8 = old_ie && (document.documentMode == null || document.documentMode < 8);
+  var ie_lt9 = old_ie && (document.documentMode == null || document.documentMode < 9);
   var ie_gt10 = /Trident\/([7-9]|\d{2,})\./.test(navigator.userAgent);
+  var ie = old_ie || ie_gt10;
   var webkit = /WebKit\//.test(navigator.userAgent);
   var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(navigator.userAgent);
   var chrome = /Chrome\//.test(navigator.userAgent);
@@ -35,7 +36,7 @@ window.CodeMirror = (function() {
   if (opera_version && opera_version >= 15) { opera = false; webkit = true; }
   // Some browsers use the wrong event properties to signal cmd/ctrl on OS X
   var flipCtrlCmd = mac && (qtwebkit || opera && (opera_version == null || opera_version < 12.11));
-  var captureMiddleClick = gecko || (ie && !ie_lt9);
+  var captureMiddleClick = gecko || (old_ie && !ie_lt9);
 
   // Optimize some code when these features are not used
   var sawReadOnlySpans = false, sawCollapsedSpans = false;
@@ -75,7 +76,7 @@ window.CodeMirror = (function() {
 
     // Override magic textarea content restore that IE sometimes does
     // on our hidden textarea on reload
-    if (ie) setTimeout(bind(resetInput, this, true), 20);
+    if (old_ie) setTimeout(bind(resetInput, this, true), 20);
 
     registerEventHandlers(this);
     // IE throws unspecified error in certain cases, when
@@ -195,6 +196,10 @@ window.CodeMirror = (function() {
 
   function loadMode(cm) {
     cm.doc.mode = CodeMirror.getMode(cm.options, cm.doc.modeOption);
+    resetModeState(cm);
+  }
+
+  function resetModeState(cm) {
     cm.doc.iter(function(line) {
       if (line.stateAfter) line.stateAfter = null;
       if (line.styles) line.styles = null;
@@ -244,7 +249,6 @@ window.CodeMirror = (function() {
     var map = keyMap[cm.options.keyMap], style = map.style;
     cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-keymap-\S+/g, "") +
       (style ? " cm-keymap-" + style : "");
-    cm.state.disableInput = map.disableInput;
   }
 
   function themeChanged(cm) {
@@ -450,7 +454,7 @@ window.CodeMirror = (function() {
   // updates.
   function updateDisplayInner(cm, changes, visible, forced) {
     var display = cm.display, doc = cm.doc;
-    if (!display.wrapper.clientWidth) {
+    if (!display.wrapper.offsetWidth) {
       display.showingFrom = display.showingTo = doc.first;
       display.viewOffset = 0;
       return;
@@ -1054,7 +1058,7 @@ window.CodeMirror = (function() {
     // doesn't work when wrapping is on, but in that case the
     // situation is slightly better, since IE does cache line-wrapping
     // information and only recomputes per-line.
-    if (ie && !ie_lt8 && !cm.options.lineWrapping && pre.childNodes.length > 100) {
+    if (old_ie && !ie_lt8 && !cm.options.lineWrapping && pre.childNodes.length > 100) {
       var fragment = document.createDocumentFragment();
       var chunk = 10, n = pre.childNodes.length;
       for (var i = 0, chunks = Math.ceil(n / chunk); i < chunks; ++i) {
@@ -1290,7 +1294,7 @@ window.CodeMirror = (function() {
       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.test(lineObj.text.charAt(ch))) ++ch;
+        while (isExtendingChar(lineObj.text.charAt(ch))) ++ch;
         var pos = PosWithInfo(lineNo, ch, ch == from ? fromOutside : toOutside,
                               xDiff < 0 ? -1 : xDiff ? 1 : 0);
         return pos;
@@ -1482,7 +1486,7 @@ window.CodeMirror = (function() {
   // supported or compatible enough yet to rely on.)
   function readInput(cm) {
     var input = cm.display.input, prevInput = cm.display.prevInput, doc = cm.doc, sel = doc.sel;
-    if (!cm.state.focused || hasSelection(input) || isReadOnly(cm) || cm.state.disableInput) return false;
+    if (!cm.state.focused || hasSelection(input) || isReadOnly(cm) || cm.options.disableInput) return false;
     if (cm.state.pasteIncoming && cm.state.fakedLastChar) {
       input.value = input.value.substring(0, input.value.length - 1);
       cm.state.fakedLastChar = false;
@@ -1500,17 +1504,27 @@ window.CodeMirror = (function() {
     var same = 0, l = Math.min(prevInput.length, text.length);
     while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) ++same;
     var from = sel.from, to = sel.to;
+    var inserted = text.slice(same);
     if (same < prevInput.length)
       from = Pos(from.line, from.ch - (prevInput.length - same));
     else if (cm.state.overwrite && posEq(from, to) && !cm.state.pasteIncoming)
-      to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + (text.length - same)));
+      to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + inserted.length));
 
     var updateInput = cm.curOp.updateInput;
-    var changeEvent = {from: from, to: to, text: splitLines(text.slice(same)),
+    var changeEvent = {from: from, to: to, text: splitLines(inserted),
                        origin: cm.state.pasteIncoming ? "paste" : "+input"};
     makeChange(cm.doc, changeEvent, "end");
     cm.curOp.updateInput = updateInput;
     signalLater(cm, "inputRead", cm, changeEvent);
+    if (!cm.state.pasteIncoming && cm.options.electricChars &&
+        cm.options.smartIndent && sel.head.ch < 100) {
+      var electric = cm.getModeAt(sel.head).electricChars;
+      if (electric) for (var i = 0; i < electric.length; i++)
+        if (inserted.indexOf(electric.charAt(i)) > -1) {
+          indentLine(cm, sel.head.line, "smart");
+          break;
+        }
+    }
 
     if (text.length > 1000 || text.indexOf("\n") > -1) input.value = cm.display.prevInput = "";
     else cm.display.prevInput = text;
@@ -1550,7 +1564,7 @@ window.CodeMirror = (function() {
   function registerEventHandlers(cm) {
     var d = cm.display;
     on(d.scroller, "mousedown", operation(cm, onMouseDown));
-    if (ie)
+    if (old_ie)
       on(d.scroller, "dblclick", operation(cm, function(e) {
         if (signalDOMEvent(cm, e)) return;
         var pos = posFromMouse(cm, e);
@@ -1669,8 +1683,8 @@ window.CodeMirror = (function() {
 
     // Needed to handle Tab key in KHTML
     if (khtml) on(d.sizer, "mouseup", function() {
-        if (document.activeElement == d.input) d.input.blur();
-        focusInput(cm);
+      if (document.activeElement == d.input) d.input.blur();
+      focusInput(cm);
     });
   }
 
@@ -1828,7 +1842,7 @@ window.CodeMirror = (function() {
     }
 
     var move = operation(cm, function(e) {
-      if (!ie && !e_button(e)) done(e);
+      if (!old_ie && !e_button(e)) done(e);
       else extend(e);
     });
     var up = operation(cm, done);
@@ -1973,7 +1987,7 @@ window.CodeMirror = (function() {
   // 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;
+  if (old_ie) wheelPixelsPerUnit = -.53;
   else if (gecko) wheelPixelsPerUnit = 15;
   else if (chrome) wheelPixelsPerUnit = -.7;
   else if (safari) wheelPixelsPerUnit = -1/3;
@@ -2127,7 +2141,7 @@ window.CodeMirror = (function() {
     var cm = this;
     if (!cm.state.focused) onFocus(cm);
     if (signalDOMEvent(cm, e) || cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return;
-    if (ie && e.keyCode == 27) e.returnValue = false;
+    if (old_ie && e.keyCode == 27) e.returnValue = false;
     var code = e.keyCode;
     // IE does strange things with escape.
     cm.doc.sel.shift = code == 16 || e.shiftKey;
@@ -2148,10 +2162,6 @@ window.CodeMirror = (function() {
     if (opera && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;}
     if (((opera && (!e.which || e.which < 10)) || khtml) && handleKeyBinding(cm, e)) return;
     var ch = String.fromCharCode(charCode == null ? keyCode : charCode);
-    if (this.options.electricChars && this.doc.mode.electricChars &&
-        this.options.smartIndent && !isReadOnly(this) &&
-        this.doc.mode.electricChars.indexOf(ch) > -1)
-      setTimeout(operation(cm, function() {indentLine(cm, cm.doc.sel.to.line, "smart");}), 75);
     if (handleCharBinding(cm, e, ch)) return;
     if (ie && !ie_lt9) cm.display.inputHasSelection = null;
     fastPoll(cm);
@@ -2222,7 +2232,7 @@ window.CodeMirror = (function() {
 
       // Try to detect the user choosing select-all
       if (display.input.selectionStart != null) {
-        if (!ie || ie_lt9) prepareSelectAllHack();
+        if (!old_ie || ie_lt9) prepareSelectAllHack();
         clearTimeout(detectingSelectAll);
         var i = 0, poll = function(){
           if (display.prevInput == "\u200b" && display.input.selectionStart == 0)
@@ -2234,7 +2244,7 @@ window.CodeMirror = (function() {
       }
     }
 
-    if (ie && !ie_lt9) prepareSelectAllHack();
+    if (old_ie && !ie_lt9) prepareSelectAllHack();
     if (captureMiddleClick) {
       e_stop(e);
       var mouseup = function() {
@@ -2507,6 +2517,7 @@ window.CodeMirror = (function() {
 
   function posEq(a, b) {return a.line == b.line && a.ch == b.ch;}
   function posLess(a, b) {return a.line < b.line || (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);}
 
   // SELECTION
@@ -2651,14 +2662,13 @@ window.CodeMirror = (function() {
     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 hidden = display.cursor.style.display == "none";
-      if (hidden) {
-        display.cursor.style.display = "";
-        display.cursor.style.left = coords.left + "px";
-        display.cursor.style.top = (coords.top - display.viewOffset) + "px";
-      }
-      display.cursor.scrollIntoView(doScroll);
-      if (hidden) display.cursor.style.display = "none";
+      var scrollNode = elt("div", "\u200b", null, "position: absolute; top: " +
+                           (coords.top - display.viewOffset) + "px; height: " +
+                           (coords.bottom - coords.top + scrollerCutOff) + "px; left: " +
+                           coords.left + "px; width: 2px;");
+      cm.display.lineSpace.appendChild(scrollNode);
+      scrollNode.scrollIntoView(doScroll);
+      cm.display.lineSpace.removeChild(scrollNode);
     }
   }
 
@@ -2741,7 +2751,10 @@ window.CodeMirror = (function() {
     var tabSize = cm.options.tabSize;
     var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize);
     var curSpaceString = line.text.match(/^\s*/)[0], indentation;
-    if (how == "smart") {
+    if (!aggressive && !/\S/.test(line.text)) {
+      indentation = 0;
+      how = "not";
+    } else if (how == "smart") {
       indentation = cm.doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text);
       if (indentation == Pass) {
         if (!aggressive) return;
@@ -2923,7 +2936,7 @@ window.CodeMirror = (function() {
     }),
     indentSelection: operation(null, function(how) {
       var sel = this.doc.sel;
-      if (posEq(sel.from, sel.to)) return indentLine(this, sel.from.line, how);
+      if (posEq(sel.from, sel.to)) return indentLine(this, sel.from.line, how, true);
       var e = sel.to.line - (sel.to.ch ? 0 : 1);
       for (var i = sel.from.line; i <= e; ++i) indentLine(this, i, how);
     }),
@@ -2968,11 +2981,31 @@ window.CodeMirror = (function() {
     },
 
     getHelper: function(pos, type) {
-      if (!helpers.hasOwnProperty(type)) return;
+      return this.getHelpers(pos, type)[0];
+    },
+
+    getHelpers: function(pos, type) {
+      var found = [];
+      if (!helpers.hasOwnProperty(type)) return helpers;
       var help = helpers[type], mode = this.getModeAt(pos);
-      return mode[type] && help[mode[type]] ||
-        mode.helperType && help[mode.helperType] ||
-        help[mode.name];
+      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;
     },
 
     getStateAfter: function(line, precise) {
@@ -3161,14 +3194,18 @@ window.CodeMirror = (function() {
     },
 
     moveV: operation(null, function(dir, unit) {
-      var sel = this.doc.sel;
-      var pos = cursorCoords(this, sel.head, "div");
-      if (sel.goalColumn != null) pos.left = sel.goalColumn;
-      var target = findPosV(this, pos, dir, unit);
-
-      if (unit == "page") addToScrollPos(this, 0, charCoords(this, target, "div").top - pos.top);
+      var sel = this.doc.sel, target, goal;
+      if (sel.shift || sel.extend || posEq(sel.from, sel.to)) {
+        var pos = cursorCoords(this, sel.head, "div");
+        if (sel.goalColumn != null) pos.left = sel.goalColumn;
+        target = findPosV(this, pos, dir, unit);
+        if (unit == "page") addToScrollPos(this, 0, charCoords(this, target, "div").top - pos.top);
+        goal = pos.left;
+      } else {
+        target = dir < 0 ? sel.from : sel.to;
+      }
       extendSelection(this.doc, target, target, dir);
-      sel.goalColumn = pos.left;
+      if (goal != null) sel.goalColumn = goal;
     }),
 
     toggleOverwrite: function(value) {
@@ -3278,7 +3315,7 @@ window.CodeMirror = (function() {
   option("indentWithTabs", false);
   option("smartIndent", true);
   option("tabSize", 4, function(cm) {
-    loadMode(cm);
+    resetModeState(cm);
     clearCaches(cm);
     regChange(cm);
   }, true);
@@ -3331,6 +3368,7 @@ window.CodeMirror = (function() {
       if (!val) resetInput(cm, true);
     }
   });
+  option("disableInput", false, function(cm, val) {if (!val) resetInput(cm, true);}, true);
   option("dragDrop", true);
 
   option("cursorBlinkRate", 530);
@@ -3338,12 +3376,13 @@ window.CodeMirror = (function() {
   option("cursorHeight", 1);
   option("workTime", 100);
   option("workDelay", 100);
-  option("flattenSpans", true);
+  option("flattenSpans", true, resetModeState, true);
+  option("addModeClass", false, resetModeState, true);
   option("pollInterval", 100);
   option("undoDepth", 40, function(cm, val){cm.doc.history.undoDepth = val;});
   option("historyEventDelay", 500);
   option("viewportMargin", 10, function(cm){cm.refresh();}, true);
-  option("maxHighlightLength", 10000, function(cm){loadMode(cm); cm.refresh();}, true);
+  option("maxHighlightLength", 10000, resetModeState, true);
   option("crudeMeasuringFrom", 10000);
   option("moveInputWithCursor", true, function(cm, val) {
     if (!val) cm.display.inputDiv.style.top = cm.display.inputDiv.style.left = 0;
@@ -3400,6 +3439,9 @@ window.CodeMirror = (function() {
       }
     }
     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;
   };
@@ -3430,9 +3472,13 @@ window.CodeMirror = (function() {
 
   var helpers = CodeMirror.helpers = {};
   CodeMirror.registerHelper = function(type, name, value) {
-    if (!helpers.hasOwnProperty(type)) helpers[type] = CodeMirror[type] = {};
+    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});
+  };
 
   // UTILITIES
 
@@ -3537,7 +3583,9 @@ window.CodeMirror = (function() {
     indentAuto: function(cm) {cm.indentSelection("smart");},
     indentMore: function(cm) {cm.indentSelection("add");},
     indentLess: function(cm) {cm.indentSelection("subtract");},
-    insertTab: function(cm) {cm.replaceSelection("\t", "end", "+input");},
+    insertTab: function(cm) {
+      cm.replaceSelection("\t", "end", "+input");
+    },
     defaultTab: function(cm) {
       if (cm.somethingSelected()) cm.indentSelection("add");
       else cm.replaceSelection("\t", "end", "+input");
@@ -3826,7 +3874,7 @@ window.CodeMirror = (function() {
     if (withOp) endOperation(cm);
   };
 
-  TextMarker.prototype.find = function() {
+  TextMarker.prototype.find = function(bothSides) {
     var from, to;
     for (var i = 0; i < this.lines.length; ++i) {
       var line = this.lines[i];
@@ -3837,7 +3885,7 @@ window.CodeMirror = (function() {
         if (span.to != null) to = Pos(found, span.to);
       }
     }
-    if (this.type == "bookmark") return from;
+    if (this.type == "bookmark" && !bothSides) return from;
     return from && {from: from, to: to};
   };
 
@@ -3874,6 +3922,8 @@ window.CodeMirror = (function() {
     }
   };
 
+  var nextMarkerId = 0;
+
   function markText(doc, from, to, options, type) {
     if (options && options.shared) return markTextShared(doc, from, to, options, type);
     if (doc.cm && !doc.cm.curOp) return operation(doc.cm, markText)(doc, from, to, options, type);
@@ -3887,25 +3937,25 @@ window.CodeMirror = (function() {
       marker.replacedWith = elt("span", [marker.replacedWith], "CodeMirror-widget");
       if (!options.handleMouseEvents) marker.replacedWith.ignoreEvents = true;
     }
-    if (marker.collapsed) sawCollapsedSpans = 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)
       addToHistory(doc, {from: from, to: to, origin: "markText"},
                    {head: doc.sel.head, anchor: doc.sel.anchor}, NaN);
 
-    var curLine = from.line, size = 0, collapsedAtStart, collapsedAtEnd, cm = doc.cm, updateMaxLine;
+    var curLine = from.line, cm = doc.cm, updateMaxLine;
     doc.iter(curLine, to.line + 1, function(line) {
       if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(doc, line) == cm.display.maxLine)
         updateMaxLine = true;
       var span = {from: null, to: null, marker: marker};
-      size += line.text.length;
-      if (curLine == from.line) {span.from = from.ch; size -= from.ch;}
-      if (curLine == to.line) {span.to = to.ch; size -= line.text.length - to.ch;}
-      if (marker.collapsed) {
-        if (curLine == to.line) collapsedAtEnd = collapsedSpanAt(line, to.ch);
-        if (curLine == from.line) collapsedAtStart = collapsedSpanAt(line, from.ch);
-        else updateLineHeight(line, 0);
-      }
+      if (curLine == from.line) span.from = from.ch;
+      if (curLine == to.line) span.to = to.ch;
+      if (marker.collapsed && curLine != from.line) updateLineHeight(line, 0);
       addMarkedSpan(line, span);
       ++curLine;
     });
@@ -3921,9 +3971,7 @@ window.CodeMirror = (function() {
         doc.clearHistory();
     }
     if (marker.collapsed) {
-      if (collapsedAtStart != collapsedAtEnd)
-        throw new Error("Inserting collapsed marker overlapping an existing one");
-      marker.size = size;
+      marker.id = ++nextMarkerId;
       marker.atomic = true;
     }
     if (cm) {
@@ -4139,20 +4187,48 @@ window.CodeMirror = (function() {
     return parts;
   }
 
-  function collapsedSpanAt(line, ch) {
+  function extraLeft(marker) { return marker.inclusiveLeft ? -1 : 0; }
+  function extraRight(marker) { return marker.inclusiveRight ? 1 : 0; }
+
+  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 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) continue;
-      if ((sp.from == null || sp.from < ch) &&
-          (sp.to == null || sp.to > ch) &&
-          (!found || found.width < sp.marker.width))
+      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 collapsedSpanAt(line, -1); }
-  function collapsedSpanAtEnd(line) { return collapsedSpanAt(line, line.text.length + 1); }
+  function collapsedSpanAtStart(line) { return collapsedSpanAtSide(line, true); }
+  function collapsedSpanAtEnd(line) { return collapsedSpanAtSide(line, false); }
+
+  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(true);
+      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) || extraRight(sp.marker) - extraLeft(marker)) > 0 ||
+          fromCmp >= 0 && (cmp(found.from, to) || extraLeft(sp.marker) - extraRight(marker)) < 0)
+        return true;
+    }
+  }
 
   function visualLine(doc, line) {
     var merged;
@@ -4312,6 +4388,10 @@ window.CodeMirror = (function() {
       } else {
         style = mode.token(stream, state);
       }
+      if (cm.options.addModeClass) {
+        var mName = CodeMirror.innerMode(mode, state).mode.name;
+        if (mName) style = "m-" + (style ? mName + " " + style : mName);
+      }
       if (!flattenSpans || curStyle != style) {
         if (curStart < stream.start) f(stream.start, curStyle);
         curStart = stream.start; curStyle = style;
@@ -4383,7 +4463,7 @@ window.CodeMirror = (function() {
     }
   }
 
-  var styleToClassCache = {};
+  var styleToClassCache = {}, styleToClassCacheWithMode = {};
   function interpretTokenStyle(style, builder) {
     if (!style) return null;
     for (;;) {
@@ -4396,8 +4476,9 @@ window.CodeMirror = (function() {
       else if (!(new RegExp("(?:^|\s)" + lineClass[2] + "(?:$|\s)")).test(builder[prop]))
         builder[prop] += " " + lineClass[2];
     }
-    return styleToClassCache[style] ||
-      (styleToClassCache[style] = "cm-" + style.replace(/ +/g, " cm-"));
+    var cache = builder.cm.options.addModeClass ? styleToClassCacheWithMode : styleToClassCache;
+    return cache[style] ||
+      (cache[style] = "cm-" + style.replace(/ +/g, " cm-"));
   }
 
   function buildLineContent(cm, realLine, measure, copyWidgets) {
@@ -4414,7 +4495,7 @@ window.CodeMirror = (function() {
       builder.measure = line == realLine && measure;
       builder.pos = 0;
       builder.addToken = builder.measure ? buildTokenMeasure : buildToken;
-      if ((ie || webkit) && cm.getOption("lineWrapping"))
+      if ((old_ie || webkit) && cm.getOption("lineWrapping"))
         builder.addToken = buildTokenSplitSpaces(builder.addToken);
       var next = insertLineContent(line, builder, getLineStyles(cm, line));
       if (measure && line == realLine && !builder.measuredSomething) {
@@ -4433,7 +4514,7 @@ window.CodeMirror = (function() {
     // Work around problem with the reported dimensions of single-char
     // direction spans on IE (issue #1129). See also the comment in
     // cursorCoords.
-    if (measure && (ie || ie_gt10) && (order = getOrder(line))) {
+    if (measure && ie && (order = getOrder(line))) {
       var l = order.length - 1;
       if (order[l].from == order[l].to) --l;
       var last = order[l], prev = order[l - 1];
@@ -4500,13 +4581,12 @@ window.CodeMirror = (function() {
   function buildTokenMeasure(builder, text, style, startStyle, endStyle) {
     var wrapping = builder.cm.options.lineWrapping;
     for (var i = 0; i < text.length; ++i) {
-      var ch = text.charAt(i), start = i == 0;
-      if (ch >= "\ud800" && ch < "\udbff" && i < text.length - 1) {
-        ch = text.slice(i, i + 2);
-        ++i;
-      } else if (i && wrapping && spanAffectsWrapping(text, i)) {
+      var start = i == 0, to = i + 1;
+      while (to < text.length && isExtendingChar(text.charAt(to))) ++to;
+      var ch = text.slice(i, to);
+      i = to - 1;
+      if (i && wrapping && spanAffectsWrapping(text, i))
         builder.pre.appendChild(elt("wbr"));
-      }
       var old = builder.measure[builder.pos];
       var span = builder.measure[builder.pos] =
         buildToken(builder, ch, style,
@@ -4515,7 +4595,7 @@ window.CodeMirror = (function() {
       // In IE single-space nodes wrap differently than spaces
       // embedded in larger text nodes, except when set to
       // white-space: normal (issue #1268).
-      if (ie && wrapping && ch == " " && i && !/\s/.test(text.charAt(i - 1)) &&
+      if (old_ie && wrapping && ch == " " && i && !/\s/.test(text.charAt(i - 1)) &&
           i < text.length - 1 && !/\s/.test(text.charAt(i + 1)))
         span.style.whiteSpace = "normal";
       builder.pos += ch.length;
@@ -4583,7 +4663,7 @@ window.CodeMirror = (function() {
             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 || collapsed.marker.size < m.size))
+            if (m.collapsed && (!collapsed || compareCollapsedMarkers(collapsed.marker, m) < 0))
               collapsed = sp;
           } else if (sp.from > pos && nextChange > sp.from) {
             nextChange = sp.from;
@@ -4916,10 +4996,11 @@ window.CodeMirror = (function() {
     clearHistory: function() {this.history = makeHistory(this.history.maxGeneration);},
 
     markClean: function() {
-      this.cleanGeneration = this.changeGeneration();
+      this.cleanGeneration = this.changeGeneration(true);
     },
-    changeGeneration: function() {
-      this.history.lastOp = this.history.lastOrigin = null;
+    changeGeneration: function(forceSplit) {
+      if (forceSplit)
+        this.history.lastOp = this.history.lastOrigin = null;
       return this.history.generation;
     },
     isClean: function (gen) {
@@ -5223,10 +5304,10 @@ window.CodeMirror = (function() {
              anchorBefore: doc.sel.anchor, headBefore: doc.sel.head,
              anchorAfter: selAfter.anchor, headAfter: selAfter.head};
       hist.done.push(cur);
-      hist.generation = ++hist.maxGeneration;
       while (hist.done.length > hist.undoDepth)
         hist.done.shift();
     }
+    hist.generation = ++hist.maxGeneration;
     hist.lastTime = time;
     hist.lastOp = opId;
     hist.lastOrigin = change.origin;
@@ -5522,7 +5603,8 @@ window.CodeMirror = (function() {
     return true;
   }
 
-  var isExtendingChar = /[\u0300-\u036F\u0483-\u0487\u0488-\u0489\u0591-\u05BD\u05BF\u05C1-\u05C2\u05C4-\u05C5\u05C7\u0610-\u061A\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7-\u06E8\u06EA-\u06ED\uA66F\u1DC0-\u1DFF\u20D0-\u20FF\uA670-\uA672\uA674-\uA67D\uA69F\udc00-\udfff\uFE20-\uFE2F]/;
+  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); }
 
   // DOM UTILITIES
 
@@ -5663,14 +5745,14 @@ window.CodeMirror = (function() {
   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: ";", 91: "Mod", 92: "Mod", 93: "Mod", 109: "-", 107: "=", 127: "Delete",
-                  186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\",
-                  221: "]", 222: "'", 63276: "PageUp", 63277: "PageDown", 63275: "End", 63273: "Home",
-                  63234: "Left", 63232: "Up", 63235: "Right", 63233: "Down", 63302: "Insert", 63272: "Delete"};
+                  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] = String(i);
+    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
@@ -5749,7 +5831,7 @@ window.CodeMirror = (function() {
   function moveInLine(line, pos, dir, byUnit) {
     if (!byUnit) return pos + dir;
     do pos += dir;
-    while (pos > 0 && isExtendingChar.test(line.text.charAt(pos)));
+    while (pos > 0 && isExtendingChar(line.text.charAt(pos)));
     return pos;
   }
 
@@ -5784,7 +5866,7 @@ window.CodeMirror = (function() {
 
   function moveLogically(line, start, dir, byUnit) {
     var target = start + dir;
-    if (byUnit) while (target > 0 && isExtendingChar.test(line.text.charAt(target))) target += dir;
+    if (byUnit) while (target > 0 && isExtendingChar(line.text.charAt(target))) target += dir;
     return target < 0 || target > line.text.length ? null : target;
   }
 

+ 7 - 8
lib/CodeMirror/mode/javascript/javascript.js

@@ -15,7 +15,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
 
     var jsKeywords = {
       "if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
-      "return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C,
+      "return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C, "debugger": C,
       "var": kw("var"), "const": kw("var"), "let": kw("var"),
       "function": kw("function"), "catch": kw("catch"),
       "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
@@ -83,7 +83,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
     } else if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
       return ret(ch);
     } else if (ch == "=" && stream.eat(">")) {
-      return ret("=>");
+      return ret("=>", "operator");
     } else if (ch == "0" && stream.eat(/x/i)) {
       stream.eatWhile(/[\da-f]/i);
       return ret("number", "number");
@@ -104,7 +104,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
         return ret("regexp", "string-2");
       } else {
         stream.eatWhile(isOperatorChar);
-        return ret("operator", null, stream.current());
+        return ret("operator", "operator", stream.current());
       }
     } else if (ch == "`") {
       state.tokenize = tokenQuasi;
@@ -114,7 +114,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
       return ret("error", "error");
     } else if (isOperatorChar.test(ch)) {
       stream.eatWhile(isOperatorChar);
-      return ret("operator", null, stream.current());
+      return ret("operator", "operator", stream.current());
     } else {
       stream.eatWhile(/[\w\$_]/);
       var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word];
@@ -304,7 +304,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
     if (type == ";") return cont();
     if (type == "if") return cont(pushlex("form"), expression, statement, poplex, maybeelse);
     if (type == "function") return cont(functiondef);
-    if (type == "for") return cont(pushlex("form"), forspec, poplex, statement, poplex);
+    if (type == "for") return cont(pushlex("form"), forspec, statement, poplex);
     if (type == "variable") return cont(pushlex("stat"), maybelabel);
     if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"),
                                       block, poplex, poplex);
@@ -370,7 +370,6 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
     if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me);
   }
   function quasi(value) {
-    if (!value) debugger;
     if (value.slice(value.length - 2) != "${") return cont();
     return cont(expression, continueQuasi);
   }
@@ -474,7 +473,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
     if (type == "keyword b" && value == "else") return cont(pushlex("form"), statement, poplex);
   }
   function forspec(type) {
-    if (type == "(") return cont(pushlex(")"), forspec1, expect(")"));
+    if (type == "(") return cont(pushlex(")"), forspec1, expect(")"), poplex);
   }
   function forspec1(type) {
     if (type == "var") return cont(vardef, expect(";"), forspec2);
@@ -538,7 +537,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
     return pass(expressionNoComma, maybeArrayComprehension);
   }
   function maybeArrayComprehension(type) {
-    if (type == "for") return pass(comprehension);
+    if (type == "for") return pass(comprehension, expect("]"));
     if (type == ",") return cont(commasep(expressionNoComma, "]"));
     return pass(commasep(expressionNoComma, "]"));
   }