Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple selections #1656

Closed
wants to merge 47 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
5690634
Multiple selections
twiss Jul 6, 2013
f01858a
Multiple block selections and some bugs
twiss Jul 6, 2013
4e30116
Don't merge selections while selecting
twiss Jul 7, 2013
dd3b551
Scroll to cursor when block selecting
twiss Jul 7, 2013
ea5f5ae
Apply autocompletes to all selections
twiss Jul 7, 2013
c0252c8
Smarter copy & paste
twiss Jul 9, 2013
4f66c06
Clean up cursor drawing
twiss Jul 9, 2013
1084045
Style
twiss Jul 9, 2013
9c68a46
Remove cursor elements before drawing
twiss Jul 9, 2013
6042ecd
Improve scrolling to cursor
twiss Jul 10, 2013
c476f4d
More cleaning up of cursor drawing
twiss Jul 10, 2013
433a412
Refactor
twiss Jul 10, 2013
76b437b
Don't drag while holding control
twiss Jul 10, 2013
7c1f088
Add border and border-radius to selections
twiss Jul 11, 2013
258e43f
Keep selections sorted, performance improvements
twiss Jul 11, 2013
2715c07
Fix smart pasting when holding ctrl+v
twiss Jul 12, 2013
1e23d1d
Fix getSelectedText with empty selections
twiss Jul 12, 2013
894f445
Merge selections while block selecting, store unmerged selections
twiss Jul 12, 2013
a63205b
Fix linenumbers when undoing multiple changes
twiss Jul 12, 2013
8c9d277
More selection drawing hackery
twiss Jul 12, 2013
76b15f2
Draw more selections correctly
twiss Jul 12, 2013
7b70589
Merge normal selections while selecting too
twiss Jul 12, 2013
14773f7
Use command key for adding selections on mac
twiss Jul 15, 2013
0fa8773
Fix windows detection
twiss Jul 15, 2013
cda39f0
[comment addon] Toggle all selections as if they where one selection
twiss Jul 16, 2013
0fc33fb
More backwards compatible API
twiss Aug 2, 2013
4342a59
[matchtags addon] Support multiple selections
twiss Aug 2, 2013
2e55116
[match-highlighter addon] Support multiple selections
twiss Aug 2, 2013
425a505
[closetag addon] Support multiple selections
twiss Aug 2, 2013
0a39a6c
[continuecomment addon] Support multiple selections
twiss Aug 4, 2013
3232657
[continuelist addon] Support multiple selections
twiss Aug 4, 2013
655e902
Clip measureLineCache size and length
twiss Aug 10, 2013
e2e0122
Revert unrelated changes
twiss Aug 10, 2013
962a969
Put CodeMirror-overwrite class on cursors wrapper div, remove display…
twiss Aug 10, 2013
3616489
Add comments about pasting heuristic
twiss Aug 10, 2013
7254213
[activeline addon] delete cm.state.activeLines in setOption("styleAct…
twiss Aug 10, 2013
1993a05
Style
twiss Aug 10, 2013
2bccf0d
Fix mergeSelections in some cases
twiss Aug 11, 2013
44f6291
Always scroll to primary cursor
twiss Aug 12, 2013
46f44b8
New API
twiss Aug 13, 2013
800ff17
Update addons to new API
twiss Aug 14, 2013
4ef1ec9
Sort selections after Selection.move, maintain primary selection when…
twiss Aug 16, 2013
e9052af
Fix starting a block selection from an empty place
twiss Aug 19, 2013
23a8171
[folding demo] Support multiple selections
twiss Aug 20, 2013
517d318
Only sort selections in the end of an operation
twiss Aug 20, 2013
4095c51
Keep selection directions in reCheckSelection
twiss Aug 20, 2013
d21c1bb
Copy all selections in doc.copy()
twiss Aug 20, 2013
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
16 changes: 14 additions & 2 deletions addon/comment/comment.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,20 @@
}

CodeMirror.commands.toggleComment = function(cm) {
var from = cm.getCursor("start"), to = cm.getCursor("end");
cm.uncomment(from, to) || cm.lineComment(from, to);
var prev, selections = [], didSomething;
cm.eachSelection(function() {
var from = cm.getCursor("start"), to = cm.getCursor("end");
if (prev && from.line == prev.line) {
if (to.line == prev.line) return;
from = {line: prev.line + 1, ch: 0};
}
didSomething = cm.uncomment(from, to) || didSomething;
selections.push([from, to]);
prev = to;
});
if (!didSomething) {
for (var i = 0; i < selections.length; i++) cm.lineComment(selections[i][0], selections[i][1]);
}
};

CodeMirror.defineExtension("lineComment", function(from, to, options) {
Expand Down
62 changes: 35 additions & 27 deletions addon/edit/closebrackets.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,50 +28,58 @@
var map = {
name : "autoCloseBrackets",
Backspace: function(cm) {
if (cm.somethingSelected()) return CodeMirror.Pass;
var cur = cm.getCursor(), around = charsAround(cm, cur);
if (around && pairs.indexOf(around) % 2 == 0)
cm.replaceRange("", CodeMirror.Pos(cur.line, cur.ch - 1), CodeMirror.Pos(cur.line, cur.ch + 1));
else
return CodeMirror.Pass;
return cm.withSelection(function(sel) {
if (sel.somethingSelected()) return CodeMirror.Pass;
var cur = sel.find(), around = charsAround(cm, cur);
if (around && pairs.indexOf(around) % 2 == 0)
cm.replaceRange("", CodeMirror.Pos(cur.line, cur.ch - 1), CodeMirror.Pos(cur.line, cur.ch + 1));
else
return CodeMirror.Pass;
});
}
};
var closingBrackets = "";
for (var i = 0; i < pairs.length; i += 2) (function(left, right) {
if (left != right) closingBrackets += right;
function surround(cm) {
var selection = cm.getSelection();
cm.replaceSelection(left + selection + right);
function surround(cm, sel) {
var selection = sel.get();
sel.replace(left + selection + right);
}
function maybeOverwrite(cm) {
var cur = cm.getCursor(), ahead = cm.getRange(cur, CodeMirror.Pos(cur.line, cur.ch + 1));
if (ahead != right || cm.somethingSelected()) return CodeMirror.Pass;
function maybeOverwrite(cm, sel) {
var cur = sel.find(), ahead = cm.getRange(cur, CodeMirror.Pos(cur.line, cur.ch + 1));
if (ahead != right || sel.somethingSelected()) return CodeMirror.Pass;
else cm.execCommand("goCharRight");
}
map["'" + left + "'"] = function(cm) {
if (left == "'" && cm.getTokenAt(cm.getCursor()).type == "comment")
return CodeMirror.Pass;
if (cm.somethingSelected()) return surround(cm);
if (left == right && maybeOverwrite(cm) != CodeMirror.Pass) return;
var cur = cm.getCursor(), ahead = CodeMirror.Pos(cur.line, cur.ch + 1);
var line = cm.getLine(cur.line), nextChar = line.charAt(cur.ch);
if (line.length == cur.ch || closingBrackets.indexOf(nextChar) >= 0 || SPACE_CHAR_REGEX.test(nextChar))
cm.replaceSelection(left + right, {head: ahead, anchor: ahead});
else
return CodeMirror.Pass;
return cm.withSelection(function(sel) {
if (left == "'" && cm.getTokenAt(sel.find()).type == "comment")
return CodeMirror.Pass;
if (sel.somethingSelected()) return surround(cm, sel);
if (left == right && maybeOverwrite(cm, sel) != CodeMirror.Pass) return;
var cur = sel.find(), ahead = CodeMirror.Pos(cur.line, cur.ch + 1);
var line = cm.getLine(cur.line), nextChar = line.charAt(cur.ch);
if (line.length == cur.ch || closingBrackets.indexOf(nextChar) >= 0 || SPACE_CHAR_REGEX.test(nextChar))
sel.replace(left + right, {head: ahead, anchor: ahead});
else
return CodeMirror.Pass;
});
};
if (left != right) map["'" + right + "'"] = function(cm) {
return cm.withSelection(function(sel) {
return maybeOverwrite(cm, sel);
});
};
if (left != right) map["'" + right + "'"] = maybeOverwrite;
})(pairs.charAt(i), pairs.charAt(i + 1));
return map;
}

function buildExplodeHandler(pairs) {
return function(cm) {
var cur = cm.getCursor(), around = charsAround(cm, cur);
if (!around || pairs.indexOf(around) % 2 != 0) return CodeMirror.Pass;
cm.operation(function() {
return cm.withSelection(function(sel) {
var cur = sel.find(), around = charsAround(cm, cur);
if (!around || pairs.indexOf(around) % 2 != 0) return CodeMirror.Pass;
var newPos = CodeMirror.Pos(cur.line + 1, 0);
cm.replaceSelection("\n\n", {anchor: newPos, head: newPos}, "+input");
sel.replace("\n\n", {anchor: newPos, head: newPos}, "+input");
cm.indentLine(cur.line + 1, null, true);
cm.indentLine(cur.line + 2, null, true);
});
Expand Down
60 changes: 31 additions & 29 deletions addon/edit/closetag.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,39 +42,41 @@
"h5", "h6", "head", "html", "iframe", "layer", "legend", "object", "ol", "p", "select", "table", "ul"];

function autoCloseTag(cm, ch) {
var pos = cm.getCursor(), tok = cm.getTokenAt(pos);
var inner = CodeMirror.innerMode(cm.getMode(), tok.state), state = inner.state;
if (inner.mode.name != "xml") return CodeMirror.Pass;
var opt = cm.getOption("autoCloseTags");
return cm.withSelection(function(sel) {
var pos = sel.find(), tok = cm.getTokenAt(pos);
var inner = CodeMirror.innerMode(cm.getMode(), tok.state), state = inner.state;
if (inner.mode.name != "xml") return CodeMirror.Pass;

var opt = cm.getOption("autoCloseTags"), html = inner.mode.configuration == "html";
var dontCloseTags = (typeof opt == "object" && opt.dontCloseTags) || (html && htmlDontClose);
var indentTags = (typeof opt == "object" && opt.indentTags) || (html && htmlIndent);
var html = inner.mode.configuration == "html";
var dontCloseTags = (typeof opt == "object" && opt.dontCloseTags) || (html && htmlDontClose);
var indentTags = (typeof opt == "object" && opt.indentTags) || (html && htmlIndent);

if (ch == ">" && state.tagName) {
var tagName = state.tagName;
if (tok.end > pos.ch) tagName = tagName.slice(0, tagName.length - tok.end + pos.ch);
var lowerTagName = tagName.toLowerCase();
// Don't process the '>' at the end of an end-tag or self-closing tag
if (tok.type == "tag" && state.type == "closeTag" ||
tok.string.indexOf("/") == (tok.string.length - 1) || // match something like <someTagName />
dontCloseTags && indexOf(dontCloseTags, lowerTagName) > -1)
return CodeMirror.Pass;
if (ch == ">" && state.tagName) {
var tagName = state.tagName;
if (tok.end > pos.ch) tagName = tagName.slice(0, tagName.length - tok.end + pos.ch);
var lowerTagName = tagName.toLowerCase();
// Don't process the '>' at the end of an end-tag or self-closing tag
if (tok.type == "tag" && state.type == "closeTag" ||
tok.string.indexOf("/") == (tok.string.length - 1) || // match something like <someTagName />
dontCloseTags && indexOf(dontCloseTags, lowerTagName) > -1)
return CodeMirror.Pass;

var doIndent = indentTags && indexOf(indentTags, lowerTagName) > -1;
var curPos = doIndent ? CodeMirror.Pos(pos.line + 1, 0) : CodeMirror.Pos(pos.line, pos.ch + 1);
cm.replaceSelection(">" + (doIndent ? "\n\n" : "") + "</" + tagName + ">",
{head: curPos, anchor: curPos});
if (doIndent) {
cm.indentLine(pos.line + 1);
cm.indentLine(pos.line + 2);
var doIndent = indentTags && indexOf(indentTags, lowerTagName) > -1;
var curPos = doIndent ? CodeMirror.Pos(pos.line + 1, 0) : CodeMirror.Pos(pos.line, pos.ch + 1);
sel.replace(">" + (doIndent ? "\n\n" : "") + "</" + tagName + ">", {head: curPos, anchor: curPos});
if (doIndent) {
cm.indentLine(pos.line + 1);
cm.indentLine(pos.line + 2);
}
return;
} else if (ch == "/" && tok.string == "<") {
var tagName = state.context && state.context.tagName;
if (tagName) sel.replace("/" + tagName + ">", "end");
return;
}
return;
} else if (ch == "/" && tok.string == "<") {
var tagName = state.context && state.context.tagName;
if (tagName) cm.replaceSelection("/" + tagName + ">", "end");
return;
}
return CodeMirror.Pass;
return CodeMirror.Pass;
});
}

function indexOf(collection, elt) {
Expand Down
46 changes: 24 additions & 22 deletions addon/edit/continuecomment.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,34 @@
blockCommentContinue: " * "});

function continueComment(cm) {
var pos = cm.getCursor(), token = cm.getTokenAt(pos);
var mode = CodeMirror.innerMode(cm.getMode(), token.state).mode;
var space;
return cm.withSelection(function() {
var pos = cm.getCursor(), token = cm.getTokenAt(pos);
var mode = CodeMirror.innerMode(cm.getMode(), token.state).mode;
var space;

if (token.type == "comment" && mode.blockCommentStart) {
var end = token.string.indexOf(mode.blockCommentEnd);
var full = cm.getRange(CodeMirror.Pos(pos.line, 0), CodeMirror.Pos(pos.line, token.end)), found;
if (end != -1 && end == token.string.length - mode.blockCommentEnd.length) {
// Comment ended, don't continue it
} else if (token.string.indexOf(mode.blockCommentStart) == 0) {
space = full.slice(0, token.start);
if (!/^\s*$/.test(space)) {
space = "";
for (var i = 0; i < token.start; ++i) space += " ";
if (token.type == "comment" && mode.blockCommentStart) {
var end = token.string.indexOf(mode.blockCommentEnd);
var full = cm.getRange(CodeMirror.Pos(pos.line, 0), CodeMirror.Pos(pos.line, token.end)), found;
if (end != -1 && end == token.string.length - mode.blockCommentEnd.length) {
// Comment ended, don't continue it
} else if (token.string.indexOf(mode.blockCommentStart) == 0) {
space = full.slice(0, token.start);
if (!/^\s*$/.test(space)) {
space = "";
for (var i = 0; i < token.start; ++i) space += " ";
}
} else if ((found = full.indexOf(mode.blockCommentContinue)) != -1 &&
found + mode.blockCommentContinue.length > token.start &&
/^\s*$/.test(full.slice(0, found))) {
space = full.slice(0, found);
}
} else if ((found = full.indexOf(mode.blockCommentContinue)) != -1 &&
found + mode.blockCommentContinue.length > token.start &&
/^\s*$/.test(full.slice(0, found))) {
space = full.slice(0, found);
}
}

if (space != null)
cm.replaceSelection("\n" + space + mode.blockCommentContinue, "end");
else
return CodeMirror.Pass;
if (space != null)
cm.replaceSelection("\n" + space + mode.blockCommentContinue, "end");
else
return CodeMirror.Pass;
});
}

CodeMirror.defineOption("continueComments", null, function(cm, val, prev) {
Expand Down
26 changes: 14 additions & 12 deletions addon/edit/continuelist.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,23 @@
unorderedBullets = '*+-';

CodeMirror.commands.newlineAndIndentContinueMarkdownList = function(cm) {
var pos = cm.getCursor(),
inList = cm.getStateAfter(pos.line).list,
match;
cm.withSelection(function() {
var pos = cm.getCursor(),
inList = cm.getStateAfter(pos.line).list,
match;

if (!inList || !(match = cm.getLine(pos.line).match(listRE))) {
cm.execCommand('newlineAndIndent');
return;
}
if (!inList || !(match = cm.getLine(pos.line).match(listRE))) {
cm.execCommand('newlineAndIndent');
return;
}

var indent = match[1], after = match[4];
var bullet = unorderedBullets.indexOf(match[2]) >= 0
? match[2]
: (parseInt(match[3], 10) + 1) + '.';
var indent = match[1], after = match[4];
var bullet = unorderedBullets.indexOf(match[2]) >= 0
? match[2]
: (parseInt(match[3], 10) + 1) + '.';

cm.replaceSelection('\n' + indent + bullet + after, 'end');
cm.replaceSelection('\n' + indent + bullet + after, 'end');
});
};

}());
28 changes: 16 additions & 12 deletions addon/edit/matchtags.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,25 @@
function doMatchTags(cm) {
cm.operation(function() {
if (cm.state.matchedTags) { cm.state.matchedTags(); cm.state.matchedTags = null; }

var cur = cm.getCursor();
var match = CodeMirror.findMatchingTag(cm, cur) || CodeMirror.findEnclosingTag(cm, cur);
if (!match) return;
var one = cm.markText(match.open.from, match.open.to, {className: "CodeMirror-matchingbracket"});
var two = cm.markText(match.close.from, match.close.to, {className: "CodeMirror-matchingbracket"});
cm.state.matchedTags = function() { one.clear(); two.clear(); };
var markers = [];
cm.withSelection(function(sel) {
var cur = sel.find();
var match = CodeMirror.findMatchingTag(cm, cur) || CodeMirror.findEnclosingTag(cm, cur);
if (!match) return;
markers.push(cm.markText(match.open.from, match.open.to, {className: "CodeMirror-matchingbracket"}));
markers.push(cm.markText(match.close.from, match.close.to, {className: "CodeMirror-matchingbracket"}));
});
cm.state.matchedTags = function() { for (var i = 0; i < markers.length; i++) markers[i].clear(); };
});
}

CodeMirror.commands.toMatchingTag = function(cm) {
var found = CodeMirror.findMatchingTag(cm, cm.getCursor());
if (found) {
var other = found.at == "close" ? found.open : found.close;
cm.setSelection(other.to, other.from);
}
cm.withSelection(function(sel) {
var found = CodeMirror.findMatchingTag(cm, sel.find());
if (found) {
var other = found.at == "close" ? found.open : found.close;
sel.move(other.to, other.from);
}
});
};
})();
15 changes: 14 additions & 1 deletion addon/hint/show-hint.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
(function() {
"use strict";

function posLess(a, b) {return a.line < b.line || (a.line == b.line && a.ch < b.ch);}

CodeMirror.showHint = function(cm, getHints, options) {
// We want a single cursor position.
if (cm.somethingSelected()) return;
Expand Down Expand Up @@ -39,7 +41,18 @@
pick: function(data, i) {
var completion = data.list[i];
if (completion.hint) completion.hint(this.cm, data, completion);
else this.cm.replaceRange(getText(completion), data.from, data.to);
else {
var context = this.cm.getRange(data.from, data.to);
var cursor = this.cm.getCursor();
var pre = posLess(data.from, cursor) ? -this.cm.getRange(data.from, cursor).length : this.cm.getRange(cursor, data.from).length;
var post = posLess(cursor, data.to) ? this.cm.getRange(cursor, data.to).length : -this.cm.getRange(data.to, cursor).length;
this.cm.eachSelection(function(sel) {
var cursor = sel.find();
var from = this.findPosH(cursor, pre, "char");
var to = this.findPosH(cursor, post, "char");
if(this.getRange(from, to) == context) this.replaceRange(getText(completion), from, to);
});
}
this.close();
},

Expand Down
38 changes: 19 additions & 19 deletions addon/search/match-highlighter.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,15 @@
}
if (this.style == null) this.style = DEFAULT_TOKEN_STYLE;
if (this.minChars == null) this.minChars = DEFAULT_MIN_CHARS;
this.overlay = this.timeout = null;
this.overlays = [];
this.timeout = null;
}

CodeMirror.defineOption("highlightSelectionMatches", false, function(cm, val, old) {
if (old && old != CodeMirror.Init) {
var over = cm.state.matchHighlighter.overlay;
if (over) cm.removeOverlay(over);
clearTimeout(cm.state.matchHighlighter.timeout);
var state = cm.state.matchHighlighter;
for (var i = 0; i < state.overlays.length; i++) cm.removeOverlay(state.overlays[i]);
clearTimeout(state.timeout);
cm.state.matchHighlighter = null;
cm.off("cursorActivity", cursorActivity);
}
Expand All @@ -51,21 +52,20 @@
function highlightMatches(cm) {
cm.operation(function() {
var state = cm.state.matchHighlighter;
if (state.overlay) {
cm.removeOverlay(state.overlay);
state.overlay = null;
}

if (!cm.somethingSelected() && state.showToken) {
var tok = cm.getTokenAt(cm.getCursor()).string;
if (/\w/.test(tok))
cm.addOverlay(state.overlay = makeOverlay(tok, true, state.style));
return;
}
if (cm.getCursor("head").line != cm.getCursor("anchor").line) return;
var selection = cm.getSelection().replace(/^\s+|\s+$/g, "");
if (selection.length >= state.minChars)
cm.addOverlay(state.overlay = makeOverlay(selection, false, state.style));
for (var i = 0; i < state.overlays.length; i++) cm.removeOverlay(state.overlays[i]);
state.overlays = [];
cm.withSelection(function(sel) {
if (!sel.somethingSelected() && state.showToken) {
var tok = cm.getTokenAt(sel.find()).string;
if (/\w/.test(tok))
cm.addOverlay(state.overlays[state.overlays.length] = makeOverlay(tok, true, state.style));
return;
}
if (sel.find().line != sel.anchor.line) return;
var selection = sel.get().replace(/^\s+|\s+$/g, "");
if (selection.length >= state.minChars)
cm.addOverlay(state.overlays[state.overlays.length] = makeOverlay(selection, false, state.style));
});
});
}

Expand Down