[Pkg-mozext-commits] [sieve-extension] 27/34: Imported Upstream version 0.2.3h+dfsg
Michael Fladischer
fladi at moszumanska.debian.org
Mon Sep 21 09:03:31 UTC 2015
This is an automated email from the git hooks/post-receive script.
fladi pushed a commit to branch master
in repository sieve-extension.
commit 31387c401c79efad4ef4dc9b669b8c35af51a1c4
Author: Michael Fladischer <FladischerMichael at fladi.at>
Date: Mon Sep 21 10:38:30 2015 +0200
Imported Upstream version 0.2.3h+dfsg
---
.../content/editor/SieveFilterEditor.html | 8 +-
.../content/editor/SieveFilterEditor.js | 111 +-
.../content/editor/SieveFilterEditor.xul | 11 +-
.../{sieve.iframe.glue.js => sieve.broker.js} | 17 +-
...or.text.glue.js => sieve.editor.text.client.js} | 63 +-
...editor.text.js => sieve.editor.text.service.js} | 194 +-
chrome/chromeFiles/content/libs/CodeMirror/AUTHORS | 79 +
.../content/libs/CodeMirror/CONTRIBUTING.md | 18 +-
chrome/chromeFiles/content/libs/CodeMirror/LICENSE | 2 +-
.../chromeFiles/content/libs/CodeMirror/README.md | 30 +-
.../libs/CodeMirror/addon/edit/closebrackets.js | 264 +-
.../content/libs/CodeMirror/addon/edit/closetag.js | 21 +-
.../libs/CodeMirror/addon/edit/continuelist.js | 22 +-
.../libs/CodeMirror/addon/edit/matchbrackets.js | 2 +-
.../content/libs/CodeMirror/addon/search/search.js | 83 +-
.../libs/CodeMirror/addon/search/searchcursor.js | 8 +-
.../content/libs/CodeMirror/lib/codemirror.css | 99 +-
.../content/libs/CodeMirror/lib/codemirror.js | 2700 +++++++++++++-------
.../content/libs/CodeMirror/mode/sieve/index.html | 93 -
.../content/libs/CodeMirror/package.json | 8 +-
.../content/modules/sieve/SieveAccounts.js | 118 +
.../content/options/SieveAccountOptions.js | 89 +-
.../content/options/SieveAccountOptions.xul | 57 +
install.rdf | 2 +-
24 files changed, 2763 insertions(+), 1336 deletions(-)
diff --git a/chrome/chromeFiles/content/editor/SieveFilterEditor.html b/chrome/chromeFiles/content/editor/SieveFilterEditor.html
index c612092..8b7be11 100644
--- a/chrome/chromeFiles/content/editor/SieveFilterEditor.html
+++ b/chrome/chromeFiles/content/editor/SieveFilterEditor.html
@@ -14,8 +14,8 @@
<script src="./../libs/CodeMirror/mode/sieve/sieve.js"></script>
- <script src="./sieve.iframe.glue.js"></script>
- <script src="./sieve.editor.text.js"></script>
+ <script src="./sieve.broker.js"></script>
+ <script src="./sieve.editor.text.service.js"></script>
<style>
.CodeMirror-fullscreen {
@@ -31,7 +31,7 @@
background-position: 650px top;
background-attachment: fixed;
- font-family: -moz-fixed;
+ font-family: -moz-fixed;
font-size: 12px;
line-height: normal;
}
@@ -55,7 +55,7 @@
<textarea id="code" name="code"></textarea>
</form>
<script>
- net.tschmid.sieve.editor.text.init();
+ net.tschmid.sieve.editor.text.service.init();
</script>
</body>
</html>
diff --git a/chrome/chromeFiles/content/editor/SieveFilterEditor.js b/chrome/chromeFiles/content/editor/SieveFilterEditor.js
index 1630c8d..e2dbcf3 100644
--- a/chrome/chromeFiles/content/editor/SieveFilterEditor.js
+++ b/chrome/chromeFiles/content/editor/SieveFilterEditor.js
@@ -48,7 +48,6 @@ var gEditorStatus =
}
-
function SieveFilterEditor()
{
SieveAbstractClient.call(this);
@@ -189,7 +188,7 @@ SieveFilterEditor.prototype.onPutScriptResponse
if (!document.getElementById('btnViewSource').checked)
{
- glue.setScript(script);
+ textEditor.setScript(script);
// Update the gui checksum...
gEditorStatus.checksum.gui = that._calcChecksum(script);
}
@@ -266,15 +265,30 @@ SieveFilterEditor.prototype.onScriptLoaded
var that = this;
- glue.loadScript(script, function() {
+ textEditor.loadScript(script, function() {
if (gEditorStatus.checksum.server == null) {
gEditorStatus.checksum.server = that._calcChecksum(script);
gSFE.setScriptName();
}
- glue.focus();
- });
+ textEditor.focus();
+ });
+
+
+ var account = SieveAccountManager.getAccountByName(gEditorStatus.account);
+
+ var settings = {}
+
+ settings.tab = {};
+ settings.tab.width = account.getSettings().getTabWidth();
+ settings.tab.policy = account.getSettings().getTabPolicy();
+
+ settings.indention = {};
+ settings.indention.width = account.getSettings().getIndentionWidth();
+ settings.indention.policy = account.getSettings().getIndentionPolicy();
+
+ textEditor.setOptions(settings);
}
SieveFilterEditor.prototype.observe
@@ -365,7 +379,7 @@ SieveFilterEditor.prototype.getScriptAsync
// In case it's the source we return the plaintext editor...
if (document.getElementById('btnViewSource').checked) {
- glue.getScript( function(script) {
+ textEditor.getScript( function(script) {
// Sanatize the line breaks to a single \n. Thunderbird scrambles them sometimes...
script = script.replace(/\r\n|\r|\n|\u0085|\u000C|\u2028|\u2029/g,"\r\n");
@@ -493,7 +507,7 @@ function onWindowPersist()
return args;
}
-var glue = null;
+var textEditor = null;
var sivEditorListener = {
onChange : function() {
@@ -514,7 +528,7 @@ function onFindString()
var isReverse = !!document.getElementById('cbxBackward').checked;
var isCaseSensitive = document.getElementById('cbxCaseSensitive').checked;
- glue.findString(token, isCaseSensitive, isReverse);
+ textEditor.findString(token, isCaseSensitive, isReverse);
return;
}
@@ -529,7 +543,7 @@ function onReplaceString()
var isReverse = !!document.getElementById('cbxBackward').checked;
var isCaseSensitive = document.getElementById('cbxCaseSensitive').checked;
- glue.replaceString(oldToken, newToken, isCaseSensitive, isReverse);
+ textEditor.replaceString(oldToken, newToken, isCaseSensitive, isReverse);
return;
}
@@ -537,8 +551,8 @@ function onReplaceString()
function onWindowLoad()
{
- glue = new net.tschmid.sieve.editor.text.glue("sivEditor2");
- glue.setListener( sivEditorListener );
+ textEditor = new net.tschmid.sieve.editor.text.Client("sivEditor2");
+ textEditor.setListener( sivEditorListener );
// hack to prevent links to be opened in the default browser window...
document.getElementById("ifSideBar").
@@ -648,7 +662,7 @@ function onViewSource(visible,aNoUpdate)
if (aNoUpdate || (gEditorStatus.checksum.gui == gSFE._calcChecksum(script)))
return;
- glue.setScript(script);
+ textEditor.setScript(script);
onInput();
return;
@@ -670,7 +684,7 @@ function onViewSource(visible,aNoUpdate)
deck.selectedIndex = 1;
// Finally we need to transfer the current script from the editor into the gui...
- glue.getScript( function(script) { updateWidgets(script) } );
+ textEditor.getScript( function(script) { updateWidgets(script) } );
}
function updateWidgets(script)
@@ -895,7 +909,7 @@ function onImport()
scriptableStream.close();
inputStream.close();
- glue.replaceSelection(script);
+ textEditor.replaceSelection(script);
onInput();
}
@@ -1036,52 +1050,45 @@ function onUseRemoteScript(script)
function onEditorShowMenu()
{
- // enxure editor is focused...
- /*if (document.commandDispatcher.focusedElement != this.parentNode.firstChild)
- this.parentNode.firstChild.focus();*/
-
- // we can use the built in function to determin if cut, copy and paste is possible
- var controller = document.commandDispatcher.getControllerForCommand("cmd_cut");
- if (controller.isCommandEnabled("cmd_cut"))
- document.getElementById("ctxCut").removeAttribute("disabled");
- else
- document.getElementById("ctxCut").setAttribute("disabled", "true");
-
- var controller = document.commandDispatcher.getControllerForCommand("cmd_copy");
- if (controller.isCommandEnabled("cmd_copy"))
- document.getElementById("ctxCopy").removeAttribute("disabled");
- else
- document.getElementById("ctxCopy").setAttribute("disabled", "true");
-
- var controller = document.commandDispatcher.getControllerForCommand("cmd_paste");
- if (controller.isCommandEnabled("cmd_paste"))
- document.getElementById("ctxPaste").removeAttribute("disabled");
- else
- document.getElementById("ctxPaste").setAttribute("disabled", "true");
- // Undo : editor.history
-
- var editor = document.getElementById("sivEditor2").contentWindow.editor;
-
- if (editor.somethingSelected())
- document.getElementById("ctxDelete").removeAttribute("disabled");
- else
- document.getElementById("ctxDelete").setAttribute("disabled", "true");
-
- if (editor.historySize().undo > 0)
- document.getElementById("ctxUndo").removeAttribute("disabled");
- else
- document.getElementById("ctxUndo").setAttribute("disabled", "true");
- // select all -> immer möglich
+ var callback = function(status) {
+
+ if (status.canCut)
+ document.getElementById("ctxCut").removeAttribute("disabled");
+ else
+ document.getElementById("ctxCut").setAttribute("disabled", "true");
+
+ if(status.canCopy)
+ document.getElementById("ctxCopy").removeAttribute("disabled");
+ else
+ document.getElementById("ctxCopy").setAttribute("disabled", "true");
+
+ if (status.canPaste)
+ document.getElementById("ctxPaste").removeAttribute("disabled");
+ else
+ document.getElementById("ctxPaste").setAttribute("disabled", "true");
+
+ if (status.canDelete)
+ document.getElementById("ctxDelete").removeAttribute("disabled");
+ else
+ document.getElementById("ctxDelete").setAttribute("disabled", "true");
+
+ if (status.canUndo)
+ document.getElementById("ctxUndo").removeAttribute("disabled");
+ else
+ document.getElementById("ctxUndo").setAttribute("disabled", "true");
+ }
+
+ textEditor.getStatus(callback);
}
function onDelete()
{
- glue.replaceSelection();
+ textEditor.replaceSelection();
}
function onSelectAll()
{
- glue.selectAll();
+ textEditor.selectAll();
}
var gSFE = new SieveFilterEditor();
\ No newline at end of file
diff --git a/chrome/chromeFiles/content/editor/SieveFilterEditor.xul b/chrome/chromeFiles/content/editor/SieveFilterEditor.xul
index 4053dae..4cd0b92 100644
--- a/chrome/chromeFiles/content/editor/SieveFilterEditor.xul
+++ b/chrome/chromeFiles/content/editor/SieveFilterEditor.xul
@@ -38,10 +38,11 @@
src="chrome://sieve/content/libs/libManageSieve/SieveAbstractClient.js"/>
<script type="application/javascript"
src="chrome://sieve/content/editor/SieveFilterEditor.js" />
+
<script type="application/javascript"
- src="chrome://sieve/content/editor/sieve.iframe.glue.js" />
+ src="chrome://sieve/content/editor/sieve.broker.js" />
<script type="application/javascript"
- src="chrome://sieve/content/editor/sieve.editor.text.glue.js" />
+ src="chrome://sieve/content/editor/sieve.editor.text.client.js" />
<!-- <script type="application/javascript"
src="chrome://global/content/printUtils.js"/>
<script type="application/javascript"
@@ -56,7 +57,7 @@
<menupopup id="ctxEditor" onpopupshowing="onEditorShowMenu();">
<menuitem id="ctxUndo"
label="&undoCmd.label;" accesskey="&undoCmd.accesskey;"
- oncommand="glue.onUndo(); event.stopPropagation();" />
+ oncommand="textEditor.onUndo(); event.stopPropagation();" />
<menuseparator/>
<menuitem id="ctxCut"
label="&cutCmd.label;" accesskey="&cutCmd.accesskey;"
@@ -87,10 +88,10 @@
<toolbarspacer />
<toolbarbutton id="btnUndo"
label="&edit.toolbar.undo;" class="toolbarbutton-1"
- oncommand="glue.onUndo()/*goDoCommand('cmd_undo')*/;" />
+ oncommand="textEditor.onUndo()/*goDoCommand('cmd_undo')*/;" />
<toolbarbutton id="btnRedo"
label="&edit.toolbar.redo;" class="toolbarbutton-1"
- oncommand="glue.onRedo()/*goDoCommand('cmd_redo');*/" />
+ oncommand="textEditor.onRedo()/*goDoCommand('cmd_redo');*/" />
<toolbarspacer />
<toolbarbutton id="btnCut"
label="&edit.toolbar.cut;" class="toolbarbutton-1"
diff --git a/chrome/chromeFiles/content/editor/sieve.iframe.glue.js b/chrome/chromeFiles/content/editor/sieve.broker.js
similarity index 74%
rename from chrome/chromeFiles/content/editor/sieve.iframe.glue.js
rename to chrome/chromeFiles/content/editor/sieve.broker.js
index c101474..dbf536f 100644
--- a/chrome/chromeFiles/content/editor/sieve.iframe.glue.js
+++ b/chrome/chromeFiles/content/editor/sieve.broker.js
@@ -10,24 +10,25 @@ if (!net.tschmid)
if (!net.tschmid.sieve)
net.tschmid.sieve = {};
-if (!net.tschmid.sieve.iframe)
- net.tschmid.sieve.iframe = {};
+if (!net.tschmid.sieve.broker)
+ net.tschmid.sieve.broker = {};
-if (!net.tschmid.sieve.iframe.glue)
- net.tschmid.sieve.iframe.glue = {};
+if (!net.tschmid.sieve.broker)
+ net.tschmid.sieve.broker = {};
/**
- * Glues two frame via html5 post message.
+ * Interacts alike a broke between two iframes. Virtually glues the two frame via html5
+ * post message to gether. So that bidirectional messaging is possible
**/
(function() {
- function SieveIframeGlue(target) {
+ function SieveBroker(target) {
this.target = target;
}
- SieveIframeGlue.prototype = {
+ SieveBroker.prototype = {
target : null,
listener : null,
@@ -83,7 +84,7 @@ if (!net.tschmid.sieve.iframe.glue)
}
// Export the constructor...
- net.tschmid.sieve.iframe.glue = SieveIframeGlue;
+ net.tschmid.sieve.Broker = SieveBroker;
}());
\ No newline at end of file
diff --git a/chrome/chromeFiles/content/editor/sieve.editor.text.glue.js b/chrome/chromeFiles/content/editor/sieve.editor.text.client.js
similarity index 58%
rename from chrome/chromeFiles/content/editor/sieve.editor.text.glue.js
rename to chrome/chromeFiles/content/editor/sieve.editor.text.client.js
index b090b52..05dfc78 100644
--- a/chrome/chromeFiles/content/editor/sieve.editor.text.glue.js
+++ b/chrome/chromeFiles/content/editor/sieve.editor.text.client.js
@@ -15,20 +15,20 @@ if (!net.tschmid.sieve.editor)
if (!net.tschmid.sieve.editor.text)
net.tschmid.sieve.editor.text = {};
-if (!net.tschmid.sieve.editor.text.glue)
- net.tschmid.sieve.editor.text.glue = {};
+if (!net.tschmid.sieve.editor.text.client)
+ net.tschmid.sieve.editor.text.client = {};
(function() {
- function SieveTextEditorGlue(id) {
- this.glue = new net.tschmid.sieve.iframe.glue(id);
+ function SieveTextEditorClient(id) {
+ this.broker = new net.tschmid.sieve.Broker(id);
this.id = id;
}
- SieveTextEditorGlue.prototype = {
+ SieveTextEditorClient.prototype = {
- glue : null,
+ broker : null,
listener : null,
callbacks : null,
id : null,
@@ -37,7 +37,7 @@ if (!net.tschmid.sieve.editor.text.glue)
this.listener = listener;
var that = this;
- this.glue.setListener( function(event, data) { that.onMessage(event, data); });
+ this.broker.setListener( function(event, data) { that.onMessage(event, data); });
},
@@ -100,7 +100,7 @@ if (!net.tschmid.sieve.editor.text.glue)
if (callback)
this.addCallback("onScriptLoaded", callback);
- this.glue.sendMessage("loadScript", script);
+ this.broker.sendMessage("loadScript", script);
},
getScript : function(callback)
@@ -108,32 +108,32 @@ if (!net.tschmid.sieve.editor.text.glue)
if (callback)
this.addCallback("onGetScript", callback);
- this.glue.sendMessage("getScript");
+ this.broker.sendMessage("getScript");
},
setScript : function(script)
{
- this.glue.sendMessage("setScript", script);
+ this.broker.sendMessage("setScript", script);
},
replaceSelection : function(text)
{
- this.glue.sendMessage("replaceSelection",text);
+ this.broker.sendMessage("replaceSelection",text);
},
selectAll : function()
{
- this.glue.sendMessage("selectAll");
+ this.broker.sendMessage("selectAll");
},
undo : function()
{
- this.glue.sendMessage("undo");
+ this.broker.sendMessage("undo");
},
redo : function()
{
- this.glue.sendMessage("redo");
+ this.broker.sendMessage("redo");
},
findString : function(token, isCaseSensitive, isReverse)
@@ -143,7 +143,7 @@ if (!net.tschmid.sieve.editor.text.glue)
data.isCaseSensitive = isCaseSensitive;
data.isReverse = isReverse;
- this.glue.sendMessage("findString", data);
+ this.broker.sendMessage("findString", data);
},
replaceString : function(oldToken, newToken, isCaseSensitive, isReverse)
@@ -154,18 +154,45 @@ if (!net.tschmid.sieve.editor.text.glue)
data.isCaseSensitive = isCaseSensitive;
data.isReverse = isReverse;
- this.glue.sendMessage("replaceString", data);
+ this.broker.sendMessage("replaceString", data);
},
focus : function() {
document.getElementById(this.id).focus();
- this.glue.sendMessage("focus");
+ this.broker.sendMessage("focus");
+ },
+
+ getStatus : function(callback) {
+
+ if (!callback)
+ throw "No valid callback for getStatus";
+
+ var proxy = function(msg) {
+ // we use the built in function to determin if cut, copy and paste is possible
+ var controller = document.commandDispatcher.getControllerForCommand("cmd_cut");
+ msg.canCut = controller.isCommandEnabled("cmd_cut");
+
+ controller = document.commandDispatcher.getControllerForCommand("cmd_copy");
+ msg.canCopy = controller.isCommandEnabled("cmd_copy");
+
+ controller = document.commandDispatcher.getControllerForCommand("cmd_paste");
+ msg.canPaste = controller.isCommandEnabled("cmd_paste");
+
+ callback(msg);
+ }
+
+ this.addCallback("onGetStatus", proxy);
+ this.broker.sendMessage("getStatus");
+ },
+
+ setOptions : function(options) {
+ this.broker.sendMessage("setOptions", options);
}
}
// Export the constructor...
- net.tschmid.sieve.editor.text.glue = SieveTextEditorGlue;
+ net.tschmid.sieve.editor.text.Client = SieveTextEditorClient;
}());
\ No newline at end of file
diff --git a/chrome/chromeFiles/content/editor/sieve.editor.text.js b/chrome/chromeFiles/content/editor/sieve.editor.text.service.js
similarity index 55%
rename from chrome/chromeFiles/content/editor/sieve.editor.text.js
rename to chrome/chromeFiles/content/editor/sieve.editor.text.service.js
index b9d6426..5e9f013 100644
--- a/chrome/chromeFiles/content/editor/sieve.editor.text.js
+++ b/chrome/chromeFiles/content/editor/sieve.editor.text.service.js
@@ -15,6 +15,9 @@ if (!net.tschmid.sieve.editor)
if (!net.tschmid.sieve.editor.text)
net.tschmid.sieve.editor.text = {};
+if (!net.tschmid.sieve.editor.text.service)
+ net.tschmid.sieve.editor.text.service = {};
+
// We need two global variables for backward compatiblity...
// they may be removed as soon as the editor communication is migrated to post message
var editor = null;
@@ -30,8 +33,8 @@ var onActiveLineChange = null;
var hlLine = null;
var listener = {}
- var glue = new net.tschmid.sieve.iframe.glue();
- glue.setListener(function(event, data) { listener.onMessage(event, data) });
+ var broker = new net.tschmid.sieve.Broker();
+ broker.setListener(function(event, data) { listener.onMessage(event, data) });
function winHeight() {
@@ -59,7 +62,7 @@ var onActiveLineChange = null;
}
function onChange() {
- glue.sendMessage("onChange");
+ broker.sendMessage("onChange");
}
function findString(token, isCaseSensitive, isReverse) {
@@ -121,7 +124,7 @@ var onActiveLineChange = null;
else
editor.setSelection(cursor.to(), cursor.from());
- glue.sendMessage("onStringFound", true);
+ broker.sendMessage("onStringFound", true);
return;
}
@@ -149,80 +152,141 @@ var onActiveLineChange = null;
onChange();
}
-
-
- listener.onMessage = function(event, data) {
-
- if (event == "focus") {
- editor.focus();
- }
-
+
+ function getStatus() {
+ var status = {};
+
+ status.canDelete = editor.somethingSelected();
+ status.canUndo = (editor.historySize().undo > 0);
+
+ broker.sendMessage("onGetStatus", status);
+ return;
+ }
+
+ function loadScript(data) {
// Load a new script. It will discard the current script
// the history and the cursorposition are reset to defaults.
- if (event == "loadScript") {
- editor.setValue(data);
- editor.setCursor({line:0,ch:0});
- editor.clearHistory();
+
+ editor.setValue(data);
+ editor.setCursor({line:0,ch:0});
+ editor.clearHistory();
- // ensure the active line cursor changed...
- onActiveLineChange();
+ // ensure the active line cursor changed...
+ onActiveLineChange();
- glue.sendMessage("onScriptLoaded");
-
- return;
- }
-
- // Updates the current script. The history as well as
- // the cursor position is maintained.
- if (event == "setScript") {
- editor.setValue(data);
- return;
- }
-
- if (event == "getScript") {
+ broker.sendMessage("onScriptLoaded");
+ }
+
+ function setScript(data) {
+ editor.setValue(data);
+ }
+
+ function getScript() {
// Get the current script...
var script = editor.getValue();
// ... and ensure the line endings are sanatized
script = script.replace(/\r\n|\r|\n|\u0085|\u000C|\u2028|\u2029/g,"\r\n");
- glue.sendMessage("onGetScript", script);
- return;
- }
+ broker.sendMessage("onGetScript", script);
+ }
+
+ function replaceSelection(data) {
+ editor.replaceSelection(data);
+ return;
+ }
+
+ function selectAll() {
+ editor.setSelection({line:0,ch:0},{line: editor.lineCount() - 1});
+ return;
+ }
+
+ function undo() {
+ editor.undo();
+ return;
+ }
+
+ function redo() {
+ editor.redo();
+ return;
+ }
+
+ function setOptions(options) {
+
+ if (options.indention && options.indention.width)
+ editor.setOption("indentUnit", options.indention.width);
+
+ if (options.indention && ("policy" in options.indention))
+ editor.setOption("indentWithTabs", (options.indention.policy == 1));
+
+ if (options.tab && options.tab.width)
+ editor.setOption("tabSize", options.tab.width);
+
+ if (options.tab && ("policy" in options.tab)) {
+
+ if (options.tab.policy == 1) {
+ // Keep tabs as they are
+ editor.setOptions("extraKeys", null);
+ }
+ else {
+ // insert spaces instead of tabs
+ editor.setOption("extraKeys", {
+ Tab: function(cm) {
+ var spaces = Array(cm.getOption("tabSize") + 1).join(" ");
+ cm.replaceSelection(spaces);
+ }
+ });
+ }
+ }
+ }
+
+
+ listener.onMessage = function(event, data) {
+
+ if (event == "focus") {
+ editor.focus();
+ return;
+ }
+
+ // Updates the current script. The history as well as
+ // the cursor position is maintained.
+
+ if (event == "loadScript")
+ return loadScript(data);
+
+ if (event == "setScript")
+ return setScript(data);
+
+ if (event == "getScript")
+ return getScript();
+
+ if (event == "replaceSelection")
+ return replaceSelection(data);
- if (event == "replaceSelection") {
- editor.replaceSelection(data);
- return;
- }
+ if (event == "selectAll")
+ return selectAll();
- if (event == "selectAll") {
- editor.setSelection({line:0,ch:0},{line: editor.lineCount() - 1});
- return;
- }
+ if (event == "undo")
+ return undo();
- if (event == "undo") {
- editor.undo();
- return;
- }
+ if (event == "redo")
+ return redo();
- if (event == "redo") {
- editor.redo();
- return;
- }
+ if (event == "replaceString")
+ return replaceString(data.oldToken, data.newToken, data.isSensitive, data.isReverse);
- if (event == "replaceString") {
- replaceString(data.oldToken, data.newToken, data.isSensitive, data.isReverse);
- return;
- }
+ if (event == "findString")
+ return findString(data.token, data.isCaseSensitive, data.isReverse);
+
+ if (event == "getStatus")
+ return getStatus();
- if (event == "findString") {
- findString(data.token, data.isCaseSensitive, data.isReverse);
- return;
- }
+ if (event == "setOptions")
+ return setOptions(data);
};
// Export the constructor...
- net.tschmid.sieve.editor.text.init = function() {
+ net.tschmid.sieve.editor.text.service.init = function() {
CodeMirror.on(window, "resize", function() {
document.body.getElementsByClassName("CodeMirror-fullscreen")[0]
@@ -230,9 +294,12 @@ var onActiveLineChange = null;
});
editor = CodeMirror.fromTextArea(document.getElementById("code"), {
- lineNumbers: true,
- theme: "eclipse",
- matchBrackets: true
+ lineNumbers : true,
+ theme : "eclipse",
+ matchBrackets : true,
+
+ inputStyle : "contenteditable"
+
});
setFullScreen(editor,true);
@@ -240,7 +307,8 @@ var onActiveLineChange = null;
hlLine = editor.addLineClass(0, "background", "activeline");
editor.on("cursorActivity", function() { onActiveLineChange(); });
- editor.on("change", function() { onChange(); });
+ editor.on("change", function() { onChange(); });
+
};
}());
diff --git a/chrome/chromeFiles/content/libs/CodeMirror/AUTHORS b/chrome/chromeFiles/content/libs/CodeMirror/AUTHORS
index 5d0740f..c8e17d0 100644
--- a/chrome/chromeFiles/content/libs/CodeMirror/AUTHORS
+++ b/chrome/chromeFiles/content/libs/CodeMirror/AUTHORS
@@ -24,6 +24,8 @@ Alexander Solovyov
Alexandre Bique
alexey-k
Alex Piggott
+Aliaksei Chapyzhenka
+Amin Shali
Amsul
amuntean
Amy
@@ -32,25 +34,32 @@ anaran
AndersMad
Anders Nawroth
Anderson Mesquita
+Andrea G
Andreas Reischuck
Andre von Houck
Andrey Fedorov
+Andrey Klyuchnikov
Andrey Lushnikov
Andy Joslin
Andy Kimball
Andy Li
+Angelo
angelozerr
angelo.zerr at gmail.com
+Ankit
Ankit Ahuja
Ansel Santosa
+Anthony Dugois
Anthony Grimes
Anton Kovalyov
+AQNOUCH Mohammed
areos
as3boyan
AtomicPages LLC
Atul Bhouraskar
Aurelian Oancea
Bastian Müller
+belhaj
Bem Jones-Bey
benbro
Beni Cherniavsky-Paskin
@@ -65,27 +74,36 @@ Blaine G
blukat29
boomyjee
borawjm
+Brad Metcalf
Brandon Frohs
Brandon Wamboldt
Brett Zamir
Brian Grinstead
Brian Sletten
Bruce Mitchener
+Calin Barbat
Chandra Sekhar Pydi
Charles Skelton
Cheah Chu Yeow
Chris Coyier
Chris Granger
Chris Houseknecht
+Chris Lohfink
Chris Morgan
Christian Oyarzun
+Christian Petrov
Christopher Brown
+Christopher Mitchell
+Christopher Pfohl
ciaranj
CodeAnimal
+coderaiser
ComFreek
Curtis Gagliardi
dagsta
daines
+Dale Jung
+Dan Bentley
Dan Heberden
Daniel, Dao Quang Minh
Daniele Di Sarli
@@ -95,14 +113,19 @@ Daniel KJ
Daniel Neel
Daniel Parnell
Danny Yoo
+darealshinji
Darius Roberts
Dave Myers
+David Barnett
David Mignot
David Pathakjee
+David Vázquez
deebugger
Deep Thought
+Devon Carew
dignifiedquire
Dimage Sapelkin
+Dmitry Kiselyov
domagoj412
Dominator008
Domizio Demichelis
@@ -131,19 +154,24 @@ flack
ForbesLindesay
Forbes Lindesay
Ford_Lawnmower
+Forrest Oliphant
Frank Wiegand
+Gabriel Gheorghian
Gabriel Horner
Gabriel Nahmias
galambalazs
Gautam Mehta
gekkoe
+Gerard Braad
Gergely Hegykozi
+Giovanni Calò
Glenn Jorde
Glenn Ruehle
Golevka
Gordon Smith
Grant Skinner
greengiant
+Gregory Koberger
Guillaume Massé
Guillaume Massé
Gustavo Rodrigues
@@ -151,6 +179,7 @@ Hakan Tunc
Hans Engel
Hardest
Hasan Karahan
+Hector Oswaldo Caballero
Herculano Campos
Hiroyuki Makino
hitsthings
@@ -165,9 +194,11 @@ ilvalle
Ingo Richter
Irakli Gozalishvili
Ivan Kurnosov
+Ivoah
Jacob Lee
Jakob Miland
Jakub Vrana
+Jakub Vrána
James Campos
James Thorne
Jamie Hill
@@ -176,22 +207,27 @@ jankeromnes
Jan Keromnes
Jan Odvarko
Jan T. Sott
+Jared Forsyth
Jason
+Jason Barnabe
Jason Grout
Jason Johnston
Jason San Jose
Jason Siefken
Jaydeep Solanki
Jean Boussier
+Jeff Blaisdell
jeffkenton
Jeff Pickhardt
jem (graphite)
+Jeremy Parmenter
Jochen Berger
Johan Ask
John Connor
John Lees-Miller
John Snelson
John Van Der Loo
+Jonas Döbertin
Jonathan Malmaud
jongalloway
Jon Malmaud
@@ -202,6 +238,7 @@ Joshua Newman
Josh Watzman
jots
jsoojeon
+ju1ius
Juan Benavides Romero
Jucovschi Constantin
Juho Vuori
@@ -210,7 +247,9 @@ jwallers at gmail.com
kaniga
Ken Newman
Ken Rockot
+Kevin Earls
Kevin Sawicki
+Kevin Ushey
Klaus Silveira
Koh Zi Han, Cliff
komakino
@@ -218,6 +257,8 @@ Konstantin Lopuhin
koops
ks-ifware
kubelsmieci
+KwanEsq
+Lanfei
Lanny
Laszlo Vidacs
leaf corcoran
@@ -225,11 +266,16 @@ Leonid Khachaturov
Leon Sorokin
Leonya Khachaturov
Liam Newman
+Libo Cannici
+LloydMilligan
LM
lochel
Lorenzo Stoakes
Luciano Longo
+Luke Stagner
lynschinzer
+M1cha
+Madhura Jayaratne
Maksim Lin
Maksym Taran
Malay Majithia
@@ -239,6 +285,7 @@ Marcel Gerber
Marco Aurélio
Marco Munizaga
Marcus Bointon
+Marek Rudnicki
Marijn Haverbeke
Mário Gonçalves
Mario Pietsch
@@ -247,11 +294,13 @@ Marko Bonaci
Martin Balek
Martín Gaitán
Martin Hasoň
+Martin Hunt
Mason Malone
Mateusz Paprocki
Mathias Bynens
mats cronqvist
Matthew Beale
+Matthew Rathbone
Matthias Bussonnier
Matthias BUSSONNIER
Matt McDonald
@@ -261,14 +310,19 @@ mauricio
Maximilian Hils
Maxim Kraev
Max Kirsch
+Max Schaefer
Max Xiantu
mbarkhau
Metatheos
Micah Dubinko
+Michael Grey
+Michael Kaminsky
Michael Lehenbauer
Michael Zhou
+Michal Dorner
Mighty Guava
Miguel Castillo
+mihailik
Mike
Mike Brevoort
Mike Diaz
@@ -277,31 +331,42 @@ Mike Kadin
MinRK
Miraculix87
misfo
+mkaminsky11
mloginov
+Moritz Schwörer
mps
+ms
mtaran-google
Narciso Jaramillo
Nathan Williams
ndr
nerbert
nextrevision
+ngn
nguillaumin
Ng Zhi An
Nicholas Bollweg
+Nicholas Bollweg (Nick)
+Nick Kreeger
Nick Small
Niels van Groningen
+nightwing
Nikita Beloglazov
Nikita Vasilyev
Nikolay Kostov
nilp0inter
Nisarg Jhaveri
nlwillia
+noragrossman
Norman Rzepka
+Oreoluwa Onatemowo
pablo
Page
Panupong Pasupat
paris
+Paris
Patil Arpith
+Patrick Stoica
Patrick Strawderman
Paul Garvin
Paul Ivanov
@@ -316,13 +381,19 @@ prasanthj
Prasanth J
Radek Piórkowski
Rahul
+Randall Mason
+Randy Burden
Randy Edmunds
Rasmus Erik Voel Jensen
+ray ratchup
+Ray Ratchup
Richard van der Meer
Richard Z.H. Wang
+Robert Crossfield
Roberto Abdelkader Martínez Pérez
robertop23
Robert Plummer
+Rrandom
Ruslan Osmanov
Ryan Prior
sabaca
@@ -331,10 +402,12 @@ sandeepshetty
Sander AKA Redsandro
santec
Sascha Peilicke
+satamas
satchmorun
sathyamoorthi
SCLINIC\jdecker
Scott Aikin
+Scott Goodhew
Sebastian Zaha
shaund
shaun gilchrist
@@ -343,6 +416,7 @@ sheopory
Shiv Deepak
Shmuel Englard
Shubham Jain
+silverwind
snasa
soliton4
sonson
@@ -354,9 +428,11 @@ Stefan Borsje
Steffen Beyer
Steve O'Hara
stoskov
+Sungho Kim
Taha Jahangir
Takuji Shimokawa
Tarmil
+tel
tfjgeorge
Thaddee Tyl
TheHowl
@@ -382,14 +458,17 @@ Vincent Woo
Volker Mische
wenli
Wesley Wiser
+Will Binns-Smith
William Jamieson
William Stein
Willy
Wojtek Ptak
Xavier Mendez
+Yassin N. Hassan
YNH Webdev
Yunchi Luo
Yuvi Panda
Zachary Dremann
+Zhang Hao
zziuni
魏鹏刚
diff --git a/chrome/chromeFiles/content/libs/CodeMirror/CONTRIBUTING.md b/chrome/chromeFiles/content/libs/CodeMirror/CONTRIBUTING.md
index 47006a1..6d65e41 100644
--- a/chrome/chromeFiles/content/libs/CodeMirror/CONTRIBUTING.md
+++ b/chrome/chromeFiles/content/libs/CodeMirror/CONTRIBUTING.md
@@ -7,7 +7,7 @@
## Getting help
Community discussion, questions, and informal bug reporting is done on the
-[CodeMirror Google group](http://groups.google.com/group/codemirror).
+[discuss.CodeMirror forum](http://discuss.codemirror.net).
## Submitting bug reports
@@ -17,7 +17,7 @@ reporting a bug, read these pointers.
**Note:** The issue tracker is for *bugs*, not requests for help. Questions
should be asked on the
-[CodeMirror Google group](http://groups.google.com/group/codemirror) instead.
+[discuss.CodeMirror forum](http://discuss.codemirror.net) instead.
### Reporting bugs effectively
@@ -61,7 +61,19 @@ should be asked on the
- Make sure all tests pass. Visit `test/index.html` in your browser to
run them.
- Submit a pull request
-([how to create a pull request](https://help.github.com/articles/fork-a-repo))
+([how to create a pull request](https://help.github.com/articles/fork-a-repo)).
+ Don't put more than one feature/fix in a single pull request.
+
+By contributing code to CodeMirror you
+
+ - agree to license the contributed code under CodeMirror's [MIT
+ license](http://codemirror.net/LICENSE).
+
+ - confirm that you have the right to contribute and license the code
+ in question. (Either you hold all rights on the code, or the rights
+ holder has explicitly granted the right to use it like this,
+ through a compatible open source license or through a direct
+ agreement with you.)
### Coding standards
diff --git a/chrome/chromeFiles/content/libs/CodeMirror/LICENSE b/chrome/chromeFiles/content/libs/CodeMirror/LICENSE
index d21bbea..f4a5a4a 100644
--- a/chrome/chromeFiles/content/libs/CodeMirror/LICENSE
+++ b/chrome/chromeFiles/content/libs/CodeMirror/LICENSE
@@ -1,4 +1,4 @@
-Copyright (C) 2014 by Marijn Haverbeke <marijnh at gmail.com> and others
+Copyright (C) 2015 by Marijn Haverbeke <marijnh at gmail.com> 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/chrome/chromeFiles/content/libs/CodeMirror/README.md b/chrome/chromeFiles/content/libs/CodeMirror/README.md
index 42b06f7..77824eb 100644
--- a/chrome/chromeFiles/content/libs/CodeMirror/README.md
+++ b/chrome/chromeFiles/content/libs/CodeMirror/README.md
@@ -1,11 +1,27 @@
# CodeMirror
[](https://travis-ci.org/codemirror/CodeMirror)
-[](https://www.npmjs.org/package/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.
+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.
-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)
+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](http://codemirror.net/doc/manual.html)) on the [project
+page](http://codemirror.net). For questions and discussion, use the
+[discussion forum](http://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.
diff --git a/chrome/chromeFiles/content/libs/CodeMirror/addon/edit/closebrackets.js b/chrome/chromeFiles/content/libs/CodeMirror/addon/edit/closebrackets.js
index 32eca01..7030268 100644
--- a/chrome/chromeFiles/content/libs/CodeMirror/addon/edit/closebrackets.js
+++ b/chrome/chromeFiles/content/libs/CodeMirror/addon/edit/closebrackets.js
@@ -9,27 +9,158 @@
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
- var DEFAULT_BRACKETS = "()[]{}''\"\"";
- var DEFAULT_EXPLODE_ON_ENTER = "[]{}";
- var SPACE_CHAR_REGEX = /\s/;
+ var defaults = {
+ pairs: "()[]{}''\"\"",
+ 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, 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.explode != null) explode = val.explode;
+ if (old && old != CodeMirror.Init) {
+ cm.removeKeyMap(keyMap);
+ cm.state.closeBrackets = null;
+ }
+ if (val) {
+ cm.state.closeBrackets = val;
+ cm.addKeyMap(keyMap);
}
- var map = buildKeymap(pairs);
- if (explode) map.Enter = buildExplodeHandler(explode);
- cm.addKeyMap(map);
});
+ 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];
+ }
+
+ var bind = defaults.pairs + "`";
+ var keyMap = {Backspace: handleBackspace, Enter: handleEnter};
+ for (var i = 0; i < bind.length; i++)
+ keyMap["'" + bind.charAt(i) + "'"] = handler(bind.charAt(i));
+
+ function handler(ch) {
+ return function(cm) { return handleChar(cm, ch); };
+ }
+
+ function getConfig(cm) {
+ var deflt = cm.state.closeBrackets;
+ if (!deflt) return null;
+ 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));
+ }
+ }
+
+ 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() {
+ 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 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 triples = getOption(conf, "triples");
+
+ var identical = pairs.charAt(pos + 1) == ch;
+ var ranges = cm.listSelections();
+ var opening = pos % 2 == 0;
+
+ var 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 (opening && !range.empty()) {
+ curType = "surround";
+ } else if ((identical || !opening) && next == ch) {
+ 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 &&
+ (cur.ch <= 2 || cm.getRange(Pos(cur.line, cur.ch - 3), Pos(cur.line, cur.ch - 2)) != ch)) {
+ curType = "addFour";
+ } else if (identical) {
+ if (!CodeMirror.isWordChar(next) && enteringString(cm, cur, ch)) curType = "both";
+ else return CodeMirror.Pass;
+ } else if (opening && (cm.getLine(cur.line).length == cur.ch ||
+ isClosingBracket(next, pairs) ||
+ /\s/.test(next))) {
+ 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") {
+ 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.triggerElectric(left + right);
+ cm.execCommand("goCharLeft");
+ } else if (type == "addFour") {
+ cm.replaceSelection(left + left + left + left, "before");
+ cm.execCommand("goCharRight");
+ }
+ });
+ }
+
+ function isClosingBracket(ch, pairs) {
+ var pos = pairs.lastIndexOf(ch);
+ return pos > -1 && pos % 2 == 1;
+ }
+
function charsAround(cm, pos) {
var str = cm.getRange(Pos(pos.line, pos.ch - 1),
Pos(pos.line, pos.ch + 1));
@@ -51,109 +182,4 @@
stream.start = stream.pos;
}
}
-
- function buildKeymap(pairs) {
- 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) {
- if (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 &&
- 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 buildExplodeHandler(pairs) {
- return 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;
- }
- 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);
- }
- });
- };
- }
});
diff --git a/chrome/chromeFiles/content/libs/CodeMirror/addon/edit/closetag.js b/chrome/chromeFiles/content/libs/CodeMirror/addon/edit/closetag.js
index a0bec7d..e68d52d 100644
--- a/chrome/chromeFiles/content/libs/CodeMirror/addon/edit/closetag.js
+++ b/chrome/chromeFiles/content/libs/CodeMirror/addon/edit/closetag.js
@@ -94,15 +94,15 @@
}
}
- function autoCloseSlash(cm) {
- if (cm.getOption("disableInput")) return CodeMirror.Pass;
+ function autoCloseCurrent(cm, typingSlash) {
var ranges = cm.listSelections(), replacements = [];
+ var head = typingSlash ? "/" : "</";
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 (tok.type == "string" || tok.string.charAt(0) != "<" ||
- tok.start != pos.ch - 1)
+ if (typingSlash && (tok.type == "string" || tok.string.charAt(0) != "<" ||
+ tok.start != pos.ch - 1))
return CodeMirror.Pass;
// Kludge to get around the fact that we are not in XML mode
// when completing in JS/CSS snippet in htmlmixed mode. Does not
@@ -110,16 +110,16 @@
// 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] = "/script>";
+ replacements[i] = head + "script>";
else if (cm.getMode().name == "htmlmixed" && inner.mode.name == "css")
- replacements[i] = "/style>";
+ replacements[i] = head + "style>";
else
return CodeMirror.Pass;
} else {
if (!state.context || !state.context.tagName ||
closingTagExists(cm, state.context.tagName, pos, state))
return CodeMirror.Pass;
- replacements[i] = "/" + state.context.tagName + ">";
+ replacements[i] = head + state.context.tagName + ">";
}
}
cm.replaceSelections(replacements);
@@ -129,6 +129,13 @@
cm.indentLine(ranges[i].head.line);
}
+ function autoCloseSlash(cm) {
+ if (cm.getOption("disableInput")) return CodeMirror.Pass;
+ return autoCloseCurrent(cm, true);
+ }
+
+ CodeMirror.commands.closeTag = function(cm) { return autoCloseCurrent(cm); };
+
function indexOf(collection, elt) {
if (collection.indexOf) return collection.indexOf(elt);
for (var i = 0, e = collection.length; i < e; ++i)
diff --git a/chrome/chromeFiles/content/libs/CodeMirror/addon/edit/continuelist.js b/chrome/chromeFiles/content/libs/CodeMirror/addon/edit/continuelist.js
index 9ad0a98..df5179f 100644
--- a/chrome/chromeFiles/content/libs/CodeMirror/addon/edit/continuelist.js
+++ b/chrome/chromeFiles/content/libs/CodeMirror/addon/edit/continuelist.js
@@ -11,36 +11,36 @@
})(function(CodeMirror) {
"use strict";
- var listRE = /^(\s*)([> ]+|[*+-]|(\d+)\.)(\s+)/,
- emptyListRE = /^(\s*)([> ]+|[*+-]|(\d+)\.)(\s*)$/,
- unorderedBullets = "*+-";
+ var listRE = /^(\s*)(>[> ]*|[*+-]\s|(\d+)([.)]))(\s*)/,
+ emptyListRE = /^(\s*)(>[> ]*|[*+-]|(\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;
var eolState = cm.getStateAfter(pos.line);
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);
+ if (!ranges[i].empty() || (!inList && !inQuote) || !match) {
cm.execCommand("newlineAndIndent");
return;
}
- if (cm.getLine(pos.line).match(emptyListRE)) {
+ if (emptyListRE.test(line)) {
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 = unorderedBullets.indexOf(match[2]) >= 0 || match[2].indexOf(">") >= 0
+ var indent = match[1], after = match[5];
+ var bullet = unorderedListRE.test(match[2]) || match[2].indexOf(">") >= 0
? match[2]
- : (parseInt(match[3], 10) + 1) + ".";
+ : (parseInt(match[3], 10) + 1) + match[4];
replacements[i] = "\n" + indent + bullet + after;
}
diff --git a/chrome/chromeFiles/content/libs/CodeMirror/addon/edit/matchbrackets.js b/chrome/chromeFiles/content/libs/CodeMirror/addon/edit/matchbrackets.js
index fa1ae03..70e1ae1 100644
--- a/chrome/chromeFiles/content/libs/CodeMirror/addon/edit/matchbrackets.js
+++ b/chrome/chromeFiles/content/libs/CodeMirror/addon/edit/matchbrackets.js
@@ -81,7 +81,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.
- if (ie_lt8 && cm.state.focused) cm.display.input.focus();
+ if (ie_lt8 && cm.state.focused) cm.focus();
var clear = function() {
cm.operation(function() {
diff --git a/chrome/chromeFiles/content/libs/CodeMirror/addon/search/search.js b/chrome/chromeFiles/content/libs/CodeMirror/addon/search/search.js
index c25aeda..ed3c477 100644
--- a/chrome/chromeFiles/content/libs/CodeMirror/addon/search/search.js
+++ b/chrome/chromeFiles/content/libs/CodeMirror/addon/search/search.js
@@ -18,6 +18,7 @@
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
+
function searchOverlay(query, caseInsensitive) {
if (typeof query == "string")
query = new RegExp(query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"), caseInsensitive ? "gi" : "g");
@@ -39,54 +40,90 @@
}
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));
}
+
+ function persistentDialog(cm, text, deflt, f) {
+ cm.openDialog(text, f, {
+ value: deflt,
+ selectValueOnOpen: true,
+ closeOnEnter: false,
+ onClose: function() { clearSearch(cm); }
+ });
+ }
+
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});
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 parseQuery(query) {
var isRE = query.match(/^\/(.*)\/([a-z]*)$/);
if (isRE) {
- query = new RegExp(isRE[1], isRE[2].indexOf("i") == -1 ? "" : "i");
- if (query.test("")) query = /x^/;
- } else if (query == "") {
- query = /x^/;
+ try { query = new RegExp(isRE[1], isRE[2].indexOf("i") == -1 ? "" : "i"); }
+ catch(e) {} // Not a regular expression after all, do a string search
}
+ if (typeof query == "string" ? query == "" : query.test(""))
+ query = /x^/;
return query;
}
+
var queryDialog =
'Search: <input type="text" style="width: 10em" class="CodeMirror-search-field"/> <span style="color: #888" class="CodeMirror-search-hint">(Use /re/ syntax for regexp search)</span>';
- 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) {
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);
- state.posFrom = state.posTo = cm.getCursor();
- findNext(cm, rev);
+ var q = cm.getSelection() || state.lastQuery;
+ if (persistent && cm.openDialog) {
+ persistentDialog(cm, queryDialog, q, function(query, event) {
+ CodeMirror.e_stop(event);
+ if (!query) return;
+ if (query != state.queryText) startSearch(cm, state, query);
+ findNext(cm, event.shiftKey);
});
- });
+ } else {
+ dialog(cm, queryDialog, "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() {
var state = getSearchState(cm);
var cursor = getSearchCursor(cm, state.query, rev ? state.posFrom : state.posTo);
@@ -95,23 +132,28 @@
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();
});}
+
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: <input type="text" style="width: 10em" class="CodeMirror-search-field"/> <span style="color: #888" class="CodeMirror-search-hint">(Use /re/ syntax for regexp search)</span>';
var replacementQueryDialog = 'With: <input type="text" style="width: 10em" class="CodeMirror-search-field"/>';
var doReplaceConfirm = "Replace? <button>Yes</button> <button>No</button> <button>Stop</button>";
+
function replace(cm, all) {
if (cm.getOption("readOnly")) return;
- dialog(cm, replaceQueryDialog, "Replace:", cm.getSelection(), function(query) {
+ var query = cm.getSelection() || getSearchState(cm).lastQuery;
+ dialog(cm, replaceQueryDialog, "Replace:", query, function(query) {
if (!query) return;
query = parseQuery(query);
dialog(cm, replacementQueryDialog, "Replace with:", "", function(text) {
@@ -151,6 +193,7 @@
}
CodeMirror.commands.find = function(cm) {clearSearch(cm); doSearch(cm);};
+ CodeMirror.commands.findPersistent = function(cm) {clearSearch(cm); doSearch(cm, false, true);};
CodeMirror.commands.findNext = doSearch;
CodeMirror.commands.findPrev = function(cm) {doSearch(cm, true);};
CodeMirror.commands.clearSearch = clearSearch;
diff --git a/chrome/chromeFiles/content/libs/CodeMirror/addon/search/searchcursor.js b/chrome/chromeFiles/content/libs/CodeMirror/addon/search/searchcursor.js
index 55c108b..b70242e 100644
--- a/chrome/chromeFiles/content/libs/CodeMirror/addon/search/searchcursor.js
+++ b/chrome/chromeFiles/content/libs/CodeMirror/addon/search/searchcursor.js
@@ -148,10 +148,10 @@
from: function() {if (this.atOccurrence) return this.pos.from;},
to: function() {if (this.atOccurrence) return this.pos.to;},
- replace: function(newText) {
+ replace: function(newText, origin) {
if (!this.atOccurrence) return;
var lines = CodeMirror.splitLines(newText);
- this.doc.replaceRange(lines, this.pos.from, this.pos.to);
+ 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));
}
@@ -177,9 +177,9 @@
});
CodeMirror.defineExtension("selectMatches", function(query, caseFold) {
- var ranges = [], next;
+ var ranges = [];
var cur = this.getSearchCursor(query, this.getCursor("from"), caseFold);
- while (next = cur.findNext()) {
+ while (cur.findNext()) {
if (CodeMirror.cmpPos(cur.to(), this.getCursor("to")) > 0) break;
ranges.push({anchor: cur.from(), head: cur.to()});
}
diff --git a/chrome/chromeFiles/content/libs/CodeMirror/lib/codemirror.css b/chrome/chromeFiles/content/libs/CodeMirror/lib/codemirror.css
index 68c67b1..318d25f 100644
--- a/chrome/chromeFiles/content/libs/CodeMirror/lib/codemirror.css
+++ b/chrome/chromeFiles/content/libs/CodeMirror/lib/codemirror.css
@@ -4,10 +4,7 @@
/* Set height, width, borders, and global font properties here */
font-family: monospace;
height: 300px;
-}
-.CodeMirror-scroll {
- /* Set scrolling behaviour here */
- overflow: auto;
+ color: black;
}
/* PADDING */
@@ -36,8 +33,7 @@
min-width: 20px;
text-align: right;
color: #999;
- -moz-box-sizing: content-box;
- box-sizing: content-box;
+ white-space: nowrap;
}
.CodeMirror-guttermarker { color: black; }
@@ -52,12 +48,12 @@
.CodeMirror div.CodeMirror-secondarycursor {
border-left: 1px solid silver;
}
-.CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor {
+.CodeMirror.cm-fat-cursor div.CodeMirror-cursor {
width: auto;
border: 0;
background: #7e7;
}
-.CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursors {
+.CodeMirror.cm-fat-cursor div.CodeMirror-cursors {
z-index: 1;
}
@@ -67,21 +63,22 @@
-webkit-animation: blink 1.06s steps(1) infinite;
-moz-animation: blink 1.06s steps(1) infinite;
animation: blink 1.06s steps(1) infinite;
+ background-color: #7e7;
}
@-moz-keyframes blink {
- 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 */
@@ -96,6 +93,15 @@ div.CodeMirror-overwrite div.CodeMirror-cursor {}
/* 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;}
@@ -115,20 +121,14 @@ 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-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;}
@@ -142,14 +142,13 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
the editor. You probably shouldn't touch them. */
.CodeMirror {
- line-height: 1;
position: relative;
overflow: hidden;
background: white;
- color: black;
}
.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 */
/* See overflow: hidden in .CodeMirror */
margin-bottom: -30px; margin-right: -30px;
@@ -157,14 +156,10 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
height: 100%;
outline: none; /* Prevent dragging from highlighting the element */
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;
}
/* The fake, visible scrollbars. Used to force redraw during scrolling
@@ -194,26 +189,38 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
.CodeMirror-gutters {
position: absolute; left: 0; top: 0;
- padding-bottom: 30px;
z-index: 3;
}
.CodeMirror-gutter {
white-space: normal;
height: 100%;
- -moz-box-sizing: content-box;
- box-sizing: content-box;
- padding-bottom: 30px;
- margin-bottom: -32px;
display: inline-block;
+ margin-bottom: -30px;
/* Hack to make IE7 behave */
*zoom:1;
*display:inline;
}
+.CodeMirror-gutter-wrapper {
+ position: absolute;
+ z-index: 4;
+ 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-lines {
cursor: text;
@@ -234,6 +241,7 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
z-index: 2;
position: relative;
overflow: visible;
+ -webkit-tap-highlight-color: transparent;
}
.CodeMirror-wrap pre {
word-wrap: break-word;
@@ -255,8 +263,18 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
.CodeMirror-widget {}
-.CodeMirror-wrap .CodeMirror-scroll {
- overflow-x: hidden;
+.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 {
@@ -286,6 +304,8 @@ div.CodeMirror-cursors {
.CodeMirror-selected { background: #d9d9d9; }
.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
.CodeMirror-crosshair { cursor: crosshair; }
+.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
+.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
.cm-searching {
background: #ffa;
@@ -305,5 +325,8 @@ div.CodeMirror-cursors {
}
}
+/* See issue #2901 */
+.cm-tab-wrap-hack:after { content: ''; }
+
/* Help users use markselection to safely style text background */
span.CodeMirror-selectedtext { background: none; }
diff --git a/chrome/chromeFiles/content/libs/CodeMirror/lib/codemirror.js b/chrome/chromeFiles/content/libs/CodeMirror/lib/codemirror.js
index 4f8a23b..e9e2c1f 100644
--- a/chrome/chromeFiles/content/libs/CodeMirror/lib/codemirror.js
+++ b/chrome/chromeFiles/content/libs/CodeMirror/lib/codemirror.js
@@ -23,7 +23,6 @@
// detected are enabled based on userAgent etc sniffing.
var gecko = /gecko\/\d/i.test(navigator.userAgent);
- // ie_uptoN means Internet Explorer version N or lower
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;
@@ -33,7 +32,6 @@
var chrome = /Chrome\//.test(navigator.userAgent);
var presto = /Opera\//.test(navigator.userAgent);
var safari = /Apple Computer/.test(navigator.vendor);
- var khtml = /KHTML\//.test(navigator.userAgent);
var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(navigator.userAgent);
var phantom = /PhantomJS/.test(navigator.userAgent);
@@ -67,31 +65,39 @@
setGuttersForLineNumbers(options);
var doc = options.value;
- if (typeof doc == "string") doc = new Doc(doc, options.mode);
+ if (typeof doc == "string") doc = new Doc(doc, options.mode, null, options.lineSeparator);
this.doc = doc;
- var display = this.display = new Display(place, 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) focusInput(this);
+ if (options.autofocus && !mobile) display.input.focus();
+ 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, focused: false,
+ overwrite: false,
+ delayingBlurEvent: false,
+ focused: false,
suppressEdits: false, // used to disable editing during key handlers when in readOnly mode
- pasteIncoming: false, cutIncoming: false, // help recognize paste/cut edits in readInput
+ pasteIncoming: false, cutIncoming: false, // help recognize paste/cut edits in input.poll
draggingText: false,
- highlight: new Delayed() // stores highlight worker timeout
+ highlight: new Delayed(), // stores highlight worker timeout
+ keySeq: null, // Unfinished key sequence
+ specialChars: null
};
+ var cm = this;
+
// Override magic textarea content restore that IE sometimes does
// on our hidden textarea on reload
- if (ie && ie_version < 11) setTimeout(bind(resetInput, this, true), 20);
+ if (ie && ie_version < 11) setTimeout(function() { cm.display.input.reset(true); }, 20);
registerEventHandlers(this);
ensureGlobalHandlers();
@@ -100,7 +106,7 @@
this.curOp.forceUpdate = true;
attachDoc(this, doc);
- if ((options.autofocus && !mobile) || activeElt() == display.input)
+ if ((options.autofocus && !mobile) || cm.hasFocus())
setTimeout(bind(onFocus, this), 20);
else
onBlur(this);
@@ -108,8 +114,14 @@
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";
}
// DISPLAY CONSTRUCTOR
@@ -118,32 +130,17 @@
// and content drawing. It holds references to DOM nodes and
// display-related state.
- function Display(place, doc) {
+ function Display(place, doc, input) {
var d = this;
+ this.input = input;
- // The semihidden textarea that is focused when the editor is
- // focused, and receives input.
- var input = d.input = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em; outline: none");
- // 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) input.style.width = "1000px";
- else input.setAttribute("wrap", "off");
- // If border: 0; -- iOS fails to open keyboard (issue #1287)
- if (ios) input.style.border = "1px solid black";
- input.setAttribute("autocorrect", "off"); input.setAttribute("autocapitalize", "off"); input.setAttribute("spellcheck", "false");
-
- // Wraps and hides input textarea
- d.inputDiv = elt("div", [input], null, "overflow: hidden; position: relative; width: 3px; height: 0px;");
- // The fake scrollbar elements.
- d.scrollbarH = elt("div", [elt("div", null, null, "height: 100%; min-height: 1px")], "CodeMirror-hscrollbar");
- d.scrollbarV = elt("div", [elt("div", null, null, "min-width: 1px")], "CodeMirror-vscrollbar");
// 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.
@@ -160,10 +157,11 @@
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: " + scrollerCutOff + "px; width: 1px;");
+ 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;
@@ -171,56 +169,44 @@
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.inputDiv, d.scrollbarH, d.scrollbarV,
- d.scrollbarFiller, d.gutterFiller, d.scroller], "CodeMirror");
+ d.wrapper = elt("div", [d.scrollbarFiller, d.gutterFiller, d.scroller], "CodeMirror");
// Work around IE7 z-index bug (not perfect, hence IE7 not really being supported)
if (ie && ie_version < 8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; }
- // Needed to hide big blue blinking cursor on Mobile Safari
- if (ios) input.style.width = "0px";
- if (!webkit) d.scroller.draggable = true;
- // Needed to handle Tab key in KHTML
- if (khtml) { d.inputDiv.style.height = "1px"; d.inputDiv.style.position = "absolute"; }
- // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8).
- if (ie && ie_version < 8) d.scrollbarH.style.minHeight = d.scrollbarV.style.minWidth = "18px";
+ if (!webkit && !(gecko && mobile)) d.scroller.draggable = true;
- if (place.appendChild) place.appendChild(d.wrapper);
- else place(d.wrapper);
+ if (place) {
+ if (place.appendChild) place.appendChild(d.wrapper);
+ else place(d.wrapper);
+ }
// Current rendered range (may be bigger than the view window).
d.viewFrom = d.viewTo = doc.first;
+ d.reportedViewFrom = d.reportedViewTo = doc.first;
// Information about the rendered lines.
d.view = [];
+ d.renderedView = null;
// Holds info about a single rendered line when it was rendered
// for measurement, while not in view.
d.externalMeasured = null;
// Empty space (in pixels) above the view
d.viewOffset = 0;
- d.lastSizeC = 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;
- // See readInput and resetInput
- d.prevInput = "";
// 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;
- // Flag that indicates whether we expect input to appear real soon
- // now (after some event like 'keypress' or 'input') and are
- // polling intensively.
- d.pollingFast = false;
- // Self-resetting timeout for the poller
- d.poll = new Delayed();
d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null;
- // Tracks when resetInput has punted to just putting a short
- // string into the textarea instead of the full selection.
- d.inaccurateSelection = false;
-
// Tracks the maximum line length so that the horizontal scrollbar
// can be kept static when scrolling.
d.maxLine = null;
@@ -236,6 +222,10 @@
// Used to track whether anything happened since the context menu
// was opened.
d.selForContextMenu = null;
+
+ d.activeTouch = null;
+
+ input.init(d);
}
// STATE UPDATES
@@ -262,6 +252,7 @@
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);
@@ -301,12 +292,6 @@
});
}
- function keyMapChanged(cm) {
- 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 : "");
- }
-
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-");
@@ -339,7 +324,6 @@
function updateGutterSpace(cm) {
var width = cm.display.gutters.offsetWidth;
cm.display.sizer.style.marginLeft = width + "px";
- cm.display.scrollbarH.style.left = cm.options.fixedGutter ? width + "px" : 0;
}
// Compute the character length of a line, taking into account
@@ -392,78 +376,167 @@
// SCROLLBARS
- function hScrollbarTakesSpace(cm) {
- return cm.display.scroller.clientHeight - cm.display.wrapper.clientHeight < scrollerCutOff - 3;
- }
-
// Prepare DOM reads needed to update the scrollbars. Done in one
// shot to minimize update/measure roundtrips.
function measureForScrollbars(cm) {
- var scroll = cm.display.scroller;
+ var d = cm.display, gutterW = d.gutters.offsetWidth;
+ var docH = Math.round(cm.doc.height + paddingVert(cm.display));
return {
- clientHeight: scroll.clientHeight,
- barHeight: cm.display.scrollbarV.clientHeight,
- scrollWidth: scroll.scrollWidth, clientWidth: scroll.clientWidth,
- hScrollbarTakesSpace: hScrollbarTakesSpace(cm),
- barWidth: cm.display.scrollbarH.clientWidth,
- docHeight: Math.round(cm.doc.height + paddingVert(cm.display))
+ 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
};
}
- // Re-synchronize the fake scrollbars with the actual size of the
- // content.
+ 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;
+ // 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";
+ }
+
+ 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);
+ // 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 updateScrollbars(cm, measure) {
if (!measure) measure = measureForScrollbars(cm);
- var d = cm.display, sWidth = scrollbarWidth(d.measure);
- var scrollHeight = measure.docHeight + scrollerCutOff;
- var needsH = measure.scrollWidth > measure.clientWidth;
- if (needsH && measure.scrollWidth <= measure.clientWidth + 1 &&
- sWidth > 0 && !measure.hScrollbarTakesSpace)
- needsH = false; // (Issue #2562)
- var needsV = scrollHeight > measure.clientHeight;
-
- if (needsV) {
- d.scrollbarV.style.display = "block";
- d.scrollbarV.style.bottom = needsH ? sWidth + "px" : "0";
- // A bug in IE8 can cause this value to be negative, so guard it.
- d.scrollbarV.firstChild.style.height =
- Math.max(0, scrollHeight - measure.clientHeight + (measure.barHeight || d.scrollbarV.clientHeight)) + "px";
- } else {
- d.scrollbarV.style.display = "";
- d.scrollbarV.firstChild.style.height = "0";
- }
- if (needsH) {
- d.scrollbarH.style.display = "block";
- d.scrollbarH.style.right = needsV ? sWidth + "px" : "0";
- d.scrollbarH.firstChild.style.width =
- (measure.scrollWidth - measure.clientWidth + (measure.barWidth || d.scrollbarH.clientWidth)) + "px";
- } else {
- d.scrollbarH.style.display = "";
- d.scrollbarH.firstChild.style.width = "0";
+ 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;
}
- if (needsH && needsV) {
+ }
+
+ // Re-synchronize the fake scrollbars with the actual size of the
+ // content.
+ function updateScrollbarsInner(cm, measure) {
+ var d = cm.display;
+ var sizes = d.scrollbars.update(measure);
+
+ d.sizer.style.paddingRight = (d.barWidth = sizes.right) + "px";
+ d.sizer.style.paddingBottom = (d.barHeight = sizes.bottom) + "px";
+
+ if (sizes.right && sizes.bottom) {
d.scrollbarFiller.style.display = "block";
- d.scrollbarFiller.style.height = d.scrollbarFiller.style.width = sWidth + "px";
+ d.scrollbarFiller.style.height = sizes.bottom + "px";
+ d.scrollbarFiller.style.width = sizes.right + "px";
} else d.scrollbarFiller.style.display = "";
- if (needsH && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) {
+ if (sizes.bottom && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) {
d.gutterFiller.style.display = "block";
- d.gutterFiller.style.height = sWidth + "px";
- d.gutterFiller.style.width = d.gutters.offsetWidth + "px";
+ d.gutterFiller.style.height = sizes.bottom + "px";
+ d.gutterFiller.style.width = measure.gutterWidth + "px";
} else d.gutterFiller.style.display = "";
-
- if (!cm.state.checkedOverlayScrollbar && measure.clientHeight > 0) {
- if (sWidth === 0) {
- var w = mac && !mac_geMountainLion ? "12px" : "18px";
- d.scrollbarV.style.minWidth = d.scrollbarH.style.minHeight = w;
- var barMouseDown = function(e) {
- if (e_target(e) != d.scrollbarV && e_target(e) != d.scrollbarH)
- operation(cm, onMouseDown)(e);
- };
- on(d.scrollbarV, "mousedown", barMouseDown);
- on(d.scrollbarH, "mousedown", barMouseDown);
- }
- cm.state.checkedOverlayScrollbar = true;
- }
}
// Compute the lines that are visible in a given viewport (defaults
@@ -479,12 +552,13 @@
// 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)
- return {from: ensureFrom,
- to: lineAtHeight(doc, heightAtLine(getLine(doc, ensureFrom)) + display.wrapper.clientHeight)};
- if (Math.min(ensureTo, doc.lastLine()) >= to)
- return {from: lineAtHeight(doc, heightAtLine(getLine(doc, ensureTo)) - display.wrapper.clientHeight),
- to: ensureTo};
+ 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)};
}
@@ -520,7 +594,7 @@
"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.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";
@@ -551,10 +625,31 @@
this.visible = visibleLines(display, cm.doc, viewport);
this.editorIsHidden = !display.wrapper.offsetWidth;
this.wrapperHeight = display.wrapper.clientHeight;
- this.oldViewFrom = display.viewFrom; this.oldViewTo = display.viewTo;
- this.oldScrollerWidth = display.scroller.clientWidth;
+ 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;
+ }
}
// Does the actual updating of the line display. Bails out
@@ -562,6 +657,7 @@
// false.
function updateDisplayIfNeeded(cm, update) {
var display = cm.display, doc = cm.doc;
+
if (update.editorIsHidden) {
resetView(cm);
return false;
@@ -571,7 +667,7 @@
if (!update.force &&
update.visible.from >= display.viewFrom && update.visible.to <= display.viewTo &&
(display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo) &&
- countDirtyView(cm) == 0)
+ display.renderedView == display.view && countDirtyView(cm) == 0)
return false;
if (maybeUpdateLineNumberWidth(cm)) {
@@ -591,7 +687,7 @@
}
var different = from != display.viewFrom || to != display.viewTo ||
- display.lastSizeC != update.wrapperHeight;
+ display.lastWrapHeight != update.wrapperHeight || display.lastWrapWidth != update.wrapperWidth;
adjustView(cm, from, to);
display.viewOffset = heightAtLine(getLine(cm.doc, display.viewFrom));
@@ -599,7 +695,7 @@
cm.display.mover.style.top = display.viewOffset + "px";
var toUpdate = countDirtyView(cm);
- if (!different && toUpdate == 0 && !update.force &&
+ if (!different && toUpdate == 0 && !update.force && display.renderedView == display.view &&
(display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo))
return false;
@@ -609,17 +705,20 @@
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();
// Prevent selection and cursors from interfering with the scroll
- // width.
+ // width and height.
removeChildren(display.cursorDiv);
removeChildren(display.selectionDiv);
+ display.gutters.style.height = display.sizer.style.minHeight = 0;
if (different) {
- display.lastSizeC = update.wrapperHeight;
+ display.lastWrapHeight = update.wrapperHeight;
+ display.lastWrapWidth = update.wrapperWidth;
startWorker(cm, 400);
}
@@ -629,16 +728,12 @@
}
function postUpdateDisplay(cm, update) {
- var force = update.force, viewport = update.viewport;
+ var viewport = update.viewport;
for (var first = true;; first = false) {
- if (first && cm.options.lineWrapping && update.oldScrollerWidth != cm.display.scroller.clientWidth) {
- force = true;
- } else {
- force = 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) - scrollerCutOff -
- cm.display.scroller.clientHeight, viewport.top)};
+ 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);
@@ -653,9 +748,11 @@
updateScrollbars(cm, barMeasure);
}
- signalLater(cm, "update", cm);
- if (cm.display.viewFrom != update.oldViewFrom || cm.display.viewTo != update.oldViewTo)
- signalLater(cm, "viewportChange", cm, cm.display.viewFrom, cm.display.viewTo);
+ 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) {
@@ -667,21 +764,15 @@
updateSelection(cm);
setDocumentHeight(cm, barMeasure);
updateScrollbars(cm, barMeasure);
+ update.finish();
}
}
function setDocumentHeight(cm, measure) {
- cm.display.sizer.style.minHeight = cm.display.heightForcer.style.top = measure.docHeight + "px";
- cm.display.gutters.style.height = Math.max(measure.docHeight, measure.clientHeight - scrollerCutOff) + "px";
- }
-
- function checkForWebkitWidthBug(cm, measure) {
- // Work around Webkit bug where it sometimes reserves space for a
- // non-existing phantom scrollbar in the scroller (Issue #2420)
- if (cm.display.sizer.offsetWidth + cm.display.gutters.offsetWidth < cm.display.scroller.clientWidth - 1) {
- cm.display.sizer.style.minHeight = cm.display.heightForcer.style.top = "0px";
- cm.display.gutters.style.height = measure.docHeight + "px";
- }
+ 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";
}
// Read the actual heights of the rendered lines, and update their
@@ -758,7 +849,7 @@
for (var i = 0; i < view.length; i++) {
var lineView = view[i];
if (lineView.hidden) {
- } else if (!lineView.node) { // Not drawn yet
+ } 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
@@ -789,7 +880,7 @@
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(lineView, dims);
+ else if (type == "widget") updateLineWidgets(cm, lineView, dims);
}
lineView.changes = null;
}
@@ -864,13 +955,26 @@
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");
+ wrap.insertBefore(lineView.gutterBackground, lineView.text);
+ }
var markers = lineView.line.gutterMarkers;
if (cm.options.lineNumbers || markers) {
var wrap = ensureLineWrapped(lineView);
- var gutterWrap = lineView.gutter =
- wrap.insertBefore(elt("div", null, "CodeMirror-gutter-wrapper", "position: absolute; left: " +
- (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px"),
- lineView.text);
+ var gutterWrap = lineView.gutter = elt("div", null, "CodeMirror-gutter-wrapper", "left: " +
+ (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px");
+ cm.display.input.setUneditable(gutterWrap);
+ wrap.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),
@@ -886,14 +990,14 @@
}
}
- function updateLineWidgets(lineView, dims) {
+ 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);
}
- insertLineWidgets(lineView, dims);
+ insertLineWidgets(cm, lineView, dims);
}
// Build a line's DOM representation from scratch
@@ -905,25 +1009,26 @@
updateLineClasses(lineView);
updateLineGutter(cm, lineView, lineN, dims);
- insertLineWidgets(lineView, 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(lineView, dims) {
- insertLineWidgetsFor(lineView.line, lineView, dims, true);
+ 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(lineView.rest[i], lineView, dims, false);
+ insertLineWidgetsFor(cm, lineView.rest[i], lineView, dims, false);
}
- function insertLineWidgetsFor(line, lineView, dims, allowAbove) {
+ 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.ignoreEvents = true;
+ 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
@@ -966,6 +1071,904 @@
function maxPos(a, b) { return cmp(a, b) < 0 ? b : a; }
function minPos(a, b) { return cmp(a, b) < 0 ? a : b; }
+ // INPUT HANDLING
+
+ function ensureFocus(cm) {
+ if (!cm.state.focused) { cm.display.input.focus(); onFocus(cm); }
+ }
+
+ function isReadOnly(cm) {
+ return cm.options.readOnly || cm.doc.cantEdit;
+ }
+
+ // 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;
+
+ function applyTextInput(cm, inserted, deleted, sel, origin) {
+ var doc = cm.doc;
+ cm.display.shift = false;
+ if (!sel) sel = doc.sel;
+
+ var paste = cm.state.pasteIncoming || origin == "paste";
+ var textLines = doc.splitLines(inserted), multiPaste = null;
+ // When pasing N lines into N selections, insert one line per selection
+ if (paste && sel.ranges.length > 1) {
+ if (lastCopied && lastCopied.join("\n") == inserted) {
+ if (sel.ranges.length % lastCopied.length == 0) {
+ multiPaste = [];
+ for (var i = 0; i < lastCopied.length; i++)
+ multiPaste.push(doc.splitLines(lastCopied[i]));
+ }
+ } 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 && !paste) // 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: origin || (paste ? "paste" : cm.state.cutIncoming ? "cut" : "+input")};
+ makeChange(cm.doc, changeEvent);
+ signalLater(cm, "inputRead", cm, changeEvent);
+ }
+ if (inserted && !paste)
+ triggerElectric(cm, inserted);
+
+ ensureCursorVisible(cm);
+ cm.curOp.updateInput = updateInput;
+ cm.curOp.typing = true;
+ cm.state.pasteIncoming = cm.state.cutIncoming = false;
+ }
+
+ function handlePaste(e, cm) {
+ var pasted = e.clipboardData && e.clipboardData.getData("text/plain");
+ if (pasted) {
+ e.preventDefault();
+ runInOp(cm, function() { applyTextInput(cm, pasted, 0, null, "paste"); });
+ return true;
+ }
+ }
+
+ function triggerElectric(cm, inserted) {
+ // When an 'electric' character is inserted, immediately trigger a reindent
+ if (!cm.options.electricChars || !cm.options.smartIndent) return;
+ var sel = cm.doc.sel;
+
+ for (var i = sel.ranges.length - 1; i >= 0; i--) {
+ var range = sel.ranges[i];
+ if (range.head.ch > 100 || (i && sel.ranges[i - 1].head.line == range.head.line)) continue;
+ var mode = cm.getModeAt(range.head);
+ var indented = false;
+ if (mode.electricChars) {
+ for (var j = 0; j < mode.electricChars.length; j++)
+ if (inserted.indexOf(mode.electricChars.charAt(j)) > -1) {
+ indented = indentLine(cm, range.head.line, "smart");
+ break;
+ }
+ } else if (mode.electricInput) {
+ if (mode.electricInput.test(getLine(cm.doc, range.head.line).text.slice(0, range.head.ch)))
+ indented = indentLine(cm, range.head.line, "smart");
+ }
+ if (indented) signalLater(cm, "electricInput", cm, range.head.line);
+ }
+ }
+
+ function copyableRanges(cm) {
+ var text = [], ranges = [];
+ for (var i = 0; i < cm.doc.sel.ranges.length; i++) {
+ var line = cm.doc.sel.ranges[i].head.line;
+ var lineRange = {anchor: Pos(line, 0), head: Pos(line + 1, 0)};
+ ranges.push(lineRange);
+ text.push(cm.getRange(lineRange.anchor, lineRange.head));
+ }
+ return {text: text, ranges: ranges};
+ }
+
+ function disableBrowserMagic(field) {
+ field.setAttribute("autocorrect", "off");
+ field.setAttribute("autocapitalize", "off");
+ field.setAttribute("spellcheck", "false");
+ }
+
+ // TEXTAREA INPUT STYLE
+
+ function TextareaInput(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();
+ // 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;
+ this.composing = null;
+ };
+
+ 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;
+ }
+
+ TextareaInput.prototype = copyObj({
+ init: function(display) {
+ var input = this, cm = this.cm;
+
+ // Wraps and hides input textarea
+ var div = this.wrapper = hiddenTextarea();
+ // The semihidden textarea that is focused when the editor is
+ // focused, and receives input.
+ var te = this.textarea = div.firstChild;
+ display.wrapper.insertBefore(div, display.wrapper.firstChild);
+
+ // Needed to hide big blue blinking cursor on Mobile Safari (doesn't seem to work in iOS 8 anymore)
+ if (ios) te.style.width = "0px";
+
+ on(te, "input", function() {
+ if (ie && ie_version >= 9 && input.hasSelection) input.hasSelection = null;
+ input.poll();
+ });
+
+ on(te, "paste", function(e) {
+ if (handlePaste(e, cm)) return 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 if (!cm.options.lineWiseCopyCut) {
+ return;
+ } 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();
+ });
+
+ // Prevent normal selection in the editor (we handle our own)
+ on(display.lineSpace, "selectstart", function(e) {
+ if (!eventInWidget(display, e)) e_preventDefault(e);
+ });
+
+ on(te, "compositionstart", function() {
+ var start = cm.getCursor("from");
+ 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;
+ }
+ });
+ },
+
+ 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;
+ },
+
+ showSelection: function(drawn) {
+ var cm = this.cm, display = cm.display;
+ removeChildrenAndAdd(display.cursorDiv, drawn.cursors);
+ removeChildrenAndAdd(display.selectionDiv, drawn.selection);
+ if (drawn.teTop != null) {
+ this.wrapper.style.top = drawn.teTop + "px";
+ this.wrapper.style.left = drawn.teLeft + "px";
+ }
+ },
+
+ // Reset the input to correspond to the selection (or to be empty,
+ // when not typing and nothing is selected)
+ 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) {} // IE8 will throw if the textarea is display: none or not in DOM
+ }
+ },
+
+ blur: function() { this.textarea.blur(); },
+
+ resetPosition: function() {
+ this.wrapper.style.top = this.wrapper.style.left = 0;
+ },
+
+ receivedFocus: function() { this.slowPoll(); },
+
+ // 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();
+ });
+ },
+
+ // 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);
+ },
+
+ // 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 (this.contextMenuPending || !cm.state.focused ||
+ (hasSelection(input) && !prevInput && !this.composing) ||
+ isReadOnly(cm) || 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;
+
+ var self = this;
+ runInOp(cm, function() {
+ applyTextInput(cm, text.slice(same), prevInput.length - same,
+ null, self.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 = self.prevInput = "";
+ else self.prevInput = text;
+
+ if (self.composing) {
+ self.composing.range.clear();
+ self.composing.range = cm.markText(self.composing.start, cm.getCursor("to"),
+ {className: "CodeMirror-composing"});
+ }
+ });
+ 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 = "\u200b" + (selected ? te.value : "");
+ te.value = "\u21da"; // Used to catch context-menu undo
+ te.value = extval;
+ input.prevInput = selected ? "" : "\u200b";
+ te.selectionStart = 1; te.selectionEnd = extval.length;
+ // Re-set this, in case some other handler touched the
+ // selection in the meantime.
+ display.selForContextMenu = cm.doc.sel;
+ }
+ }
+ function rehide() {
+ input.contextMenuPending = false;
+ input.wrapper.style.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 &&
+ te.selectionEnd > 0 && input.prevInput == "\u200b")
+ 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);
+
+ // CONTENTEDITABLE INPUT STYLE
+
+ function ContentEditableInput(cm) {
+ this.cm = cm;
+ this.lastAnchorNode = this.lastAnchorOffset = this.lastFocusNode = this.lastFocusOffset = null;
+ this.polling = new Delayed();
+ this.gracePeriod = false;
+ }
+
+ 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) { handlePaste(e, cm); })
+
+ 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);
+ });
+
+ 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 if (!cm.options.lineWiseCopyCut) {
+ return;
+ } 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);
+ else if (gecko) this.startGracePeriod();
+ }
+ this.rememberSelection();
+ },
+
+ startGracePeriod: function() {
+ var input = this;
+ clearTimeout(this.gracePeriod);
+ this.gracePeriod = setTimeout(function() {
+ input.gracePeriod = false;
+ if (input.selectionChanged())
+ input.cm.operation(function() { input.cm.curOp.selectionChanged = true; });
+ }, 20);
+ },
+
+ 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);
+ },
+
+ selectionChanged: function() {
+ var sel = window.getSelection();
+ return sel.anchorNode != this.lastAnchorNode || sel.anchorOffset != this.lastAnchorOffset ||
+ sel.focusNode != this.lastFocusNode || sel.focusOffset != this.lastFocusOffset;
+ },
+
+ pollSelection: function() {
+ if (!this.composing && !this.gracePeriod && this.selectionChanged()) {
+ var sel = window.getSelection(), cm = this.cm;
+ 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;
+ }
+ var toIndex = findViewIndex(cm, to.line);
+ if (toIndex == display.view.length - 1) {
+ var toLine = display.viewTo - 1;
+ var toNode = display.lineDiv.lastChild;
+ } else {
+ var toLine = lineNo(display.view[toIndex + 1].line) - 1;
+ var toNode = display.view[toIndex + 1].node.previousSibling;
+ }
+
+ 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;
+
+ 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;
+ }
+ },
+
+ 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, side);
+ 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 < cm.display.view.length; i++) {
+ var lineView = cm.display.view[i];
+ if (lineView.node == lineNode)
+ return locateNodeInLineView(lineView, node, offset);
+ }
+ }
+
+ function locateNodeInLineView(lineView, node, offset) {
+ var wrapper = lineView.text.firstChild, bad = false;
+ if (!node || !contains(wrapper, node)) return badPos(Pos(lineNo(lineView.line), 0), true);
+ if (node == wrapper) {
+ bad = true;
+ node = wrapper.childNodes[offset];
+ offset = 0;
+ if (!node) {
+ var line = lineView.rest ? lst(lineView.rest) : lineView.line;
+ return badPos(Pos(lineNo(line), line.text.length), bad);
+ }
+ }
+
+ var textNode = node.nodeType == 3 ? node : null, topNode = node;
+ if (!textNode && node.childNodes.length == 1 && node.firstChild.nodeType == 3) {
+ textNode = node.firstChild;
+ if (offset) offset = textNode.nodeValue.length;
+ }
+ while (topNode.parentNode != wrapper) topNode = topNode.parentNode;
+ var measure = lineView.measure, maps = measure.maps;
+
+ function find(textNode, topNode, offset) {
+ for (var i = -1; i < (maps ? maps.length : 0); i++) {
+ var map = i < 0 ? measure.map : maps[i];
+ for (var j = 0; j < map.length; j += 3) {
+ var curNode = map[j + 2];
+ if (curNode == textNode || curNode == topNode) {
+ var line = lineNo(i < 0 ? lineView.line : lineView.rest[i]);
+ var ch = map[j] + offset;
+ if (offset < 0 || curNode != textNode) ch = map[j + (offset ? 1 : 0)];
+ return Pos(line, ch);
+ }
+ }
+ }
+ }
+ var found = find(textNode, topNode, offset);
+ if (found) return badPos(found, bad);
+
+ // FIXME this is all really shaky. might handle the few cases it needs to handle, but likely to cause problems
+ for (var after = topNode.nextSibling, dist = textNode ? textNode.nodeValue.length - offset : 0; after; after = after.nextSibling) {
+ found = find(after, after.firstChild, 0);
+ if (found)
+ return badPos(Pos(found.line, found.ch - dist), bad);
+ else
+ dist += after.textContent.length;
+ }
+ for (var before = topNode.previousSibling, dist = 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;
+ }
+ }
+
+ function domTextBetween(cm, from, to, fromLine, toLine) {
+ var text = "", closing = false, lineSep = cm.doc.lineSeparator();
+ function recognizeMarker(id) { return function(marker) { return marker.id == id; }; }
+ function 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(lineSep);
+ 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 += lineSep;
+ closing = false;
+ }
+ text += val;
+ }
+ }
+ for (;;) {
+ walk(from);
+ if (from == to) break;
+ from = from.nextSibling;
+ }
+ return text;
+ }
+
+ CodeMirror.inputStyles = {"textarea": TextareaInput, "contenteditable": ContentEditableInput};
+
// SELECTION / CURSOR
// Selection objects are immutable. A new one is created every time
@@ -1253,13 +2256,17 @@
// SELECTION DRAWING
- // Redraw the selection and/or cursor
- function drawSelection(cm) {
- var display = cm.display, doc = cm.doc, result = {};
+ function updateSelection(cm) {
+ cm.display.input.showSelection(cm.display.input.prepareSelection());
+ }
+
+ function prepareSelection(cm, primary) {
+ var doc = cm.doc, result = {};
var curFragment = result.cursors = document.createDocumentFragment();
var selFragment = result.selection = document.createDocumentFragment();
for (var i = 0; i < doc.sel.ranges.length; i++) {
+ if (primary === false && i == doc.sel.primIndex) continue;
var range = doc.sel.ranges[i];
var collapsed = range.empty();
if (collapsed || cm.options.showCursorWhenSelecting)
@@ -1267,33 +2274,9 @@
if (!collapsed)
drawSelectionRange(cm, range, selFragment);
}
-
- // 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 showSelection(cm, drawn) {
- removeChildrenAndAdd(cm.display.cursorDiv, drawn.cursors);
- removeChildrenAndAdd(cm.display.selectionDiv, drawn.selection);
- if (drawn.teTop != null) {
- cm.display.inputDiv.style.top = drawn.teTop + "px";
- cm.display.inputDiv.style.left = drawn.teLeft + "px";
- }
- }
-
- function updateSelection(cm) {
- showSelection(cm, drawSelection(cm));
- }
-
// Draws a cursor for the given range
function drawSelectionCursor(cm, range, output) {
var pos = cursorCoords(cm, range.head, "div", null, null, !cm.options.singleCursorHeightPerLine);
@@ -1317,7 +2300,8 @@
function drawSelectionRange(cm, range, output) {
var display = cm.display, doc = cm.doc;
var fragment = document.createDocumentFragment();
- var padding = paddingH(cm.display), leftSide = padding.left, rightSide = display.lineSpace.offsetWidth - padding.right;
+ var padding = paddingH(cm.display), leftSide = padding.left;
+ var rightSide = Math.max(display.sizerWidth, displayWidth(cm) - display.sizer.offsetLeft) - padding.right;
function add(left, top, width, bottom) {
if (top < 0) top = 0;
@@ -1496,13 +2480,21 @@
return data;
}
+ function scrollGap(cm) { return scrollerGap - cm.display.nativeBarWidth; }
+ function displayWidth(cm) {
+ return cm.display.scroller.clientWidth - scrollGap(cm) - cm.display.barWidth;
+ }
+ function displayHeight(cm) {
+ return cm.display.scroller.clientHeight - scrollGap(cm) - cm.display.barHeight;
+ }
+
// Ensure the lineView.wrapping.heights array is populated. This is
// an array of bottom offsets for the lines that make up a drawn
// line. When lineWrapping is on, there might be more than one
// height.
function ensureLineHeights(cm, lineView, rect) {
var wrapping = cm.options.lineWrapping;
- var curWidth = wrapping && cm.display.scroller.clientWidth;
+ var curWidth = wrapping && displayWidth(cm);
if (!lineView.measure.heights || wrapping && lineView.measure.width != curWidth) {
var heights = lineView.measure.heights = [];
if (wrapping) {
@@ -1568,10 +2560,12 @@
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);
@@ -1607,9 +2601,7 @@
var nullRect = {left: 0, right: 0, top: 0, bottom: 0};
- function measureCharInner(cm, prepared, ch, bias) {
- var map = prepared.map;
-
+ function nodeAndOffsetInLineMap(map, ch, bias) {
var node, start, end, collapse;
// First, search the line map for the text node corresponding to,
// or closest to, the target character.
@@ -1643,13 +2635,19 @@
break;
}
}
+ return {node: node, start: start, end: end, collapse: collapse, coverStart: mStart, coverEnd: mEnd};
+ }
+
+ function measureCharInner(cm, prepared, ch, bias) {
+ var place = nodeAndOffsetInLineMap(prepared.map, ch, bias);
+ var node = place.node, start = place.start, end = place.end, collapse = place.collapse;
var rect;
if (node.nodeType == 3) { // If it is a text node, use a range to retrieve the coordinates.
for (var i = 0; i < 4; i++) { // Retry a maximum of 4 times when nonsense rectangles are returned
- while (start && isExtendingChar(prepared.line.text.charAt(mStart + start))) --start;
- while (mStart + end < mEnd && isExtendingChar(prepared.line.text.charAt(mStart + end))) ++end;
- if (ie && ie_version < 9 && start == 0 && end == mEnd - mStart) {
+ 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();
@@ -1737,7 +2735,8 @@
// 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), or "page".
+ // "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]);
@@ -1960,6 +2959,7 @@
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
};
if (operationGroup) {
@@ -2020,6 +3020,7 @@
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 ||
@@ -2045,12 +3046,14 @@
// 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;
- op.maxScrollLeft = Math.max(0, display.sizer.offsetLeft + op.adjustWidthTo +
- scrollerCutOff - display.scroller.clientWidth);
+ 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.newSelectionNodes = drawSelection(cm);
+ op.preparedSelection = display.input.prepareSelection();
}
function endOperation_W2(op) {
@@ -2063,8 +3066,8 @@
cm.display.maxLineChanged = false;
}
- if (op.newSelectionNodes)
- showSelection(cm, op.newSelectionNodes);
+ if (op.preparedSelection)
+ cm.display.input.showSelection(op.preparedSelection);
if (op.updatedDisplay)
setDocumentHeight(cm, op.barMeasure);
if (op.updatedDisplay || op.startHeight != cm.doc.height)
@@ -2073,15 +3076,13 @@
if (op.selectionChanged) restartBlink(cm);
if (cm.state.focused && op.updateInput)
- resetInput(cm, op.typing);
+ cm.display.input.reset(op.typing);
+ if (op.focus && op.focus == activeElt()) ensureFocus(op.cm);
}
function endOperation_finish(op) {
var cm = op.cm, display = cm.display, doc = cm.doc;
- if (op.adjustWidthTo != null && Math.abs(op.barMeasure.scrollWidth - cm.display.scroller.scrollWidth) > 1)
- updateScrollbars(cm);
-
if (op.updatedDisplay) postUpdateDisplay(cm, op.update);
// Abort mouse wheel delta measurement, when scrolling explicitly
@@ -2090,12 +3091,14 @@
// Propagate the scroll position to the actual DOM scroller
if (op.scrollTop != null && (display.scroller.scrollTop != op.scrollTop || op.forceScroll)) {
- var top = Math.max(0, Math.min(display.scroller.scrollHeight - display.scroller.clientHeight, op.scrollTop));
- display.scroller.scrollTop = display.scrollbarV.scrollTop = doc.scrollTop = top;
+ 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)) {
- var left = Math.max(0, Math.min(display.scroller.scrollWidth - display.scroller.clientWidth, op.scrollLeft));
- display.scroller.scrollLeft = display.scrollbarH.scrollLeft = doc.scrollLeft = left;
+ 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.
@@ -2116,19 +3119,11 @@
if (display.wrapper.offsetHeight)
doc.scrollTop = cm.display.scroller.scrollTop;
- // Apply workaround for two webkit bugs
- if (op.updatedDisplay && webkit) {
- if (cm.options.lineWrapping)
- checkForWebkitWidthBug(cm, op.barMeasure); // (Issue #2420)
- if (op.barMeasure.scrollWidth > op.barMeasure.clientWidth &&
- op.barMeasure.scrollWidth < op.barMeasure.clientWidth + 1 &&
- !hScrollbarTakesSpace(cm))
- updateScrollbars(cm); // (Issue #2562)
- }
-
// 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
@@ -2354,166 +3349,6 @@
return dirty;
}
- // INPUT HANDLING
-
- // Poll for input changes, using the normal rate of polling. This
- // runs as long as the editor is focused.
- function slowPoll(cm) {
- if (cm.display.pollingFast) return;
- cm.display.poll.set(cm.options.pollInterval, function() {
- readInput(cm);
- if (cm.state.focused) slowPoll(cm);
- });
- }
-
- // 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.
- function fastPoll(cm) {
- var missed = false;
- cm.display.pollingFast = true;
- function p() {
- var changed = readInput(cm);
- if (!changed && !missed) {missed = true; cm.display.poll.set(60, p);}
- else {cm.display.pollingFast = false; slowPoll(cm);}
- }
- cm.display.poll.set(20, p);
- }
-
- // 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;
-
- // 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).
- function readInput(cm) {
- var input = cm.display.input, prevInput = cm.display.prevInput, doc = cm.doc;
- // 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)
- 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 && cm.display.inputHasSelection === text ||
- mac && /[\uf700-\uf7ff]/.test(text)) {
- resetInput(cm);
- return false;
- }
-
- var withOp = !cm.curOp;
- if (withOp) startOperation(cm);
- cm.display.shift = false;
-
- if (text.charCodeAt(0) == 0x200b && 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 inserted = text.slice(same), textLines = splitLines(inserted);
-
- // When pasing N lines into N selections, insert one line per selection
- var multiPaste = null;
- if (cm.state.pasteIncoming && doc.sel.ranges.length > 1) {
- if (lastCopied && lastCopied.join("\n") == inserted)
- multiPaste = doc.sel.ranges.length % lastCopied.length == 0 && map(lastCopied, splitLines);
- else if (textLines.length == doc.sel.ranges.length)
- multiPaste = map(textLines, function(l) { return [l]; });
- }
-
- // Normal behavior is to insert the new text into every selection
- for (var i = doc.sel.ranges.length - 1; i >= 0; i--) {
- var range = doc.sel.ranges[i];
- var from = range.from(), to = range.to();
- // Handle deletion
- if (same < prevInput.length)
- from = Pos(from.line, from.ch - (prevInput.length - same));
- // Handle overwrite
- else if (cm.state.overwrite && range.empty() && !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);
- // 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 || doc.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;
-
- // Don't leave long text in the textarea, since it makes further polling slow
- if (text.length > 1000 || text.indexOf("\n") > -1) input.value = cm.display.prevInput = "";
- else cm.display.prevInput = text;
- if (withOp) endOperation(cm);
- cm.state.pasteIncoming = cm.state.cutIncoming = false;
- return true;
- }
-
- // Reset the input to correspond to the selection (or to be empty,
- // when not typing and nothing is selected)
- function resetInput(cm, typing) {
- var minimal, selected, doc = cm.doc;
- if (cm.somethingSelected()) {
- cm.display.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();
- cm.display.input.value = content;
- if (cm.state.focused) selectInput(cm.display.input);
- if (ie && ie_version >= 9) cm.display.inputHasSelection = content;
- } else if (!typing) {
- cm.display.prevInput = cm.display.input.value = "";
- if (ie && ie_version >= 9) cm.display.inputHasSelection = null;
- }
- cm.display.inaccurateSelection = minimal;
- }
-
- function focusInput(cm) {
- if (cm.options.readOnly != "nocursor" && (!mobile || activeElt() != cm.display.input))
- cm.display.input.focus();
- }
-
- function ensureFocus(cm) {
- if (!cm.state.focused) { focusInput(cm); onFocus(cm); }
- }
-
- function isReadOnly(cm) {
- return cm.options.readOnly || cm.doc.cantEdit;
- }
-
// EVENT HANDLERS
// Attach the necessary event handlers when initializing the editor
@@ -2532,15 +3367,64 @@
}));
else
on(d.scroller, "dblclick", function(e) { signalDOMEvent(cm, e) || e_preventDefault(e); });
- // Prevent normal selection in the editor (we handle our own)
- on(d.lineSpace, "selectstart", function(e) {
- if (!eventInWidget(d, 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);});
+ // 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);
+
// Sync scrolling between fake scrollbars and real scrollable
// area, ensure viewport is updated when scrolling.
on(d.scroller, "scroll", function() {
@@ -2550,111 +3434,48 @@
signal(cm, "scroll", cm);
}
});
- on(d.scrollbarV, "scroll", function() {
- if (d.scroller.clientHeight) setScrollTop(cm, d.scrollbarV.scrollTop);
- });
- on(d.scrollbarH, "scroll", function() {
- if (d.scroller.clientHeight) setScrollLeft(cm, d.scrollbarH.scrollLeft);
- });
// 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);});
- // Prevent clicks in the scrollbars from killing focus
- function reFocus() { if (cm.state.focused) setTimeout(bind(focusInput, cm), 0); }
- on(d.scrollbarH, "mousedown", reFocus);
- on(d.scrollbarV, "mousedown", reFocus);
// Prevent wrapper from ever scrolling
on(d.wrapper, "scroll", function() { d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; });
- on(d.input, "keyup", function(e) { onKeyUp.call(cm, e); });
- on(d.input, "input", function() {
- if (ie && ie_version >= 9 && cm.display.inputHasSelection) cm.display.inputHasSelection = null;
- fastPoll(cm);
- });
- on(d.input, "keydown", operation(cm, onKeyDown));
- on(d.input, "keypress", operation(cm, onKeyPress));
- on(d.input, "focus", bind(onFocus, cm));
- on(d.input, "blur", bind(onBlur, cm));
-
- 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));
- }
- on(d.scroller, "paste", function(e) {
- if (eventInWidget(d, e)) return;
- cm.state.pasteIncoming = true;
- focusInput(cm);
- fastPoll(cm);
- });
- on(d.input, "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 = d.input.selectionStart, end = d.input.selectionEnd;
- d.input.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.
- d.input.selectionEnd = end;
- d.input.selectionStart = start;
- cm.state.fakedLastChar = true;
- }
- cm.state.pasteIncoming = true;
- fastPoll(cm);
- });
+ d.dragFunctions = {
+ simple: function(e) {if (!signalDOMEvent(cm, e)) e_stop(e);},
+ start: function(e){onDragStart(cm, e);},
+ drop: operation(cm, onDrop)
+ };
- function prepareCopyCut(e) {
- if (cm.somethingSelected()) {
- lastCopied = cm.getSelections();
- if (d.inaccurateSelection) {
- d.prevInput = "";
- d.inaccurateSelection = false;
- d.input.value = lastCopied.join("\n");
- selectInput(d.input);
- }
- } else {
- 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));
- }
- if (e.type == "cut") {
- cm.setSelections(ranges, null, sel_dontScroll);
- } else {
- d.prevInput = "";
- d.input.value = text.join("\n");
- selectInput(d.input);
- }
- lastCopied = text;
- }
- if (e.type == "cut") cm.state.cutIncoming = true;
- }
- on(d.input, "cut", prepareCopyCut);
- on(d.input, "copy", prepareCopyCut);
+ 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));
+ }
- // Needed to handle Tab key in KHTML
- if (khtml) on(d.sizer, "mouseup", function() {
- if (activeElt() == d.input) d.input.blur();
- focusInput(cm);
- });
+ function dragDropChanged(cm, value, old) {
+ var wasOn = old && old != CodeMirror.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.simple);
+ toggle(cm.display.scroller, "dragover", funcs.simple);
+ toggle(cm.display.scroller, "drop", funcs.drop);
+ }
}
// Called when the window resizes
function onResize(cm) {
- // Might be a text scaling operation, clear size caches.
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();
}
@@ -2663,7 +3484,9 @@
// 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.ignoreEvents || n.parentNode == display.sizer && n != display.mover) return true;
+ if (!n || (n.nodeType == 1 && n.getAttribute("cm-ignore-events") == "true") ||
+ (n.parentNode == display.sizer && n != display.mover))
+ return true;
}
}
@@ -2674,11 +3497,8 @@
// coordinates beyond the right of the text.
function posFromMouse(cm, e, liberal, forRect) {
var display = cm.display;
- if (!liberal) {
- var target = e_target(e);
- if (target == display.scrollbarH || target == display.scrollbarV ||
- target == display.scrollbarFiller || target == display.gutterFiller) return null;
- }
+ 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; }
@@ -2697,8 +3517,8 @@
// 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) {
- if (signalDOMEvent(this, e)) return;
var cm = this, display = cm.display;
+ if (display.activeTouch && display.input.supportsTouch() || signalDOMEvent(cm, e)) return;
display.shift = e.shiftKey;
if (eventInWidget(display, e)) {
@@ -2724,18 +3544,20 @@
case 2:
if (webkit) cm.state.lastMiddleDown = +new Date;
if (start) extendSelection(cm.doc, start);
- setTimeout(bind(focusInput, cm), 20);
+ setTimeout(function() {display.input.focus();}, 20);
e_preventDefault(e);
break;
case 3:
if (captureRightClick) onContextMenu(cm, e);
+ else delayBlurEvent(cm);
break;
}
}
var lastClick, lastDoubleClick;
function leftButtonDown(cm, e, start) {
- setTimeout(bind(ensureFocus, cm), 0);
+ if (ie) setTimeout(bind(ensureFocus, cm), 0);
+ else cm.curOp.focus = activeElt();
var now = +new Date, type;
if (lastDoubleClick && lastDoubleClick.time > now - 400 && cmp(lastDoubleClick.pos, start) == 0) {
@@ -2748,9 +3570,11 @@
lastClick = {time: now, pos: start};
}
- var sel = cm.doc.sel, modifier = mac ? e.metaKey : e.ctrlKey;
+ var sel = cm.doc.sel, modifier = mac ? e.metaKey : e.ctrlKey, contained;
if (cm.options.dragDrop && dragAndDrop && !isReadOnly(cm) &&
- type == "single" && sel.contains(start) > -1 && sel.somethingSelected())
+ type == "single" && (contained = sel.contains(start)) > -1 &&
+ (cmp((contained = sel.ranges[contained]).from(), start) < 0 || start.xRel > 0) &&
+ (cmp(contained.to(), start) > 0 || start.xRel < 0))
leftButtonStartDrag(cm, e, start, modifier);
else
leftButtonSelect(cm, e, start, type, modifier);
@@ -2759,7 +3583,7 @@
// 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) {
- var display = cm.display;
+ var display = cm.display, startTime = +new Date;
var dragEnd = operation(cm, function(e2) {
if (webkit) display.scroller.draggable = false;
cm.state.draggingText = false;
@@ -2767,12 +3591,13 @@
off(display.scroller, "drop", dragEnd);
if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) {
e_preventDefault(e2);
- if (!modifier)
+ if (!modifier && +new Date - 200 < startTime)
extendSelection(cm.doc, start);
- focusInput(cm);
- // Work around unexplainable focus problem in IE9 (#2127)
- if (ie && ie_version == 9)
- setTimeout(function() {document.body.focus(); focusInput(cm);}, 20);
+ // Work around unexplainable focus problem in IE9 (#2127) and Chrome (#3081)
+ if (webkit || ie && ie_version == 9)
+ setTimeout(function() {document.body.focus(); display.input.focus();}, 20);
+ else
+ display.input.focus();
}
});
// Let the drag handler handle this.
@@ -2789,15 +3614,16 @@
var display = cm.display, doc = cm.doc;
e_preventDefault(e);
- var ourRange, ourIndex, startSel = doc.sel;
+ var ourRange, ourIndex, startSel = doc.sel, ranges = startSel.ranges;
if (addNew && !e.shiftKey) {
ourIndex = doc.sel.contains(start);
if (ourIndex > -1)
- ourRange = doc.sel.ranges[ourIndex];
+ ourRange = ranges[ourIndex];
else
ourRange = new Range(start, start);
} else {
ourRange = doc.sel.primary();
+ ourIndex = doc.sel.primIndex;
}
if (e.altKey) {
@@ -2825,12 +3651,15 @@
ourIndex = 0;
setSelection(doc, new Selection([ourRange], 0), sel_mouse);
startSel = doc.sel;
- } else if (ourIndex > -1) {
- replaceOneSelection(doc, ourIndex, ourRange, sel_mouse);
- } else {
- ourIndex = doc.sel.ranges.length;
- setSelection(doc, normalizeSelection(doc.sel.ranges.concat([ourRange]), ourIndex),
+ } 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" && !e.shiftKey) {
+ 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;
@@ -2889,7 +3718,7 @@
var cur = posFromMouse(cm, e, true, type == "rect");
if (!cur) return;
if (cmp(cur, lastPos) != 0) {
- ensureFocus(cm);
+ cm.curOp.focus = activeElt();
extendTo(cur);
var visible = visibleLines(display, doc);
if (cur.line >= visible.to || cur.line < visible.from)
@@ -2907,7 +3736,7 @@
function done(e) {
counter = Infinity;
e_preventDefault(e);
- focusInput(cm);
+ display.input.focus();
off(document, "mousemove", move);
off(document, "mouseup", up);
doc.history.lastSelOrigin = null;
@@ -2973,7 +3802,9 @@
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"};
+ var change = {from: pos, to: pos,
+ text: cm.doc.splitLines(text.join(cm.doc.lineSeparator())),
+ origin: "paste"};
makeChange(cm.doc, change);
setSelectionReplaceHistory(cm.doc, simpleSelection(pos, changeEnd(change)));
}
@@ -2986,19 +3817,19 @@
if (cm.state.draggingText && cm.doc.sel.contains(pos) > -1) {
cm.state.draggingText(e);
// Ensure the editor is re-focused
- setTimeout(bind(focusInput, cm), 20);
+ 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))
+ if (cm.state.draggingText && !(mac ? e.altKey : 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");
- focusInput(cm);
+ cm.display.input.focus();
}
}
catch(e){}
@@ -3036,7 +3867,7 @@
cm.doc.scrollTop = val;
if (!gecko) updateDisplaySimple(cm, {top: val});
if (cm.display.scroller.scrollTop != val) cm.display.scroller.scrollTop = val;
- if (cm.display.scrollbarV.scrollTop != val) cm.display.scrollbarV.scrollTop = val;
+ cm.display.scrollbars.setScrollTop(val);
if (gecko) updateDisplaySimple(cm);
startWorker(cm, 100);
}
@@ -3048,7 +3879,7 @@
cm.doc.scrollLeft = val;
alignHorizontally(cm);
if (cm.display.scroller.scrollLeft != val) cm.display.scroller.scrollLeft = val;
- if (cm.display.scrollbarH.scrollLeft != val) cm.display.scrollbarH.scrollLeft = val;
+ cm.display.scrollbars.setScrollLeft(val);
}
// Since the delta values reported on mouse wheel events are
@@ -3072,11 +3903,22 @@
else if (chrome) wheelPixelsPerUnit = -.7;
else if (safari) wheelPixelsPerUnit = -1/3;
- function onScrollWheel(cm, e) {
+ 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;
+ };
+
+ function onScrollWheel(cm, e) {
+ var delta = wheelEventDelta(e), dx = delta.x, dy = delta.y;
var display = cm.display, scroll = display.scroller;
// Quit if there's nothing to scroll here
@@ -3154,7 +3996,7 @@
}
// Ensure previous input has been read, so that the handler sees a
// consistent view of the document
- if (cm.display.pollingFast && readInput(cm)) cm.display.pollingFast = false;
+ cm.display.input.ensurePolled();
var prevShift = cm.display.shift, done = false;
try {
if (isReadOnly(cm)) cm.state.suppressEdits = true;
@@ -3167,68 +4009,76 @@
return done;
}
- // Collect the currently active keymaps.
- function allKeyMaps(cm) {
- var maps = cm.state.keyMaps.slice(0);
- if (cm.options.extraKeys) maps.push(cm.options.extraKeys);
- maps.push(cm.options.keyMap);
- return maps;
+ 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);
+ }
+
+ var stopSeq = new Delayed;
+ function dispatchKey(cm, name, e, handle) {
+ var seq = cm.state.keySeq;
+ if (seq) {
+ if (isModifierKey(name)) return "handled";
+ stopSeq.set(50, function() {
+ if (cm.state.keySeq == seq) {
+ cm.state.keySeq = null;
+ cm.display.input.reset();
+ }
+ });
+ name = seq + " " + name;
+ }
+ var result = lookupKeyForEditor(cm, name, handle);
+
+ if (result == "multi")
+ cm.state.keySeq = name;
+ if (result == "handled")
+ signalLater(cm, "keyHandled", cm, name, e);
+
+ if (result == "handled" || result == "multi") {
+ e_preventDefault(e);
+ restartBlink(cm);
+ }
+
+ if (seq && !result && /\'$/.test(name)) {
+ e_preventDefault(e);
+ return true;
+ }
+ return !!result;
}
- var maybeTransition;
// Handle a key from the keydown event.
function handleKeyBinding(cm, e) {
- // Handle automatic keymap transitions
- var startMap = getKeyMap(cm.options.keyMap), next = startMap.auto;
- clearTimeout(maybeTransition);
- if (next && !isModifierKey(e)) maybeTransition = setTimeout(function() {
- if (getKeyMap(cm.options.keyMap) == startMap) {
- cm.options.keyMap = (next.call ? next.call(null, cm) : next);
- keyMapChanged(cm);
- }
- }, 50);
-
- var name = keyName(e, true), handled = false;
+ var name = keyName(e, true);
if (!name) return false;
- var keymaps = allKeyMaps(cm);
- if (e.shiftKey) {
+ 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-'.
- handled = lookupKey("Shift-" + name, keymaps, function(b) {return doHandleBinding(cm, b, true);})
- || lookupKey(name, keymaps, function(b) {
- if (typeof b == "string" ? /^go[A-Z]/.test(b) : b.motion)
- return doHandleBinding(cm, b);
- });
+ 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 {
- handled = lookupKey(name, keymaps, function(b) { return doHandleBinding(cm, b); });
- }
-
- if (handled) {
- e_preventDefault(e);
- restartBlink(cm);
- signalLater(cm, "keyHandled", cm, name, e);
+ return dispatchKey(cm, name, e, function(b) { return doHandleBinding(cm, b); });
}
- return handled;
}
// Handle a key from the keypress event
function handleCharBinding(cm, e, ch) {
- var handled = lookupKey("'" + ch + "'", allKeyMaps(cm),
- function(b) { return doHandleBinding(cm, b, true); });
- if (handled) {
- e_preventDefault(e);
- restartBlink(cm);
- signalLater(cm, "keyHandled", cm, "'" + ch + "'", e);
- }
- return handled;
+ return dispatchKey(cm, "'" + ch + "'", e,
+ function(b) { return doHandleBinding(cm, b, true); });
}
var lastStoppedKey = null;
function onKeyDown(e) {
var cm = this;
- ensureFocus(cm);
+ 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;
@@ -3269,36 +4119,49 @@
function onKeyPress(e) {
var cm = this;
- if (signalDOMEvent(cm, e) || e.ctrlKey && !e.altKey || mac && e.metaKey) 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)) || khtml) && handleKeyBinding(cm, 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;
- if (ie && ie_version >= 9) cm.display.inputHasSelection = null;
- fastPoll(cm);
+ cm.display.input.onKeyPress(e);
}
// FOCUS/BLUR EVENTS
+ function delayBlurEvent(cm) {
+ cm.state.delayingBlurEvent = true;
+ setTimeout(function() {
+ if (cm.state.delayingBlurEvent) {
+ cm.state.delayingBlurEvent = false;
+ onBlur(cm);
+ }
+ }, 100);
+ }
+
function onFocus(cm) {
+ if (cm.state.delayingBlurEvent) cm.state.delayingBlurEvent = false;
+
if (cm.options.readOnly == "nocursor") return;
if (!cm.state.focused) {
signal(cm, "focus", cm);
cm.state.focused = true;
addClass(cm.display.wrapper, "CodeMirror-focused");
- // The prevInput test prevents this from firing when a context
- // menu is closed (since the resetInput would kill the
+ // 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) {
- resetInput(cm);
- if (webkit) setTimeout(bind(resetInput, cm, true), 0); // Issue #1730
+ cm.display.input.reset();
+ if (webkit) setTimeout(function() { cm.display.input.reset(true); }, 20); // Issue #1730
}
+ cm.display.input.receivedFocus();
}
- slowPoll(cm);
restartBlink(cm);
}
function onBlur(cm) {
+ if (cm.state.delayingBlurEvent) return;
+
if (cm.state.focused) {
signal(cm, "blur", cm);
cm.state.focused = false;
@@ -3314,78 +4177,8 @@
// textarea (making it as unobtrusive as possible) to let the
// right-click take effect on it.
function onContextMenu(cm, e) {
- if (signalDOMEvent(cm, e, "contextmenu")) return;
- var display = cm.display;
- if (eventInWidget(display, e) || contextMenuInGutter(cm, e)) return;
-
- 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 = display.input.style.cssText;
- display.inputDiv.style.position = "absolute";
- display.input.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)
- focusInput(cm);
- if (webkit) window.scrollTo(null, oldScrollY);
- resetInput(cm);
- // Adds "Select all" to context menu in FF
- if (!cm.somethingSelected()) display.input.value = display.prevInput = " ";
- 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 (display.input.selectionStart != null) {
- var selected = cm.somethingSelected();
- var extval = display.input.value = "\u200b" + (selected ? display.input.value : "");
- display.prevInput = selected ? "" : "\u200b";
- display.input.selectionStart = 1; display.input.selectionEnd = extval.length;
- // Re-set this, in case some other handler touched the
- // selection in the meantime.
- display.selForContextMenu = cm.doc.sel;
- }
- }
- function rehide() {
- display.inputDiv.style.position = "relative";
- display.input.style.cssText = oldCSS;
- if (ie && ie_version < 9) display.scrollbarV.scrollTop = display.scroller.scrollTop = scrollPos;
- slowPoll(cm);
-
- // Try to detect the user choosing select-all
- if (display.input.selectionStart != null) {
- if (!ie || (ie && ie_version < 9)) prepareSelectAllHack();
- var i = 0, poll = function() {
- if (display.selForContextMenu == cm.doc.sel && display.input.selectionStart == 0)
- operation(cm, commands.selectAll)(cm);
- else if (i++ < 10) display.detectingSelectAll = setTimeout(poll, 500);
- else resetInput(cm);
- };
- 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);
- }
+ if (eventInWidget(cm.display, e) || contextMenuInGutter(cm, e)) return;
+ cm.display.input.onContextMenu(e);
}
function contextMenuInGutter(cm, e) {
@@ -3670,7 +4463,9 @@
var lendiff = change.text.length - (to.line - from.line) - 1;
// Remember that these lines changed, for updating the display
- if (from.line == to.line && change.text.length == 1 && !isWholeLineUpdate(cm.doc, change))
+ 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);
@@ -3692,7 +4487,7 @@
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);
+ if (typeof code == "string") code = doc.splitLines(code);
makeChange(doc, {from: from, to: to, text: code, origin: origin});
}
@@ -3701,13 +4496,15 @@
// 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;
+
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 + scrollerCutOff) + "px; left: " +
+ (coords.bottom - coords.top + scrollGap(cm) + display.barHeight) + "px; left: " +
coords.left + "px; width: 2px;");
cm.display.lineSpace.appendChild(scrollNode);
scrollNode.scrollIntoView(doScroll);
@@ -3736,8 +4533,9 @@
setScrollLeft(cm, scrollPos.scrollLeft);
if (Math.abs(cm.doc.scrollLeft - startLeft) > 1) changed = true;
}
- if (!changed) return coords;
+ if (!changed) break;
}
+ return coords;
}
// Scroll a given set of coordinates into view (immediately).
@@ -3755,7 +4553,7 @@
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 = display.scroller.clientHeight - scrollerCutOff, result = {};
+ 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;
@@ -3767,7 +4565,7 @@
}
var screenleft = cm.curOp && cm.curOp.scrollLeft != null ? cm.curOp.scrollLeft : display.scroller.scrollLeft;
- var screenw = display.scroller.clientWidth - scrollerCutOff - display.gutters.offsetWidth;
+ var screenw = displayWidth(cm) - (cm.options.fixedGutter ? display.gutters.offsetWidth : 0);
var tooWide = x2 - x1 > screenw;
if (tooWide) x2 = x1 + screenw;
if (x1 < 10)
@@ -3776,7 +4574,6 @@
result.scrollLeft = Math.max(0, x1 - (tooWide ? 0 : 10));
else if (x2 > screenw + screenleft - 3)
result.scrollLeft = x2 + (tooWide ? 0 : 10) - screenw;
-
return result;
}
@@ -3869,6 +4666,8 @@
if (indentString != curSpaceString) {
replaceRange(doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input");
+ line.stateAfter = null;
+ return true;
} else {
// Ensure that, if the cursor was in the whitespace at the start
// of the line, it is moved to the end of that space.
@@ -3881,7 +4680,6 @@
}
}
}
- line.stateAfter = null;
}
// Utility for applying a change to a line by handle or number,
@@ -4010,7 +4808,7 @@
CodeMirror.prototype = {
constructor: CodeMirror,
- focus: function(){window.focus(); focusInput(this); fastPoll(this);},
+ focus: function(){window.focus(); this.display.input.focus();},
setOption: function(option, value) {
var options = this.options, old = options[option];
@@ -4024,12 +4822,12 @@
getDoc: function() {return this.doc;},
addKeyMap: function(map, bottom) {
- this.state.keyMaps[bottom ? "push" : "unshift"](map);
+ 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 || (typeof maps[i] != "string" && maps[i].name == map)) {
+ if (maps[i] == map || maps[i].name == map) {
maps.splice(i, 1);
return true;
}
@@ -4086,20 +4884,11 @@
// 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) {
- var doc = this.doc;
- pos = clipPos(doc, pos);
- var state = getStateBefore(this, pos.line, precise), mode = this.doc.mode;
- var line = getLine(doc, pos.line);
- var stream = new StringStream(line.text, this.options.tabSize);
- while (stream.pos < pos.ch && !stream.eol()) {
- stream.start = stream.pos;
- var style = readToken(mode, stream, state);
- }
- return {start: stream.start,
- end: stream.pos,
- string: stream.current(),
- type: style || null,
- state: state};
+ return takeToken(this, pos, precise);
+ },
+
+ getLineTokens: function(line, precise) {
+ return takeToken(this, Pos(line), precise, true);
},
getTokenTypeAt: function(pos) {
@@ -4130,7 +4919,7 @@
getHelpers: function(pos, type) {
var found = [];
- if (!helpers.hasOwnProperty(type)) return helpers;
+ 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]]);
@@ -4180,10 +4969,15 @@
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);
+ 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").top +
(end ? this.doc.height - heightAtLine(lineObj) : 0);
},
@@ -4212,12 +5006,6 @@
});
}),
- 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;
@@ -4240,6 +5028,8 @@
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;
@@ -4277,6 +5067,8 @@
return commands[cmd](this);
},
+ triggerElectric: methodOp(function(text) { triggerElectric(this, text); }),
+
findPosH: function(from, amount, unit, visually) {
var dir = 1;
if (amount < 0) { dir = -1; amount = -amount; }
@@ -4366,7 +5158,7 @@
signal(this, "overwriteToggle", this, this.state.overwrite);
},
- hasFocus: function() { return activeElt() == this.display.input; },
+ hasFocus: function() { return this.display.input.getField() == activeElt(); },
scrollTo: methodOp(function(x, y) {
if (x != null || y != null) resolveScrollToPos(this);
@@ -4374,10 +5166,11 @@
if (y != null) this.curOp.scrollTop = y;
}),
getScrollInfo: function() {
- var scroller = this.display.scroller, co = scrollerCutOff;
+ var scroller = this.display.scroller;
return {left: scroller.scrollLeft, top: scroller.scrollTop,
- height: scroller.scrollHeight - co, width: scroller.scrollWidth - co,
- clientHeight: scroller.clientHeight - co, clientWidth: scroller.clientWidth - co};
+ 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) {
@@ -4441,14 +5234,14 @@
old.cm = null;
attachDoc(this, doc);
clearCaches(this);
- resetInput(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;},
+ 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;}
@@ -4489,12 +5282,31 @@
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("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", /[\t\u0000-\u0019\u00ad\u200b-\u200f\u2028\u2029\ufeff]/g, function(cm, val, old) {
+ cm.state.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"), "g");
+ if (old != CodeMirror.Init) cm.refresh();
+ });
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);
@@ -4502,7 +5314,12 @@
themeChanged(cm);
guttersChanged(cm);
}, true);
- option("keyMap", "default", keyMapChanged);
+ 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);
@@ -4514,7 +5331,13 @@
cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px" : "0";
cm.refresh();
}, true);
- option("coverGutterNextToScrollbar", false, updateScrollbars, 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);
@@ -4524,6 +5347,7 @@
option("showCursorWhenSelecting", false, updateSelection, true);
option("resetSelectionOnContextMenu", true);
+ option("lineWiseCopyCut", true);
option("readOnly", false, function(cm, val) {
if (val == "nocursor") {
@@ -4532,11 +5356,11 @@
cm.display.disabled = true;
} else {
cm.display.disabled = false;
- if (!val) resetInput(cm);
+ if (!val) cm.display.input.reset();
}
});
- option("disableInput", false, function(cm, val) {if (!val) resetInput(cm);}, true);
- option("dragDrop", true);
+ option("disableInput", false, function(cm, val) {if (!val) cm.display.input.reset();}, true);
+ option("dragDrop", true, dragDropChanged);
option("cursorBlinkRate", 530);
option("cursorScrollMargin", 0);
@@ -4552,11 +5376,11 @@
option("viewportMargin", 10, function(cm){cm.refresh();}, true);
option("maxHighlightLength", 10000, resetModeState, true);
option("moveInputWithCursor", true, function(cm, val) {
- if (!val) cm.display.inputDiv.style.top = cm.display.inputDiv.style.left = 0;
+ if (!val) cm.display.input.resetPosition();
});
option("tabindex", null, function(cm, val) {
- cm.display.input.tabIndex = val || "";
+ cm.display.input.getField().tabIndex = val || "";
});
option("autofocus", null);
@@ -4824,7 +5648,8 @@
} 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),
+ cm.replaceRange(line.charAt(0) + cm.doc.lineSeparator() +
+ prev.charAt(prev.length - 1),
Pos(cur.line - 1, prev.length - 1), Pos(cur.line, 1), "+transpose");
}
}
@@ -4838,7 +5663,7 @@
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.replaceRange(cm.doc.lineSeparator(), range.anchor, range.head, "+input");
cm.indentLine(range.from().line + 1, null, true);
ensureCursorVisible(cm);
}
@@ -4847,9 +5672,11 @@
toggleOverwrite: function(cm) {cm.toggleOverwrite();}
};
+
// STANDARD KEYMAPS
var keyMap = CodeMirror.keyMap = {};
+
keyMap.basic = {
"Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown",
"End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown",
@@ -4871,6 +5698,13 @@
"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",
@@ -4881,77 +5715,107 @@
"Cmd-U": "undoSelection", "Shift-Cmd-U": "redoSelection", "Ctrl-Up": "goDocStart", "Ctrl-Down": "goDocEnd",
fallthrough: ["basic", "emacsy"]
};
- // 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["default"] = mac ? keyMap.macDefault : keyMap.pcDefault;
// KEYMAP DISPATCH
- function getKeyMap(val) {
- if (typeof val == "string") return keyMap[val];
- else return val;
- }
-
- // Given an array of keymaps and a key name, call handle on any
- // bindings found, until that returns a truthy value, at which point
- // we consider the key handled. Implements things like binding a key
- // to false stopping further handling and keymap fallthrough.
- var lookupKey = CodeMirror.lookupKey = function(name, maps, handle) {
- function lookup(map) {
- map = getKeyMap(map);
- var found = map[name];
- if (found === false) return "stop";
- if (found != null && handle(found)) return true;
- if (map.nofallthrough) return "stop";
-
- var fallthrough = map.fallthrough;
- if (fallthrough == null) return false;
- if (Object.prototype.toString.call(fallthrough) != "[object Array]")
- return lookup(fallthrough);
- for (var i = 0; i < fallthrough.length; ++i) {
- var done = lookup(fallthrough[i]);
- if (done) return done;
+ function normalizeKeyName(name) {
+ var parts = name.split(/-(?!$)/), name = parts[parts.length - 1];
+ var alt, ctrl, shift, cmd;
+ for (var i = 0; i < parts.length - 1; i++) {
+ var mod = parts[i];
+ if (/^(cmd|meta|m)$/i.test(mod)) cmd = true;
+ else if (/^a(lt)?$/i.test(mod)) alt = true;
+ else if (/^(c|ctrl|control)$/i.test(mod)) ctrl = true;
+ else if (/^s(hift)$/i.test(mod)) shift = true;
+ else throw new Error("Unrecognized modifier name: " + mod);
+ }
+ if (alt) name = "Alt-" + name;
+ if (ctrl) name = "Ctrl-" + name;
+ if (cmd) name = "Cmd-" + name;
+ if (shift) name = "Shift-" + name;
+ return name;
+ }
+
+ // This is a kludge to keep keymaps mostly working as raw objects
+ // (backwards compatibility) while at the same time support features
+ // like normalization and multi-stroke key bindings. It compiles a
+ // new normalized keymap, and then updates the old object to reflect
+ // this.
+ 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; }
+
+ var keys = map(keyname.split(" "), normalizeKeyName);
+ for (var i = 0; i < keys.length; i++) {
+ var val, name;
+ if (i == keys.length - 1) {
+ name = keys.join(" ");
+ val = value;
+ } else {
+ name = keys.slice(0, i + 1).join(" ");
+ val = "...";
+ }
+ var prev = copy[name];
+ if (!prev) copy[name] = val;
+ else if (prev != val) throw new Error("Inconsistent bindings for " + name);
}
- return false;
+ delete keymap[keyname];
}
+ 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";
- for (var i = 0; i < maps.length; ++i) {
- var done = lookup(maps[i]);
- if (done) return done != "stop";
+ if (map.fallthrough) {
+ if (Object.prototype.toString.call(map.fallthrough) != "[object Array]")
+ return lookupKey(key, map.fallthrough, handle, context);
+ for (var i = 0; i < map.fallthrough.length; i++) {
+ var result = lookupKey(key, map.fallthrough[i], handle, context);
+ if (result) return result;
+ }
}
};
// Modifier key presses don't count as 'real' key presses for the
// purpose of keymap fallthrough.
- var isModifierKey = CodeMirror.isModifierKey = function(event) {
- var name = keyNames[event.keyCode];
+ var isModifierKey = CodeMirror.isModifierKey = function(value) {
+ var name = typeof value == "string" ? value : keyNames[value.keyCode];
return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod";
};
// 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 name = keyNames[event.keyCode];
+ var base = keyNames[event.keyCode], name = base;
if (name == null || event.altGraphKey) return false;
- if (event.altKey) name = "Alt-" + name;
- if (flipCtrlCmd ? event.metaKey : event.ctrlKey) name = "Ctrl-" + name;
- if (flipCtrlCmd ? event.ctrlKey : event.metaKey) name = "Cmd-" + name;
- if (!noShift && event.shiftKey) name = "Shift-" + name;
+ if (event.altKey && base != "Alt") name = "Alt-" + name;
+ if ((flipCtrlCmd ? event.metaKey : event.ctrlKey) && base != "Ctrl") name = "Ctrl-" + name;
+ if ((flipCtrlCmd ? event.ctrlKey : event.metaKey) && base != "Cmd") name = "Cmd-" + name;
+ if (!noShift && event.shiftKey && base != "Shift") name = "Shift-" + name;
return name;
};
+ function getKeyMap(val) {
+ return typeof val == "string" ? keyMap[val] : val;
+ }
+
// FROMTEXTAREA
CodeMirror.fromTextArea = function(textarea, options) {
- if (!options) options = {};
+ options = options ? copyObj(options) : {};
options.value = textarea.value;
- if (!options.tabindex && textarea.tabindex)
- options.tabindex = textarea.tabindex;
+ 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
@@ -4979,23 +5843,26 @@
}
}
+ options.finishInit = function(cm) {
+ cm.save = save;
+ cm.getTextArea = function() { return textarea; };
+ cm.toTextArea = function() {
+ cm.toTextArea = isNaN; // Prevent this from being ran twice
+ save();
+ textarea.parentNode.removeChild(cm.getWrapperElement());
+ textarea.style.display = "";
+ if (textarea.form) {
+ off(textarea.form, "submit", save);
+ if (typeof textarea.form.submit == "function")
+ textarea.form.submit = realSubmit;
+ }
+ };
+ };
+
textarea.style.display = "none";
var cm = CodeMirror(function(node) {
textarea.parentNode.insertBefore(node, textarea.nextSibling);
}, options);
- 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;
- }
- };
return cm;
};
@@ -5088,10 +5955,13 @@
// marker continues beyond the start/end of the line. Markers have
// links back to the lines they currently touch.
+ var nextMarkerId = 0;
+
var TextMarker = CodeMirror.TextMarker = function(doc, type) {
this.lines = [];
this.type = type;
this.doc = doc;
+ this.id = ++nextMarkerId;
};
eventMixin(TextMarker);
@@ -5223,7 +6093,7 @@
// 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.ignoreEvents = true;
+ if (!options.handleMouseEvents) marker.widgetNode.setAttribute("cm-ignore-events", "true");
if (options.insertLeft) marker.widgetNode.insertLeft = true;
}
if (marker.collapsed) {
@@ -5267,7 +6137,7 @@
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)
+ 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);
@@ -5407,6 +6277,7 @@
// 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;
@@ -5674,10 +6545,10 @@
// Line widgets are block elements displayed above or below a line.
- var LineWidget = CodeMirror.LineWidget = function(cm, node, options) {
+ var LineWidget = CodeMirror.LineWidget = function(doc, node, options) {
if (options) for (var opt in options) if (options.hasOwnProperty(opt))
this[opt] = options[opt];
- this.cm = cm;
+ this.doc = doc;
this.node = node;
};
eventMixin(LineWidget);
@@ -5688,50 +6559,55 @@
}
LineWidget.prototype.clear = function() {
- var cm = this.cm, ws = this.line.widgets, line = this.line, no = lineNo(line);
+ 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);
- runInOp(cm, function() {
+ updateLineHeight(line, Math.max(0, line.height - height));
+ if (cm) 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;
+ var oldH = this.height, cm = this.doc.cm, line = this.line;
this.height = null;
var diff = widgetHeight(this) - oldH;
if (!diff) return;
- runInOp(cm, function() {
+ updateLineHeight(line, line.height + diff);
+ if (cm) runInOp(cm, function() {
cm.curOp.forceUpdate = true;
adjustScrollWhenAboveVisible(cm, line, diff);
- updateLineHeight(line, line.height + diff);
});
};
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: -" + widget.cm.getGutterElement().offsetWidth + "px;";
- removeChildrenAndAdd(widget.cm.display.measure, elt("div", [widget.node], null, parentStyle));
+ 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.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) {
+ function addLineWidget(doc, handle, node, options) {
+ var widget = new LineWidget(doc, node, options);
+ var cm = doc.cm;
+ if (cm && widget.noHScroll) cm.display.alignWidgets = true;
+ changeLine(doc, handle, "widget", function(line) {
var widgets = line.widgets || (line.widgets = []);
if (widget.insertAt == null) widgets.push(widget);
else widgets.splice(Math.min(widgets.length - 1, Math.max(0, widget.insertAt)), 0, widget);
widget.line = line;
- if (!lineIsHidden(cm.doc, line)) {
- var aboveVisible = heightAtLine(line) < cm.doc.scrollTop;
+ if (cm && !lineIsHidden(doc, line)) {
+ var aboveVisible = heightAtLine(line) < doc.scrollTop;
updateLineHeight(line, line.height + widgetHeight(widget));
if (aboveVisible) addToScrollPos(cm, null, widget.height);
cm.curOp.forceUpdate = true;
@@ -5794,20 +6670,44 @@
if (inner.mode.blankLine) return inner.mode.blankLine(inner.state);
}
- function readToken(mode, stream, state) {
+ 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.");
}
+ // 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};
+ }
+
+ 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));
+ }
+ return asArray ? tokens : getObj();
+ }
+
// 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) {
@@ -5816,15 +6716,18 @@
stream.pos = text.length;
style = null;
} else {
- style = extractLineClasses(readToken(mode, stream, state), lineClasses);
+ style = extractLineClasses(readToken(mode, stream, state, inner), lineClasses);
}
- if (cm.options.addModeClass) {
- var mName = CodeMirror.innerMode(mode, state).mode.name;
+ if (inner) {
+ var mName = inner[0].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;
+ while (curStart < stream.start) {
+ curStart = Math.min(stream.start, curStart + 50000);
+ f(curStart, curStyle);
+ }
+ curStyle = style;
}
stream.start = stream.pos;
}
@@ -5878,12 +6781,13 @@
return {styles: st, classes: lineClasses.bgClass || lineClasses.textClass ? lineClasses : null};
}
- function getLineStyles(cm, line) {
+ 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++;
}
return line.styles;
}
@@ -5923,7 +6827,9 @@
// 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};
+ var builder = {pre: elt("pre", [content], "CodeMirror-line"), content: content,
+ col: 0, pos: 0, cm: cm,
+ splitSpaces: (ie || webkit) && cm.getOption("lineWrapping")};
lineView.measure = {};
// Iterate over the logical lines that make up this visual line.
@@ -5933,12 +6839,11 @@
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 = [];
- insertLineContent(line, builder, getLineStyles(cm, line));
+ 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 || "");
@@ -5960,26 +6865,33 @@
}
}
+ // See issue #2901
+ if (webkit && /\bcm-tab\b/.test(builder.content.lastChild.className))
+ builder.content.className = "cm-tab-wrap-hack";
+
signal(cm, "renderLine", cm, lineView.line, builder.pre);
if (builder.pre.className)
builder.textClass = joinClasses(builder.pre.className, builder.textClass || "");
+
return builder;
}
function defaultSpecialCharPlaceholder(ch) {
var token = elt("span", "\u2022", "cm-invalidchar");
token.title = "\\u" + ch.charCodeAt(0).toString(16);
+ token.setAttribute("aria-label", token.title);
return token;
}
// Build up the DOM representation for a single token, and add it to
// the line map. Takes care to render special characters separately.
- function buildToken(builder, text, style, startStyle, endStyle, title) {
+ function buildToken(builder, text, style, startStyle, endStyle, title, css) {
if (!text) return;
- var special = builder.cm.options.specialChars, mustWrap = false;
+ var displayText = builder.splitSpaces ? text.replace(/ {3,}/g, splitSpaces) : text;
+ var special = builder.cm.state.specialChars, mustWrap = false;
if (!special.test(text)) {
builder.col += text.length;
- var content = document.createTextNode(text);
+ var 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;
@@ -5990,7 +6902,7 @@
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));
+ 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);
@@ -6002,9 +6914,16 @@
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;
+ } else if (m[0] == "\r" || m[0] == "\n") {
+ var txt = content.appendChild(elt("span", m[0] == "\r" ? "␍" : "", "cm-invalidchar"));
+ txt.setAttribute("cm-text", m[0]);
+ builder.col += 1;
} 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;
@@ -6013,33 +6932,28 @@
builder.pos++;
}
}
- if (style || startStyle || endStyle || mustWrap) {
+ if (style || startStyle || endStyle || mustWrap || css) {
var fullStyle = style || "";
if (startStyle) fullStyle += startStyle;
if (endStyle) fullStyle += endStyle;
- var token = elt("span", [content], fullStyle);
+ var token = elt("span", [content], fullStyle, css);
if (title) token.title = title;
return builder.content.appendChild(token);
}
builder.content.appendChild(content);
}
- function buildTokenSplitSpaces(inner) {
- function split(old) {
- var out = " ";
- for (var i = 0; i < old.length - 2; ++i) out += i % 2 ? " " : "\u00a0";
- out += " ";
- return out;
- }
- return function(builder, text, style, startStyle, endStyle, title) {
- inner(builder, text.replace(/ {3,}/g, split), style, startStyle, endStyle, title);
- };
+ function splitSpaces(old) {
+ var out = " ";
+ for (var i = 0; i < old.length - 2; ++i) out += i % 2 ? " " : "\u00a0";
+ out += " ";
+ return out;
}
// 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) {
+ return function(builder, text, style, startStyle, endStyle, title, css) {
style = style ? style + " cm-force-border" : "cm-force-border";
var start = builder.pos, end = start + text.length;
for (;;) {
@@ -6048,8 +6962,8 @@
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);
+ if (part.to >= end) return inner(builder, text, style, startStyle, endStyle, title, css);
+ inner(builder, text.slice(0, part.to - start), style, startStyle, null, title, css);
startStyle = null;
text = text.slice(part.to - start);
start = part.to;
@@ -6059,8 +6973,14 @@
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.map.push(builder.pos, builder.pos + size, widget);
+ builder.cm.display.input.setUneditable(widget);
builder.content.appendChild(widget);
}
builder.pos += size;
@@ -6076,18 +6996,24 @@
return;
}
- var len = allText.length, pos = 0, i = 1, text = "", style;
+ 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 = "";
+ 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.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 = 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;
@@ -6096,12 +7022,12 @@
} 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.to == pos) collapsed = false;
}
if (!collapsed && foundBookmarks.length) for (var j = 0; j < foundBookmarks.length; ++j)
buildCollapsedSpan(builder, 0, foundBookmarks[j]);
@@ -6115,7 +7041,7 @@
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);
+ spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : "", title, css);
}
if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;}
pos = end;
@@ -6144,17 +7070,24 @@
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;
+ }
var from = change.from, to = change.to, text = change.text;
var firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line);
var lastText = lst(text), lastSpans = spansFor(text.length - 1), nlines = to.line - from.line;
// Adjust the line structure
- if (isWholeLineUpdate(doc, change)) {
+ 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.
- for (var i = 0, added = []; i < text.length - 1; ++i)
- added.push(new Line(text[i], spansFor(i), estimateHeight));
+ 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);
@@ -6162,8 +7095,7 @@
if (text.length == 1) {
update(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.text.slice(to.ch), lastSpans);
} else {
- for (var added = [], i = 1; i < text.length - 1; ++i)
- added.push(new Line(text[i], spansFor(i), estimateHeight));
+ 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);
@@ -6174,8 +7106,7 @@
} else {
update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0));
update(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans);
- for (var i = 1, added = []; i < text.length - 1; ++i)
- added.push(new Line(text[i], spansFor(i), estimateHeight));
+ var added = linesFor(1, text.length - 1);
if (nlines > 1) doc.remove(from.line + 1, nlines - 1);
doc.insert(from.line + 1, added);
}
@@ -6335,8 +7266,8 @@
};
var nextDocId = 0;
- var Doc = CodeMirror.Doc = function(text, mode, firstLine) {
- if (!(this instanceof Doc)) return new Doc(text, mode, firstLine);
+ var Doc = CodeMirror.Doc = function(text, mode, firstLine, lineSep) {
+ if (!(this instanceof Doc)) return new Doc(text, mode, firstLine, lineSep);
if (firstLine == null) firstLine = 0;
BranchChunk.call(this, [new LeafChunk([new Line("", null)])]);
@@ -6350,8 +7281,9 @@
this.history = new History(null);
this.id = ++nextDocId;
this.modeOption = mode;
+ this.lineSep = lineSep;
- if (typeof text == "string") text = splitLines(text);
+ if (typeof text == "string") text = this.splitLines(text);
updateDoc(this, {from: start, to: start, text: text});
setSelection(this, simpleSelection(start), sel_dontScroll);
};
@@ -6381,12 +7313,12 @@
getValue: function(lineSep) {
var lines = getLines(this, this.first, this.first + this.size);
if (lineSep === false) return lines;
- return lines.join(lineSep || "\n");
+ 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: splitLines(code), origin: "setValue"}, true);
+ text: this.splitLines(code), origin: "setValue", full: true}, true);
setSelection(this, simpleSelection(top));
}),
replaceRange: function(code, from, to, origin) {
@@ -6397,7 +7329,7 @@
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");
+ return lines.join(lineSep || this.lineSeparator());
},
getLine: function(line) {var l = this.getLineHandle(line); return l && l.text;},
@@ -6463,13 +7395,13 @@
lines = lines ? lines.concat(sel) : sel;
}
if (lineSep === false) return lines;
- else return lines.join(lineSep || "\n");
+ 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 || "\n");
+ if (lineSep !== false) sel = sel.join(lineSep || this.lineSeparator());
parts[i] = sel;
}
return parts;
@@ -6484,7 +7416,7 @@
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};
+ 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 = changes.length - 1; i >= 0; i--)
@@ -6531,22 +7463,26 @@
},
addLineClass: docMethodOp(function(handle, where, cls) {
- return changeLine(this, handle, "class", function(line) {
- var prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : "wrapClass";
+ 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 (new RegExp("(?:^|\\s)" + cls + "(?:$|\\s)").test(line[prop])) return false;
+ 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, "class", function(line) {
- var prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : "wrapClass";
+ 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(new RegExp("(?:^|\\s+)" + cls + "(?:$|\\s+)"));
+ 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;
@@ -6555,13 +7491,19 @@
});
}),
+ 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, "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};
+ clearWhenEmpty: false, shared: options && options.shared,
+ handleMouseEvents: options && options.handleMouseEvents};
pos = clipPos(this, pos);
return markText(this, pos, pos, realOpts, "bookmark");
},
@@ -6624,7 +7566,8 @@
},
copy: function(copyHistory) {
- var doc = new Doc(getLines(this, this.first, this.first + this.size), this.modeOption, this.first);
+ var doc = new Doc(getLines(this, this.first, this.first + this.size),
+ this.modeOption, this.first, this.lineSep);
doc.scrollTop = this.scrollTop; doc.scrollLeft = this.scrollLeft;
doc.sel = this.sel;
doc.extend = false;
@@ -6640,7 +7583,7 @@
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);
+ var copy = new Doc(getLines(this, from, to), options.mode || this.modeOption, from, this.lineSep);
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}];
@@ -6669,14 +7612,20 @@
iterLinkedDocs: function(f) {linkedDocs(this, f);},
getMode: function() {return this.mode;},
- getEditor: function() {return this.cm;}
+ 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"; }
});
// Public alias.
Doc.prototype.eachLine = Doc.prototype.iter;
// Set up methods on CodeMirror's prototype to redirect to the editor's document.
- var dontDelegate = "iter insert remove copy getEditor".split(" ");
+ 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);};
@@ -7165,6 +8114,8 @@
// 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;
}
@@ -7192,7 +8143,7 @@
// MISC UTILITIES
// Number of pixels added to scroller and sizer to hide scrollbar
- var scrollerCutOff = 30;
+ var scrollerGap = 30;
// Returned or thrown by various protocols to signal 'I'm not
// handling this'.
@@ -7260,22 +8211,21 @@
if (array[i] == elt) return i;
return -1;
}
- if ([].indexOf) indexOf = function(array, elt) { return array.indexOf(elt); };
function map(array, f) {
var out = [];
for (var i = 0; i < array.length; i++) out[i] = f(array[i], i);
return out;
}
- if ([].map) map = function(array, f) { return array.map(f); };
+
+ function nothing() {}
function createObj(base, props) {
var inst;
if (Object.create) {
inst = Object.create(base);
} else {
- var ctor = function() {};
- ctor.prototype = base;
- inst = new ctor();
+ nothing.prototype = base;
+ inst = new nothing();
}
if (props) copyObj(props, inst);
return inst;
@@ -7294,7 +8244,7 @@
return function(){return f.apply(null, args);};
}
- var nonASCIISingleCaseWordChar = /[\u00df\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/;
+ var nonASCIISingleCaseWordChar = /[\u00df\u0587\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));
@@ -7330,15 +8280,16 @@
}
var range;
- if (document.createRange) range = function(node, start, end) {
+ if (document.createRange) range = function(node, start, end, endNode) {
var r = document.createRange();
- r.setEnd(node, end);
+ r.setEnd(endNode || node, end);
r.setStart(node, start);
return r;
};
else range = function(node, start, end) {
var r = document.body.createTextRange();
- r.moveToElementText(node.parentNode);
+ try { r.moveToElementText(node.parentNode); }
+ catch(e) { return r; }
r.collapse(true);
r.moveEnd("character", end);
r.moveStart("character", start);
@@ -7355,14 +8306,23 @@
return removeChildren(parent).appendChild(e);
}
- function contains(parent, child) {
+ 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);
- while (child = child.parentNode)
+ do {
+ if (child.nodeType == 11) child = child.host;
if (child == parent) return true;
- }
+ } while (child = child.parentNode);
+ };
- function activeElt() { return document.activeElement; }
+ function activeElt() {
+ var activeElement = document.activeElement;
+ while (activeElement && activeElement.root && activeElement.root.activeElement)
+ activeElement = activeElement.root.activeElement;
+ return 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() {
@@ -7370,14 +8330,19 @@
catch(e) { return document.body; }
};
- function classTest(cls) { return new RegExp("\\b" + cls + "\\b\\s*"); }
- function rmClass(node, cls) {
- var test = classTest(cls);
- if (test.test(node.className)) node.className = node.className.replace(test, "");
- }
- function addClass(node, cls) {
- if (!classTest(cls).test(node.className)) node.className += " " + cls;
- }
+ 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++)
@@ -7412,7 +8377,6 @@
on(window, "resize", function() {
if (resizeTimer == null) resizeTimer = setTimeout(function() {
resizeTimer = null;
- knownScrollbarWidth = null;
forEachCodeMirror(onResize);
}, 100);
});
@@ -7433,16 +8397,6 @@
return "draggable" in div || "dragDrop" in div;
}();
- var knownScrollbarWidth;
- function scrollbarWidth(measure) {
- if (knownScrollbarWidth != null) return knownScrollbarWidth;
- var test = elt("div", null, null, "width: 50px; height: 50px; overflow-x: scroll");
- removeChildrenAndAdd(measure, test);
- if (test.offsetWidth)
- knownScrollbarWidth = test.offsetHeight - test.clientHeight;
- return knownScrollbarWidth || 0;
- }
-
var zwspSupported;
function zeroWidthElement(measure) {
if (zwspSupported == null) {
@@ -7451,8 +8405,10 @@
if (measure.firstChild.offsetHeight != 0)
zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !(ie && ie_version < 8);
}
- if (zwspSupported) return elt("span", "\u200b");
- else return elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right: -1px");
+ 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
@@ -7468,7 +8424,7 @@
// 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 splitLinesAuto = 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);
@@ -7815,6 +8771,8 @@
lst(order).to -= m[0].length;
order.push(new BidiSpan(0, len - m[0].length, len));
}
+ if (order[0].level == 2)
+ order.unshift(new BidiSpan(1, order[0].to, order[0].to));
if (order[0].level != lst(order).level)
order.push(new BidiSpan(order[0].level, len, len));
@@ -7824,7 +8782,7 @@
// THE END
- CodeMirror.version = "4.7.0";
+ CodeMirror.version = "5.5.0";
return CodeMirror;
});
diff --git a/chrome/chromeFiles/content/libs/CodeMirror/mode/sieve/index.html b/chrome/chromeFiles/content/libs/CodeMirror/mode/sieve/index.html
deleted file mode 100644
index 6f029b6..0000000
--- a/chrome/chromeFiles/content/libs/CodeMirror/mode/sieve/index.html
+++ /dev/null
@@ -1,93 +0,0 @@
-<!doctype html>
-
-<title>CodeMirror: Sieve (RFC5228) mode</title>
-<meta charset="utf-8"/>
-<link rel=stylesheet href="../../doc/docs.css">
-
-<link rel="stylesheet" href="../../lib/codemirror.css">
-<script src="../../lib/codemirror.js"></script>
-<script src="sieve.js"></script>
-<style>.CodeMirror {background: #f8f8f8;}</style>
-<div id=nav>
- <a href="http://codemirror.net"><h1>CodeMirror</h1><img id=logo src="../../doc/logo.png"></a>
-
- <ul>
- <li><a href="../../index.html">Home</a>
- <li><a href="../../doc/manual.html">Manual</a>
- <li><a href="https://github.com/codemirror/codemirror">Code</a>
- </ul>
- <ul>
- <li><a href="../index.html">Language modes</a>
- <li><a class=active href="#">Sieve (RFC5228)</a>
- </ul>
-</div>
-
-<article>
-<h2>Sieve (RFC5228) mode</h2>
-<form><textarea id="code" name="code">
-#
-# Example Sieve Filter
-# Declare any optional features or extension used by the script
-#
-
-require ["fileinto", "reject"];
-
-#
-# Reject any large messages (note that the four leading dots get
-# "stuffed" to three)
-#
-if size :over 1M
-{
- reject text:
-Please do not send me large attachments.
-Put your file on a server and send me the URL.
-Thank you.
-.... Fred
-.
-;
- stop;
-}
-
-#
-# Handle messages from known mailing lists
-# Move messages from IETF filter discussion list to filter folder
-#
-if header :is "Sender" "owner-ietf-mta-filters at imc.org"
-{
- fileinto "filter"; # move to "filter" folder
-}
-#
-# Keep all messages to or from people in my company
-#
-elsif address :domain :is ["From", "To"] "example.com"
-{
- keep; # keep in "In" folder
-}
-
-#
-# Try and catch unsolicited email. If a message is not to me,
-# or it contains a subject known to be spam, file it away.
-#
-elsif anyof (not address :all :contains
- ["To", "Cc", "Bcc"] "me at example.com",
- header :matches "subject"
- ["*make*money*fast*", "*university*dipl*mas*"])
-{
- # If message header does not contain my address,
- # it's from a list.
- fileinto "spam"; # move to "spam" folder
-}
-else
-{
- # Move all other (non-company) mail to "personal"
- # folder.
- fileinto "personal";
-}
-</textarea></form>
- <script>
- var editor = CodeMirror.fromTextArea(document.getElementById("code"), {});
- </script>
-
- <p><strong>MIME types defined:</strong> <code>application/sieve</code>.</p>
-
- </article>
diff --git a/chrome/chromeFiles/content/libs/CodeMirror/package.json b/chrome/chromeFiles/content/libs/CodeMirror/package.json
index e7d5bd9..5c02fc1 100644
--- a/chrome/chromeFiles/content/libs/CodeMirror/package.json
+++ b/chrome/chromeFiles/content/libs/CodeMirror/package.json
@@ -1,14 +1,14 @@
{
"name": "codemirror",
- "version":"4.7.0",
+ "version":"5.5.0",
"main": "lib/codemirror.js",
"description": "In-browser code editing made bearable",
- "licenses": [{"type": "MIT",
- "url": "http://codemirror.net/LICENSE"}],
+ "license": "MIT",
"directories": {"lib": "./lib"},
"scripts": {"test": "node ./test/run.js"},
"devDependencies": {"node-static": "0.6.0",
- "phantomjs": "1.9.2-5"},
+ "phantomjs": "1.9.2-5",
+ "blint": ">=0.1.1"},
"bugs": "http://github.com/codemirror/CodeMirror/issues",
"keywords": ["JavaScript", "CodeMirror", "Editor"],
"homepage": "http://codemirror.net",
diff --git a/chrome/chromeFiles/content/modules/sieve/SieveAccounts.js b/chrome/chromeFiles/content/modules/sieve/SieveAccounts.js
index bc02f47..4722b19 100644
--- a/chrome/chromeFiles/content/modules/sieve/SieveAccounts.js
+++ b/chrome/chromeFiles/content/modules/sieve/SieveAccounts.js
@@ -836,6 +836,7 @@ SieveAccountSettings.prototype.setForcedAuthMechanism
{
Services.prefs.setCharPref(this.sieveKey+".sasl.mechanism",method);
}
+
SieveAccountSettings.prototype.getForcedAuthMechanism
= function ()
{
@@ -845,6 +846,123 @@ SieveAccountSettings.prototype.getForcedAuthMechanism
return "plain";
}
+
+/**
+ * Currently the UI supports two very different sieve editors. The plain text editor and the graphical editor.
+ *
+ * @param {int} editor
+ * pass a 0 for the default editor or a 1 for the graphical editor.
+ */
+SieveAccountSettings.prototype.setDefaultEditor
+ = function(editor)
+{
+ Services.prefs.setIntPref(this.sieveKey+".editor.default",editor);
+}
+
+SieveAccountSettings.prototype.getDefaultEditor
+ = function ()
+{
+ if (Services.prefs.prefHasUserValue(this.sieveKey+".editor.default"))
+ return Services.prefs.getIntPref(this.sieveKey+".editor.default");
+
+ return 0;
+}
+
+/**
+ * The plain text editor supports automatic indention.
+ * The indention width can be configured and adopted to the needs.
+ * It it totally independent from the tab width parameter
+ *
+ * @param {int} width
+ * the indention width, default to two.
+ */
+SieveAccountSettings.prototype.setIndentionWidth
+ = function(width)
+{
+ Services.prefs.setIntPref(this.sieveKey+".editor.indention.width",width);
+}
+
+SieveAccountSettings.prototype.getIndentionWidth
+ = function ()
+{
+ if (Services.prefs.prefHasUserValue(this.sieveKey+".editor.indention.width"))
+ return Services.prefs.getIntPref(this.sieveKey+".editor.indention.width");
+
+ return 2;
+}
+
+/**
+ * Concerning automatic indention there are two strategies. One is to use tabs
+ * the other one is using spaces.
+ *
+ * By default spaces are used for indention.
+ *
+ * @param {int} policy
+ * the indention policy. A zero means spaces, a one tabs
+ */
+SieveAccountSettings.prototype.setIndentionPolicy
+ = function(policy)
+{
+ Services.prefs.setIntPref(this.sieveKey+".editor.indention.policy", policy);
+}
+
+SieveAccountSettings.prototype.getIndentionPolicy
+ = function ()
+{
+ if (Services.prefs.prefHasUserValue(this.sieveKey+".editor.indention.policy"))
+ return Services.prefs.getIntPref(this.sieveKey+".editor.indention.policy");
+
+ return 0;
+}
+
+/**
+ * With automatic indention, the need for a tab key is gone. Which means most
+ * editor replace the tab key with whitespaces.
+ *
+ * By default tabs are replaced with spaces
+ *
+ * @param {int} policy
+ * the tab policy. Pass zero replace tabs with spaces and one to force tabs.
+ */
+SieveAccountSettings.prototype.setTabPolicy
+ = function(policy)
+{
+ Services.prefs.setIntPref(this.sieveKey+".editor.tab.policy",policy);
+}
+
+SieveAccountSettings.prototype.getTabPolicy
+ = function ()
+{
+ if (Services.prefs.prefHasUserValue(this.sieveKey+".editor.tab.policy"))
+ return Services.prefs.getIntPref(this.sieveKey+".editor.tab.policy");
+
+ return 0;
+}
+
+/**
+ * The tab width specifies over how many spaces the tap will span.
+ *
+ * The default value is 2
+ *
+ * @param {int} width
+ * the tab width as integer.
+ */
+SieveAccountSettings.prototype.setTabWidth
+ = function(width)
+{
+ Services.prefs.setIntPref(this.sieveKey+".editor.tab.width",width);
+}
+
+SieveAccountSettings.prototype.getTabWidth
+ = function ()
+{
+ if (Services.prefs.prefHasUserValue(this.sieveKey+".editor.tab.width"))
+ return Services.prefs.getIntPref(this.sieveKey+".editor.tab.width");
+
+ return 2;
+}
+
+
//** SieveNoAuthorization ****************************************************//
function SieveNoAuthorization()
{
diff --git a/chrome/chromeFiles/content/options/SieveAccountOptions.js b/chrome/chromeFiles/content/options/SieveAccountOptions.js
index 4bd238c..33aea6e 100644
--- a/chrome/chromeFiles/content/options/SieveAccountOptions.js
+++ b/chrome/chromeFiles/content/options/SieveAccountOptions.js
@@ -382,6 +382,77 @@ function onAuthorizationChange(value)
gAccount.getAuthorization(3).setAuthorization(value);
}
+// === Editor Sheet ==========================================================
+
+/**
+ * Populates the editor property sheet with data.
+ *
+ * @param {SieveAccount} account
+ * A SieveAccount which should be used to populate the property sheet
+ */
+function onEditorSheetLoad(account)
+{
+ try {
+ //var rgDefaultEditor = document.getElementById('rgDefaultEditor');
+ //rgDefaultEditor.selectedIndex = account.getSettings().getDefaultEditor();
+
+ document.getElementById("txtIndentionWidth").value = account.getSettings().getIndentionWidth();
+ document.getElementById("txtTabWidth").value = account.getSettings().getTabWidth();
+
+ document.getElementById('mlIndentionPolicy').selectedIndex = account.getSettings().getIndentionPolicy();
+ document.getElementById('mlTabPolicy').selectedIndex = account.getSettings().getTabPolicy();
+ }
+ catch (e) {
+ Components.utils.reportError(e);
+ }
+
+}
+
+function onDefaultEditorSelect(type)
+{
+ if (gAccount == null)
+ return;
+
+ if ((type == null) || (type > 1))
+ type = 0;
+
+ gAccount.getSettings().setDefaultEditor(type);
+}
+
+function onIndentionWidthChange(value)
+{
+ if (gAccount == null)
+ return;
+
+ gAccount.getSettings().setIndentionWidth(value);
+}
+
+function onIndentionPolicySelect(value)
+{
+ if (gAccount == null)
+ return;
+
+ gAccount.getSettings().setIndentionPolicy(value);
+}
+
+
+function onTabWidthChange(value)
+{
+ if (gAccount == null)
+ return;
+
+ gAccount.getSettings().setTabWidth(value);
+}
+
+
+function onTabPolicySelect(value)
+{
+ if (gAccount == null)
+ return;
+
+ gAccount.getSettings().setTabPolicy(value);
+}
+
// === Debug Sheet =============================================================
function onDebugSheetLoad(account)
@@ -415,12 +486,18 @@ function onDebugFlagCommand(sender,bit)
function onDialogLoad()
{
gAccount = window.arguments[0]["SieveAccount"];
- onServerSheetLoad(gAccount);
- onSecuritySheetLoad(gAccount);
- onProxySheetLoad(gAccount);
- onGeneralSheetLoad(gAccount);
- onAdvancedSheetLoad(gAccount);
- onDebugSheetLoad(gAccount);
+ try {
+ onServerSheetLoad(gAccount);
+ onSecuritySheetLoad(gAccount);
+ onProxySheetLoad(gAccount);
+ onGeneralSheetLoad(gAccount);
+ onAdvancedSheetLoad(gAccount);
+ onEditorSheetLoad(gAccount);
+ onDebugSheetLoad(gAccount);
+ }
+ catch (e) {
+ Components.utils.reportError(e);
+ }
}
function onDialogAccept()
diff --git a/chrome/chromeFiles/content/options/SieveAccountOptions.xul b/chrome/chromeFiles/content/options/SieveAccountOptions.xul
index 91c4fbc..5a1573b 100644
--- a/chrome/chromeFiles/content/options/SieveAccountOptions.xul
+++ b/chrome/chromeFiles/content/options/SieveAccountOptions.xul
@@ -35,6 +35,7 @@
<tab label="&options.tab.general;" />
<tab label="&options.tab.proxy;" />
<tab label="&options.tab.advanced;" />
+ <tab label="Editor" />
<tab label="&options.tab.debug;" />
<tab label="&options.tab.credits;" />
</tabs>
@@ -225,6 +226,62 @@
</groupbox>
<hbox flex="1"/>
</vbox>
+
+ <!-- editor panel -->
+ <vbox>
+ <!--
+ <groupbox>
+ <caption label="Default Mode" />
+ <description>The sieve addon supports two editing modes, the plain text editor and the graphical editor.
+ The graphical editor is still a bit experimental and supports only a limited set of sieve instructions.</description>
+ <description>You can switch between the editors by toggeling the source button in the toolbar</description>
+ <radiogroup id="rgDefaultEditor" onselect="onDefaultEditorSelect(this.selectedIndex);">
+ <radio label="Plain text editor"/>
+ <radio label="Graphical Editor"/>
+ </radiogroup>
+ </groupbox>
+ -->
+
+ <groupbox>
+ <caption label="Indention" />
+
+ <description>The plain text editor supports automatic indenting. </description>
+ <box align="center" >
+ <label value="Indention Width:" control="txtIndentionWidth"/>
+ <textbox id="txtIndentionWidth" size="4"
+ onchange="onIndentionWidthChange(this.value);"/>
+ </box>
+ <hbox align="center" >
+ <label control="mlIndentionPolicy" value="Indention Policy:" />
+ <menulist id="mlIndentionPolicy" oncommand="onIndentionPolicySelect(this.selectedIndex)">
+ <menupopup>
+ <menuitem label="Spaces Only" value="0"/>
+ <menuitem label="Tabs Only" value="1"/>
+ </menupopup>
+ </menulist>
+ </hbox>
+ </groupbox>
+
+ <groupbox>
+ <caption label="Tab Handling" />
+ <description>Texteditor with automatic indenting replaced the need for a tab key, but there are many editors which default to tabs due to historic reasons.</description>
+ <box align="center" >
+ <label value="Tab Width" />
+ <textbox id="txtTabWidth" size="4"
+ onchange="onTabWidthChange(this.value);"/>
+ </box>
+ <hbox align="center" >
+ <label control="mlTabPolicy" value="Tab Policy" />
+ <menulist id="mlTabPolicy" oncommand="onTabPolicySelect(this.selectedIndex)">
+ <menupopup>
+ <menuitem label="Insert Spaces for Tabs" value="0"/>
+ <menuitem label="Insert Tabs" value="1"/>
+ </menupopup>
+ </menulist>
+ </hbox>
+ </groupbox>
+ <hbox flex="1"/>
+ </vbox>
<!-- debug panel -->
<vbox>
diff --git a/install.rdf b/install.rdf
index 46a8a75..c7507c9 100644
--- a/install.rdf
+++ b/install.rdf
@@ -15,7 +15,7 @@
<Description about="urn:mozilla:install-manifest">
<em:id>sieve at mozdev.org</em:id>
- <em:version>0.2.3f</em:version>
+ <em:version>0.2.3h</em:version>
<em:type>2</em:type>
<em:targetApplication>
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-mozext/sieve-extension.git
More information about the Pkg-mozext-commits
mailing list