28 changes: 23 additions & 5 deletions keymap/sublime.js
Original file line number Diff line number Diff line change
Expand Up @@ -481,17 +481,22 @@
});
};

function findAndGoTo(cm, forward) {
function getTarget(cm) {
var from = cm.getCursor("from"), to = cm.getCursor("to");
if (CodeMirror.cmpPos(from, to) == 0) {
var word = wordAt(cm, from);
if (!word.word) return;
from = word.from;
to = word.to;
}
return {from: from, to: to, query: cm.getRange(from, to), word: word};
}

var query = cm.getRange(from, to);
var cur = cm.getSearchCursor(query, forward ? to : from);
function findAndGoTo(cm, forward) {
var target = getTarget(cm);
if (!target) return;
var query = target.query;
var cur = cm.getSearchCursor(query, forward ? target.to : target.from);

if (forward ? cur.findNext() : cur.findPrevious()) {
cm.setSelection(cur.from(), cur.to());
Expand All @@ -500,12 +505,25 @@
: cm.clipPos(Pos(cm.lastLine())));
if (forward ? cur.findNext() : cur.findPrevious())
cm.setSelection(cur.from(), cur.to());
else if (word)
cm.setSelection(from, to);
else if (target.word)
cm.setSelection(target.from, target.to);
}
};
cmds[map[ctrl + "F3"] = "findUnder"] = function(cm) { findAndGoTo(cm, true); };
cmds[map["Shift-" + ctrl + "F3"] = "findUnderPrevious"] = function(cm) { findAndGoTo(cm,false); };
cmds[map["Alt-F3"] = "findAllUnder"] = function(cm) {
var target = getTarget(cm);
if (!target) return;
var cur = cm.getSearchCursor(target.query);
var matches = [];
var primaryIndex = -1;
while (cur.findNext()) {
matches.push({anchor: cur.from(), head: cur.to()});
if (cur.from().line <= target.from.line && cur.from().ch <= target.from.ch)
primaryIndex++;
}
cm.setSelections(matches, primaryIndex);
};

map["Shift-" + ctrl + "["] = "fold";
map["Shift-" + ctrl + "]"] = "unfold";
Expand Down
543 changes: 425 additions & 118 deletions keymap/vim.js

Large diffs are not rendered by default.

479 changes: 321 additions & 158 deletions lib/codemirror.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions mode/clike/clike.js
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,7 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {


),
multiLineStrings: true,
blockKeywords: words("catch class do else finally for forSome if match switch try while"),
atoms: words("true false null"),
hooks: {
Expand Down
6 changes: 3 additions & 3 deletions mode/css/css.js
Original file line number Diff line number Diff line change
Expand Up @@ -461,13 +461,13 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
"glyph-orientation-vertical", "text-anchor", "writing-mode"
], propertyKeywords = keySet(propertyKeywords_);

var nonStandardPropertyKeywords = [
var nonStandardPropertyKeywords_ = [
"scrollbar-arrow-color", "scrollbar-base-color", "scrollbar-dark-shadow-color",
"scrollbar-face-color", "scrollbar-highlight-color", "scrollbar-shadow-color",
"scrollbar-3d-light-color", "scrollbar-track-color", "shape-inside",
"searchfield-cancel-button", "searchfield-decoration", "searchfield-results-button",
"searchfield-results-decoration", "zoom"
], nonStandardPropertyKeywords = keySet(nonStandardPropertyKeywords);
], nonStandardPropertyKeywords = keySet(nonStandardPropertyKeywords_);

var colorKeywords_ = [
"aliceblue", "antiquewhite", "aqua", "aquamarine", "azure", "beige",
Expand Down Expand Up @@ -589,7 +589,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
], fontProperties = keySet(fontProperties_);

var allWords = mediaTypes_.concat(mediaFeatures_).concat(propertyKeywords_)
.concat(nonStandardPropertyKeywords).concat(colorKeywords_).concat(valueKeywords_);
.concat(nonStandardPropertyKeywords_).concat(colorKeywords_).concat(valueKeywords_);
CodeMirror.registerHelper("hintWords", "css", allWords);

function tokenCComment(stream, state) {
Expand Down
1 change: 1 addition & 0 deletions mode/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ <h2>Language modes</h2>
<li><a href="javascript/index.html">JavaScript</a></li>
<li><a href="jinja2/index.html">Jinja2</a></li>
<li><a href="julia/index.html">Julia</a></li>
<li><a href="kotlin/index.html">Kotlin</a></li>
<li><a href="css/less.html">LESS</a></li>
<li><a href="livescript/index.html">LiveScript</a></li>
<li><a href="lua/index.html">Lua</a></li>
Expand Down
2 changes: 2 additions & 0 deletions mode/javascript/javascript.js
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,8 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
var result = function() {
var state = cx.state, indent = state.indented;
if (state.lexical.type == "stat") indent = state.lexical.indented;
else for (var outer = state.lexical; outer && outer.type == ")" && outer.align; outer = outer.prev)
indent = outer.indented;
state.lexical = new JSLexical(indent, cx.stream.column(), type, null, state.lexical, info);
};
result.lex = true;
Expand Down
6 changes: 6 additions & 0 deletions mode/javascript/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,12 @@
" [keyword else]",
" [number 3];");

MT("indent_funarg",
"[variable foo]([number 10000],",
" [keyword function]([def a]) {",
" [keyword debugger];",
"};");

MT("indent_below_if",
"[keyword for] (;;)",
" [keyword if] ([variable foo])",
Expand Down
89 changes: 89 additions & 0 deletions mode/kotlin/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<!doctype html>

<title>CodeMirror: Kotlin 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="kotlin.js"></script>
<style type="text/css">.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}</style>
<div id=nav>
<a href="http://codemirror.net"><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/marijnh/codemirror">Code</a>
</ul>
<ul>
<li><a href="../index.html">Language modes</a>
<li><a class=active href="#">Kotlin</a>
</ul>
</div>

<article>
<h2>Kotlin mode</h2>


<div><textarea id="code" name="code">
package org.wasabi.http

import java.util.concurrent.Executors
import java.net.InetSocketAddress
import org.wasabi.app.AppConfiguration
import io.netty.bootstrap.ServerBootstrap
import io.netty.channel.nio.NioEventLoopGroup
import io.netty.channel.socket.nio.NioServerSocketChannel
import org.wasabi.app.AppServer

public class HttpServer(private val appServer: AppServer) {

val bootstrap: ServerBootstrap
val primaryGroup: NioEventLoopGroup
val workerGroup: NioEventLoopGroup

{
// Define worker groups
primaryGroup = NioEventLoopGroup()
workerGroup = NioEventLoopGroup()

// Initialize bootstrap of server
bootstrap = ServerBootstrap()

bootstrap.group(primaryGroup, workerGroup)
bootstrap.channel(javaClass<NioServerSocketChannel>())
bootstrap.childHandler(NettyPipelineInitializer(appServer))
}

public fun start(wait: Boolean = true) {
val channel = bootstrap.bind(appServer.configuration.port)?.sync()?.channel()

if (wait) {
channel?.closeFuture()?.sync()
}
}

public fun stop() {
// Shutdown all event loops
primaryGroup.shutdownGracefully()
workerGroup.shutdownGracefully()

// Wait till all threads are terminated
primaryGroup.terminationFuture().sync()
workerGroup.terminationFuture().sync()
}
}
</textarea></div>

<script>
var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
mode: {name: "kotlin"},
lineNumbers: true,
indentUnit: 4
});
</script>
<h3>Mode for Kotlin (http://kotlin.jetbrains.org/)</h3>
<p>Developed by Hadi Hariri (https://github.com/hhariri).</p>
<p><strong>MIME type defined:</strong> <code>text/x-kotlin</code>.</p>
</article>
280 changes: 280 additions & 0 deletions mode/kotlin/kotlin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE

(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";

CodeMirror.defineMode("kotlin", function (config, parserConfig) {
function words(str) {
var obj = {}, words = str.split(" ");
for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
return obj;
}

var multiLineStrings = parserConfig.multiLineStrings;

var keywords = words(
"package continue return object while break class data trait throw super" +
" when type this else This try val var fun for is in if do as true false null get set");
var softKeywords = words("import" +
" where by get set abstract enum open annotation override private public internal" +
" protected catch out vararg inline finally final ref");
var blockKeywords = words("catch class do else finally for if where try while enum");
var atoms = words("null true false this");

var curPunc;

function tokenBase(stream, state) {
var ch = stream.next();
if (ch == '"' || ch == "'") {
return startString(ch, stream, state);
}
// Wildcard import w/o trailing semicolon (import smth.*)
if (ch == "." && stream.eat("*")) {
return "word";
}
if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
curPunc = ch;
return null;
}
if (/\d/.test(ch)) {
if (stream.eat(/eE/)) {
stream.eat(/\+\-/);
stream.eatWhile(/\d/);
}
return "number";
}
if (ch == "/") {
if (stream.eat("*")) {
state.tokenize.push(tokenComment);
return tokenComment(stream, state);
}
if (stream.eat("/")) {
stream.skipToEnd();
return "comment";
}
if (expectExpression(state.lastToken)) {
return startString(ch, stream, state);
}
}
// Commented
if (ch == "-" && stream.eat(">")) {
curPunc = "->";
return null;
}
if (/[\-+*&%=<>!?|\/~]/.test(ch)) {
stream.eatWhile(/[\-+*&%=<>|~]/);
return "operator";
}
stream.eatWhile(/[\w\$_]/);

var cur = stream.current();
if (atoms.propertyIsEnumerable(cur)) {
return "atom";
}
if (softKeywords.propertyIsEnumerable(cur)) {
if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement";
return "softKeyword";
}

if (keywords.propertyIsEnumerable(cur)) {
if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement";
return "keyword";
}
return "word";
}

tokenBase.isBase = true;

function startString(quote, stream, state) {
var tripleQuoted = false;
if (quote != "/" && stream.eat(quote)) {
if (stream.eat(quote)) tripleQuoted = true;
else return "string";
}
function t(stream, state) {
var escaped = false, next, end = !tripleQuoted;

while ((next = stream.next()) != null) {
if (next == quote && !escaped) {
if (!tripleQuoted) {
break;
}
if (stream.match(quote + quote)) {
end = true;
break;
}
}

if (quote == '"' && next == "$" && !escaped && stream.eat("{")) {
state.tokenize.push(tokenBaseUntilBrace());
return "string";
}

if (next == "$" && !escaped && !stream.eat(" ")) {
state.tokenize.push(tokenBaseUntilSpace());
return "string";
}
escaped = !escaped && next == "\\";
}
if (multiLineStrings)
state.tokenize.push(t);
if (end) state.tokenize.pop();
return "string";
}

state.tokenize.push(t);
return t(stream, state);
}

function tokenBaseUntilBrace() {
var depth = 1;

function t(stream, state) {
if (stream.peek() == "}") {
depth--;
if (depth == 0) {
state.tokenize.pop();
return state.tokenize[state.tokenize.length - 1](stream, state);
}
} else if (stream.peek() == "{") {
depth++;
}
return tokenBase(stream, state);
}

t.isBase = true;
return t;
}

function tokenBaseUntilSpace() {
function t(stream, state) {
if (stream.eat(/[\w]/)) {
var isWord = stream.eatWhile(/[\w]/);
if (isWord) {
state.tokenize.pop();
return "word";
}
}
state.tokenize.pop();
return "string";
}

t.isBase = true;
return t;
}

function tokenComment(stream, state) {
var maybeEnd = false, ch;
while (ch = stream.next()) {
if (ch == "/" && maybeEnd) {
state.tokenize.pop();
break;
}
maybeEnd = (ch == "*");
}
return "comment";
}

function expectExpression(last) {
return !last || last == "operator" || last == "->" || /[\.\[\{\(,;:]/.test(last) ||
last == "newstatement" || last == "keyword" || last == "proplabel";
}

function Context(indented, column, type, align, prev) {
this.indented = indented;
this.column = column;
this.type = type;
this.align = align;
this.prev = prev;
}

function pushContext(state, col, type) {
return state.context = new Context(state.indented, col, type, null, state.context);
}

function popContext(state) {
var t = state.context.type;
if (t == ")" || t == "]" || t == "}")
state.indented = state.context.indented;
return state.context = state.context.prev;
}

// Interface

return {
startState: function (basecolumn) {
return {
tokenize: [tokenBase],
context: new Context((basecolumn || 0) - config.indentUnit, 0, "top", false),
indented: 0,
startOfLine: true,
lastToken: null
};
},

token: function (stream, state) {
var ctx = state.context;
if (stream.sol()) {
if (ctx.align == null) ctx.align = false;
state.indented = stream.indentation();
state.startOfLine = true;
// Automatic semicolon insertion
if (ctx.type == "statement" && !expectExpression(state.lastToken)) {
popContext(state);
ctx = state.context;
}
}
if (stream.eatSpace()) return null;
curPunc = null;
var style = state.tokenize[state.tokenize.length - 1](stream, state);
if (style == "comment") return style;
if (ctx.align == null) ctx.align = true;
if ((curPunc == ";" || curPunc == ":") && ctx.type == "statement") popContext(state);
// Handle indentation for {x -> \n ... }
else if (curPunc == "->" && ctx.type == "statement" && ctx.prev.type == "}") {
popContext(state);
state.context.align = false;
}
else if (curPunc == "{") pushContext(state, stream.column(), "}");
else if (curPunc == "[") pushContext(state, stream.column(), "]");
else if (curPunc == "(") pushContext(state, stream.column(), ")");
else if (curPunc == "}") {
while (ctx.type == "statement") ctx = popContext(state);
if (ctx.type == "}") ctx = popContext(state);
while (ctx.type == "statement") ctx = popContext(state);
}
else if (curPunc == ctx.type) popContext(state);
else if (ctx.type == "}" || ctx.type == "top" || (ctx.type == "statement" && curPunc == "newstatement"))
pushContext(state, stream.column(), "statement");
state.startOfLine = false;
state.lastToken = curPunc || style;
return style;
},

indent: function (state, textAfter) {
if (!state.tokenize[state.tokenize.length - 1].isBase) return 0;
var firstChar = textAfter && textAfter.charAt(0), ctx = state.context;
if (ctx.type == "statement" && !expectExpression(state.lastToken)) ctx = ctx.prev;
var closing = firstChar == ctx.type;
if (ctx.type == "statement") {
return ctx.indented + (firstChar == "{" ? 0 : config.indentUnit);
}
else if (ctx.align) return ctx.column + (closing ? 0 : 1);
else return ctx.indented + (closing ? 0 : config.indentUnit);
},

electricChars: "{}"
};
});

CodeMirror.defineMIME("text/x-kotlin", "kotlin");

});
192 changes: 97 additions & 95 deletions mode/meta.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,101 +12,103 @@
"use strict";

CodeMirror.modeInfo = [
{name: 'APL', mime: 'text/apl', mode: 'apl'},
{name: 'Asterisk', mime: 'text/x-asterisk', mode: 'asterisk'},
{name: 'C', mime: 'text/x-csrc', mode: 'clike'},
{name: 'C++', mime: 'text/x-c++src', mode: 'clike'},
{name: 'Cobol', mime: 'text/x-cobol', mode: 'cobol'},
{name: 'Java', mime: 'text/x-java', mode: 'clike'},
{name: 'C#', mime: 'text/x-csharp', mode: 'clike'},
{name: 'Scala', mime: 'text/x-scala', mode: 'clike'},
{name: 'Clojure', mime: 'text/x-clojure', mode: 'clojure'},
{name: 'CoffeeScript', mime: 'text/x-coffeescript', mode: 'coffeescript'},
{name: 'Common Lisp', mime: 'text/x-common-lisp', mode: 'commonlisp'},
{name: 'Cypher', mime: 'application/x-cypher-query', mode: 'cypher'},
{name: 'CSS', mime: 'text/css', mode: 'css'},
{name: 'D', mime: 'text/x-d', mode: 'd'},
{name: 'diff', mime: 'text/x-diff', mode: 'diff'},
{name: 'DTD', mime: 'application/xml-dtd', mode: 'dtd'},
{name: 'Dylan', mime: 'text/x-dylan', mode: 'dylan'},
{name: 'ECL', mime: 'text/x-ecl', mode: 'ecl'},
{name: 'Eiffel', mime: 'text/x-eiffel', mode: 'eiffel'},
{name: 'Erlang', mime: 'text/x-erlang', mode: 'erlang'},
{name: 'Fortran', mime: 'text/x-fortran', mode: 'fortran'},
{name: 'F#', mime: 'text/x-fsharp', mode: 'mllike'},
{name: 'Gas', mime: 'text/x-gas', mode: 'gas'},
{name: 'Gherkin', mime: 'text/x-feature', mode: 'gherkin'},
{name: 'GitHub Flavored Markdown', mime: 'text/x-gfm', mode: 'gfm'},
{name: 'Go', mime: 'text/x-go', mode: 'go'},
{name: 'Groovy', mime: 'text/x-groovy', mode: 'groovy'},
{name: 'HAML', mime: 'text/x-haml', mode: 'haml'},
{name: 'Haskell', mime: 'text/x-haskell', mode: 'haskell'},
{name: 'Haxe', mime: 'text/x-haxe', mode: 'haxe'},
{name: 'ASP.NET', mime: 'application/x-aspx', mode: 'htmlembedded'},
{name: 'Embedded Javascript', mime: 'application/x-ejs', mode: 'htmlembedded'},
{name: 'JavaServer Pages', mime: 'application/x-jsp', mode: 'htmlembedded'},
{name: 'HTML', mime: 'text/html', mode: 'htmlmixed'},
{name: 'HTTP', mime: 'message/http', mode: 'http'},
{name: 'Jade', mime: 'text/x-jade', mode: 'jade'},
{name: 'JavaScript', mime: 'text/javascript', mode: 'javascript'},
{name: 'JSON', mime: 'application/x-json', mode: 'javascript'},
{name: 'JSON', mime: 'application/json', mode: 'javascript'},
{name: 'JSON-LD', mime: 'application/ld+json', mode: 'javascript'},
{name: 'TypeScript', mime: 'application/typescript', mode: 'javascript'},
{name: 'Jinja2', mime: null, mode: 'jinja2'},
{name: 'Julia', mime: 'text/x-julia', mode: 'julia'},
{name: 'LESS', mime: 'text/x-less', mode: 'css'},
{name: 'LiveScript', mime: 'text/x-livescript', mode: 'livescript'},
{name: 'Lua', mime: 'text/x-lua', mode: 'lua'},
{name: 'Markdown (GitHub-flavour)', mime: 'text/x-markdown', mode: 'markdown'},
{name: 'mIRC', mime: 'text/mirc', mode: 'mirc'},
{name: 'Nginx', mime: 'text/x-nginx-conf', mode: 'nginx'},
{name: 'NTriples', mime: 'text/n-triples', mode: 'ntriples'},
{name: 'OCaml', mime: 'text/x-ocaml', mode: 'mllike'},
{name: 'Octave', mime: 'text/x-octave', mode: 'octave'},
{name: 'Pascal', mime: 'text/x-pascal', mode: 'pascal'},
{name: 'PEG.js', mime: null, mode: 'pegjs'},
{name: 'Perl', mime: 'text/x-perl', mode: 'perl'},
{name: 'PHP', mime: 'text/x-php', mode: 'php'},
{name: 'PHP(HTML)', mime: 'application/x-httpd-php', mode: 'php'},
{name: 'Pig', mime: 'text/x-pig', mode: 'pig'},
{name: 'Plain Text', mime: 'text/plain', mode: 'null'},
{name: 'Properties files', mime: 'text/x-properties', mode: 'properties'},
{name: 'Python', mime: 'text/x-python', mode: 'python'},
{name: 'Puppet', mime: 'text/x-puppet', mode: 'puppet'},
{name: 'Cython', mime: 'text/x-cython', mode: 'python'},
{name: 'R', mime: 'text/x-rsrc', mode: 'r'},
{name: 'reStructuredText', mime: 'text/x-rst', mode: 'rst'},
{name: 'Ruby', mime: 'text/x-ruby', mode: 'ruby'},
{name: 'Rust', mime: 'text/x-rustsrc', mode: 'rust'},
{name: 'Sass', mime: 'text/x-sass', mode: 'sass'},
{name: 'Scheme', mime: 'text/x-scheme', mode: 'scheme'},
{name: 'SCSS', mime: 'text/x-scss', mode: 'css'},
{name: 'Shell', mime: 'text/x-sh', mode: 'shell'},
{name: 'Sieve', mime: 'application/sieve', mode: 'sieve'},
{name: 'Smalltalk', mime: 'text/x-stsrc', mode: 'smalltalk'},
{name: 'Smarty', mime: 'text/x-smarty', mode: 'smarty'},
{name: 'SmartyMixed', mime: 'text/x-smarty', mode: 'smartymixed'},
{name: 'Solr', mime: 'text/x-solr', mode: 'solr'},
{name: 'SPARQL', mime: 'application/x-sparql-query', mode: 'sparql'},
{name: 'SQL', mime: 'text/x-sql', mode: 'sql'},
{name: 'MariaDB', mime: 'text/x-mariadb', mode: 'sql'},
{name: 'sTeX', mime: 'text/x-stex', mode: 'stex'},
{name: 'LaTeX', mime: 'text/x-latex', mode: 'stex'},
{name: 'SystemVerilog', mime: 'text/x-systemverilog', mode: 'verilog'},
{name: 'Tcl', mime: 'text/x-tcl', mode: 'tcl'},
{name: 'TiddlyWiki ', mime: 'text/x-tiddlywiki', mode: 'tiddlywiki'},
{name: 'Tiki wiki', mime: 'text/tiki', mode: 'tiki'},
{name: 'TOML', mime: 'text/x-toml', mode: 'toml'},
{name: 'Turtle', mime: 'text/turtle', mode: 'turtle'},
{name: 'VB.NET', mime: 'text/x-vb', mode: 'vb'},
{name: 'VBScript', mime: 'text/vbscript', mode: 'vbscript'},
{name: 'Velocity', mime: 'text/velocity', mode: 'velocity'},
{name: 'Verilog', mime: 'text/x-verilog', mode: 'verilog'},
{name: 'XML', mime: 'application/xml', mode: 'xml'},
{name: 'XQuery', mime: 'application/xquery', mode: 'xquery'},
{name: 'YAML', mime: 'text/x-yaml', mode: 'yaml'},
{name: 'Z80', mime: 'text/x-z80', mode: 'z80'}
{name: "APL", mime: "text/apl", mode: "apl"},
{name: "Asterisk", mime: "text/x-asterisk", mode: "asterisk"},
{name: "C", mime: "text/x-csrc", mode: "clike"},
{name: "C++", mime: "text/x-c++src", mode: "clike"},
{name: "Cobol", mime: "text/x-cobol", mode: "cobol"},
{name: "Java", mime: "text/x-java", mode: "clike"},
{name: "C#", mime: "text/x-csharp", mode: "clike"},
{name: "Scala", mime: "text/x-scala", mode: "clike"},
{name: "Clojure", mime: "text/x-clojure", mode: "clojure"},
{name: "CoffeeScript", mime: "text/x-coffeescript", mode: "coffeescript"},
{name: "Common Lisp", mime: "text/x-common-lisp", mode: "commonlisp"},
{name: "Cypher", mime: "application/x-cypher-query", mode: "cypher"},
{name: "CSS", mime: "text/css", mode: "css"},
{name: "D", mime: "text/x-d", mode: "d"},
{name: "diff", mime: "text/x-diff", mode: "diff"},
{name: "DTD", mime: "application/xml-dtd", mode: "dtd"},
{name: "Dylan", mime: "text/x-dylan", mode: "dylan"},
{name: "ECL", mime: "text/x-ecl", mode: "ecl"},
{name: "Eiffel", mime: "text/x-eiffel", mode: "eiffel"},
{name: "Erlang", mime: "text/x-erlang", mode: "erlang"},
{name: "Fortran", mime: "text/x-fortran", mode: "fortran"},
{name: "F#", mime: "text/x-fsharp", mode: "mllike"},
{name: "Gas", mime: "text/x-gas", mode: "gas"},
{name: "Gherkin", mime: "text/x-feature", mode: "gherkin"},
{name: "GitHub Flavored Markdown", mime: "text/x-gfm", mode: "gfm"},
{name: "Go", mime: "text/x-go", mode: "go"},
{name: "Groovy", mime: "text/x-groovy", mode: "groovy"},
{name: "HAML", mime: "text/x-haml", mode: "haml"},
{name: "Haskell", mime: "text/x-haskell", mode: "haskell"},
{name: "Haxe", mime: "text/x-haxe", mode: "haxe"},
{name: "ASP.NET", mime: "application/x-aspx", mode: "htmlembedded"},
{name: "Embedded Javascript", mime: "application/x-ejs", mode: "htmlembedded"},
{name: "JavaServer Pages", mime: "application/x-jsp", mode: "htmlembedded"},
{name: "HTML", mime: "text/html", mode: "htmlmixed"},
{name: "HTTP", mime: "message/http", mode: "http"},
{name: "Jade", mime: "text/x-jade", mode: "jade"},
{name: "JavaScript", mime: "text/javascript", mode: "javascript"},
{name: "JavaScript", mime: "application/javascript", mode: "javascript"},
{name: "JSON", mime: "application/x-json", mode: "javascript"},
{name: "JSON", mime: "application/json", mode: "javascript"},
{name: "JSON-LD", mime: "application/ld+json", mode: "javascript"},
{name: "TypeScript", mime: "application/typescript", mode: "javascript"},
{name: "Jinja2", mime: null, mode: "jinja2"},
{name: "Julia", mime: "text/x-julia", mode: "julia"},
{name: "Kotlin", mime: "text/x-kotlin", mode: "kotlin"},
{name: "LESS", mime: "text/x-less", mode: "css"},
{name: "LiveScript", mime: "text/x-livescript", mode: "livescript"},
{name: "Lua", mime: "text/x-lua", mode: "lua"},
{name: "Markdown (GitHub-flavour)", mime: "text/x-markdown", mode: "markdown"},
{name: "mIRC", mime: "text/mirc", mode: "mirc"},
{name: "Nginx", mime: "text/x-nginx-conf", mode: "nginx"},
{name: "NTriples", mime: "text/n-triples", mode: "ntriples"},
{name: "OCaml", mime: "text/x-ocaml", mode: "mllike"},
{name: "Octave", mime: "text/x-octave", mode: "octave"},
{name: "Pascal", mime: "text/x-pascal", mode: "pascal"},
{name: "PEG.js", mime: null, mode: "pegjs"},
{name: "Perl", mime: "text/x-perl", mode: "perl"},
{name: "PHP", mime: "text/x-php", mode: "php"},
{name: "PHP(HTML)", mime: "application/x-httpd-php", mode: "php"},
{name: "Pig", mime: "text/x-pig", mode: "pig"},
{name: "Plain Text", mime: "text/plain", mode: "null"},
{name: "Properties files", mime: "text/x-properties", mode: "properties"},
{name: "Python", mime: "text/x-python", mode: "python"},
{name: "Puppet", mime: "text/x-puppet", mode: "puppet"},
{name: "Cython", mime: "text/x-cython", mode: "python"},
{name: "R", mime: "text/x-rsrc", mode: "r"},
{name: "reStructuredText", mime: "text/x-rst", mode: "rst"},
{name: "Ruby", mime: "text/x-ruby", mode: "ruby"},
{name: "Rust", mime: "text/x-rustsrc", mode: "rust"},
{name: "Sass", mime: "text/x-sass", mode: "sass"},
{name: "Scheme", mime: "text/x-scheme", mode: "scheme"},
{name: "SCSS", mime: "text/x-scss", mode: "css"},
{name: "Shell", mime: "text/x-sh", mode: "shell"},
{name: "Sieve", mime: "application/sieve", mode: "sieve"},
{name: "Smalltalk", mime: "text/x-stsrc", mode: "smalltalk"},
{name: "Smarty", mime: "text/x-smarty", mode: "smarty"},
{name: "SmartyMixed", mime: "text/x-smarty", mode: "smartymixed"},
{name: "Solr", mime: "text/x-solr", mode: "solr"},
{name: "SPARQL", mime: "application/x-sparql-query", mode: "sparql"},
{name: "SQL", mime: "text/x-sql", mode: "sql"},
{name: "MariaDB", mime: "text/x-mariadb", mode: "sql"},
{name: "sTeX", mime: "text/x-stex", mode: "stex"},
{name: "LaTeX", mime: "text/x-latex", mode: "stex"},
{name: "SystemVerilog", mime: "text/x-systemverilog", mode: "verilog"},
{name: "Tcl", mime: "text/x-tcl", mode: "tcl"},
{name: "TiddlyWiki ", mime: "text/x-tiddlywiki", mode: "tiddlywiki"},
{name: "Tiki wiki", mime: "text/tiki", mode: "tiki"},
{name: "TOML", mime: "text/x-toml", mode: "toml"},
{name: "Turtle", mime: "text/turtle", mode: "turtle"},
{name: "VB.NET", mime: "text/x-vb", mode: "vb"},
{name: "VBScript", mime: "text/vbscript", mode: "vbscript"},
{name: "Velocity", mime: "text/velocity", mode: "velocity"},
{name: "Verilog", mime: "text/x-verilog", mode: "verilog"},
{name: "XML", mime: "application/xml", mode: "xml"},
{name: "XQuery", mime: "application/xquery", mode: "xquery"},
{name: "YAML", mime: "text/x-yaml", mode: "yaml"},
{name: "Z80", mime: "text/x-z80", mode: "z80"}
];

});
2 changes: 1 addition & 1 deletion mode/puppet/puppet.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ CodeMirror.defineMode("puppet", function () {
// Match characters that we are going to assume
// are trying to be regex
if (ch == '/') {
stream.match(/.*\//);
stream.match(/.*?\//);
return 'variable-3';
}
// Match all the numbers
Expand Down
22 changes: 18 additions & 4 deletions mode/ruby/ruby.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,30 @@ CodeMirror.defineMode("ruby", function(config) {
var ch = stream.next(), m;
if (ch == "`" || ch == "'" || ch == '"') {
return chain(readQuoted(ch, "string", ch == '"' || ch == "`"), stream, state);
} else if (ch == "/" && !stream.eol() && stream.peek() != " ") {
if (stream.eat("=")) return "operator";
return chain(readQuoted(ch, "string-2", true), stream, state);
} else if (ch == "/") {
var currentIndex = stream.current().length;
if (stream.skipTo("/")) {
var search_till = stream.current().length;
stream.backUp(stream.current().length - currentIndex);
var balance = 0; // balance brackets
while (stream.current().length < search_till) {
var chchr = stream.next();
if (chchr == "(") balance += 1;
else if (chchr == ")") balance -= 1;
if (balance < 0) break;
}
stream.backUp(stream.current().length - currentIndex);
if (balance == 0)
return chain(readQuoted(ch, "string-2", true), stream, state);
}
return "operator";
} else if (ch == "%") {
var style = "string", embed = true;
if (stream.eat("s")) style = "atom";
else if (stream.eat(/[WQ]/)) style = "string";
else if (stream.eat(/[r]/)) style = "string-2";
else if (stream.eat(/[wxq]/)) { style = "string"; embed = false; }
var delim = stream.eat(/[^\w\s]/);
var delim = stream.eat(/[^\w\s=]/);
if (!delim) return "operator";
if (matching.propertyIsEnumerable(delim)) delim = matching[delim];
return chain(readQuoted(delim, style, embed, true), stream, state);
Expand Down
29 changes: 17 additions & 12 deletions mode/smartymixed/smartymixed.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
* @date 05.07.2013
*/

// Warning: Don't base other modes on this one. This here is a
// terrible way to write a mixed mode.

(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"), require("../htmlmixed/htmlmixed"), require("../smarty/smarty"));
Expand All @@ -20,11 +23,10 @@
"use strict";

CodeMirror.defineMode("smartymixed", function(config) {
var settings, regs, helpers, parsers,
htmlMixedMode = CodeMirror.getMode(config, "htmlmixed"),
smartyMode = CodeMirror.getMode(config, "smarty"),
var htmlMixedMode = CodeMirror.getMode(config, "htmlmixed");
var smartyMode = CodeMirror.getMode(config, "smarty");

settings = {
var settings = {
rightDelimiter: '}',
leftDelimiter: '{'
};
Expand All @@ -36,15 +38,18 @@ CodeMirror.defineMode("smartymixed", function(config) {
settings.rightDelimiter = config.rightDelimiter;
}

regs = {
smartyComment: new RegExp("^" + settings.leftDelimiter + "\\*"),
literalOpen: new RegExp(settings.leftDelimiter + "literal" + settings.rightDelimiter),
literalClose: new RegExp(settings.leftDelimiter + "\/literal" + settings.rightDelimiter),
hasLeftDelimeter: new RegExp(".*" + settings.leftDelimiter),
htmlHasLeftDelimeter: new RegExp("[^<>]*" + settings.leftDelimiter)
function reEsc(str) { return str.replace(/[^\s\w]/g, "\\$&"); }

var reLeft = reEsc(settings.leftDelimiter), reRight = reEsc(settings.rightDelimiter);
var regs = {
smartyComment: new RegExp("^" + reRight + "\\*"),
literalOpen: new RegExp(reLeft + "literal" + reRight),
literalClose: new RegExp(reLeft + "\/literal" + reRight),
hasLeftDelimeter: new RegExp(".*" + reLeft),
htmlHasLeftDelimeter: new RegExp("[^<>]*" + reLeft)
};

helpers = {
var helpers = {
chain: function(stream, state, parser) {
state.tokenize = parser;
return parser(stream, state);
Expand All @@ -70,7 +75,7 @@ CodeMirror.defineMode("smartymixed", function(config) {
}
};

parsers = {
var parsers = {
html: function(stream, state) {
if (!state.inLiteral && stream.match(regs.htmlHasLeftDelimeter, false) && state.htmlMixedState.htmlState.tagName === null) {
state.tokenize = parsers.smarty;
Expand Down
2 changes: 1 addition & 1 deletion mode/vbscript/vbscript.js
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ CodeMirror.defineMode("vbscript", function(conf, parserConf) {
style = state.tokenize(stream, state);

current = stream.current();
if (style.substr(0, 8) === 'variable' || style==='builtin' || style==='keyword'){//|| knownWords.indexOf(current.substring(1)) > -1) {
if (style && (style.substr(0, 8) === 'variable' || style==='builtin' || style==='keyword')){//|| knownWords.indexOf(current.substring(1)) > -1) {
if (style === 'builtin' || style === 'keyword') style='variable';
if (knownWords.indexOf(current.substr(1)) > -1) style='variable-2';

Expand Down
2 changes: 1 addition & 1 deletion mode/yaml/yaml.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ CodeMirror.defineMode("yaml", function() {
}

/* pairs (associative arrays) -> key */
if (!state.pair && stream.match(/^\s*\S+(?=\s*:($|\s))/i)) {
if (!state.pair && stream.match(/^\s*(?:[,\[\]{}&*!|>'"%@`][^\s'":]|[^,\[\]{}#&*!|>'"%@`])[^#]*?(?=\s*:($|\s))/)) {
state.pair = true;
state.keyCol = stream.indentation();
return "atom";
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "codemirror",
"version":"4.3.0",
"version":"4.4.0",
"main": "lib/codemirror.js",
"description": "In-browser code editing made bearable",
"licenses": [{"type": "MIT",
Expand Down
37 changes: 37 additions & 0 deletions test/comment_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ namespace = "comment_";
}

var simpleProg = "function foo() {\n return bar;\n}";
var inlineBlock = "foo(/* bar */ true);";
var inlineBlocks = "foo(/* bar */ true, /* baz */ false);";
var multiLineInlineBlock = ["above();", "foo(/* bar */ true);", "below();"];

test("block", "javascript", function(cm) {
cm.blockComment(Pos(0, 3), Pos(3, 0), {blockCommentLead: " *"});
Expand All @@ -19,6 +22,17 @@ namespace = "comment_";
cm.uncomment(Pos(0, 3), Pos(2, 0), {blockCommentLead: " *"});
}, simpleProg, simpleProg);

test("blockToggle2", "javascript", function(cm) {
cm.setCursor({line: 0, ch: 7 /* inside the block comment */});
cm.execCommand("toggleComment");
}, inlineBlock, "foo(bar true);");

// This test should work but currently fails.
// test("blockToggle3", "javascript", function(cm) {
// cm.setCursor({line: 0, ch: 7 /* inside the first block comment */});
// cm.execCommand("toggleComment");
// }, inlineBlocks, "foo(bar true, /* baz */ false);");

test("line", "javascript", function(cm) {
cm.lineComment(Pos(1, 1), Pos(1, 1));
}, simpleProg, "function foo() {\n// return bar;\n}");
Expand All @@ -36,6 +50,29 @@ namespace = "comment_";
cm.blockComment(Pos(0, 0), Pos(1));
}, "def blah()\n return hah\n", "# def blah()\n# return hah\n");

test("ignoreExternalBlockComments", "javascript", function(cm) {
cm.execCommand("toggleComment");
}, inlineBlocks, "// " + inlineBlocks);

test("ignoreExternalBlockComments2", "javascript", function(cm) {
cm.setCursor({line: 0, ch: null /* eol */});
cm.execCommand("toggleComment");
}, inlineBlocks, "// " + inlineBlocks);

test("ignoreExternalBlockCommentsMultiLineAbove", "javascript", function(cm) {
cm.setSelection({line: 0, ch: 0}, {line: 1, ch: 1});
cm.execCommand("toggleComment");
}, multiLineInlineBlock.join("\n"), ["// " + multiLineInlineBlock[0],
"// " + multiLineInlineBlock[1],
multiLineInlineBlock[2]].join("\n"));

test("ignoreExternalBlockCommentsMultiLineBelow", "javascript", function(cm) {
cm.setSelection({line: 1, ch: 13 /* after end of block comment */}, {line: 2, ch: 1});
cm.execCommand("toggleComment");
}, multiLineInlineBlock.join("\n"), [multiLineInlineBlock[0],
"// " + multiLineInlineBlock[1],
"// " + multiLineInlineBlock[2]].join("\n"));

test("commentRange", "javascript", function(cm) {
cm.blockComment(Pos(1, 2), Pos(1, 13), {fullLines: false});
}, simpleProg, "function foo() {\n /*return bar;*/\n}");
Expand Down
2 changes: 1 addition & 1 deletion test/lint/lint.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ var topAllowedGlobals = Object.create(null);
("Error RegExp Number String Array Function Object Math Date undefined " +
"parseInt parseFloat Infinity NaN isNaN " +
"window document navigator prompt alert confirm console " +
"FileReader Worker postMessage importScripts " +
"screen FileReader Worker postMessage importScripts " +
"setInterval clearInterval setTimeout clearTimeout " +
"CodeMirror " +
"test exports require module define")
Expand Down
35 changes: 35 additions & 0 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1988,3 +1988,38 @@ testCM("resizeLineWidget", function(cm) {
cm.setSize(40);
is(widget.parentNode.offsetWidth < 42);
});

testCM("combinedOperations", function(cm) {
var place = document.getElementById("testground");
var other = CodeMirror(place, {value: "123"});
try {
cm.operation(function() {
cm.addLineClass(0, "wrap", "foo");
other.addLineClass(0, "wrap", "foo");
});
eq(byClassName(cm.getWrapperElement(), "foo").length, 1);
eq(byClassName(other.getWrapperElement(), "foo").length, 1);
cm.operation(function() {
cm.removeLineClass(0, "wrap", "foo");
other.removeLineClass(0, "wrap", "foo");
});
eq(byClassName(cm.getWrapperElement(), "foo").length, 0);
eq(byClassName(other.getWrapperElement(), "foo").length, 0);
} finally {
place.removeChild(other.getWrapperElement());
}
}, {value: "abc"});

testCM("eventOrder", function(cm) {
var seen = [];
cm.on("change", function() {
if (!seen.length) cm.replaceSelection(".");
seen.push("change");
});
cm.on("cursorActivity", function() {
cm.replaceSelection("!");
seen.push("activity");
});
cm.replaceSelection("/");
eq(seen.join(","), "change,change,activity,change");
});
164 changes: 153 additions & 11 deletions test/vim_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -895,6 +895,20 @@ testVim('cc_append', function(cm, vim, helpers) {
helpers.doKeys('c', 'c');
eq(expectedLineCount, cm.lineCount());
});
testVim('c_visual_block', function(cm, vim, helpers) {
cm.setCursor(0, 1);
helpers.doKeys('<C-v>', '2', 'j', 'l', 'l', 'l', 'c');
var replacement = new Array(cm.listSelections().length+1).join('hello ').split(' ');
replacement.pop();
cm.replaceSelections(replacement);
eq('1hello\n5hello\nahellofg', cm.getValue());
cm.setCursor(2, 3);
helpers.doKeys('<C-v>', '2', 'k', 'h', 'C');
replacement = new Array(cm.listSelections().length+1).join('world ').split(' ');
replacement.pop();
cm.replaceSelections(replacement);
eq('1hworld\n5hworld\nahworld', cm.getValue());
}, {value: '1234\n5678\nabcdefg'});
// Swapcase commands edit in place and do not modify registers.
testVim('g~w_repeat', function(cm, vim, helpers) {
// Assert that dw does delete newline if it should go to the next line, and
Expand All @@ -918,8 +932,34 @@ testVim('g~g~', function(cm, vim, helpers) {
var register = helpers.getRegisterController().getRegister();
eq('', register.toString());
is(!register.linewise);
eqPos(curStart, cm.getCursor());
eqPos({line: curStart.line, ch:0}, cm.getCursor());
}, { value: ' word1\nword2\nword3\nword4\nword5\nword6' });
testVim('visual_block_~', function(cm, vim, helpers) {
cm.setCursor(1, 1);
helpers.doKeys('<C-v>', 'l', 'l', 'j', '~');
helpers.assertCursorAt(1, 1);
eq('hello\nwoRLd\naBCDe', cm.getValue());
cm.setCursor(2, 0);
helpers.doKeys('v', 'l', 'l', '~');
helpers.assertCursorAt(2, 0);
eq('hello\nwoRLd\nAbcDe', cm.getValue());
},{value: 'hello\nwOrld\nabcde' });
testVim('._swapCase_visualBlock', function(cm, vim, helpers) {
helpers.doKeys('<C-v>', 'j', 'j', 'l', '~');
cm.setCursor(0, 3);
helpers.doKeys('.');
eq('HelLO\nWorLd\nAbcdE', cm.getValue());
},{value: 'hEllo\nwOrlD\naBcDe' });
testVim('._delete_visualBlock', function(cm, vim, helpers) {
helpers.doKeys('<C-v>', 'j', 'x');
eq('ive\ne\nsome\nsugar', cm.getValue());
helpers.doKeys('.');
eq('ve\n\nsome\nsugar', cm.getValue());
helpers.doKeys('j', 'j', '.');
eq('ve\n\nome\nugar', cm.getValue());
helpers.doKeys('u', '<C-r>', '.');
eq('ve\n\nme\ngar', cm.getValue());
},{value: 'give\nme\nsome\nsugar' });
testVim('>{motion}', function(cm, vim, helpers) {
cm.setCursor(1, 3);
var expectedLineCount = cm.lineCount();
Expand Down Expand Up @@ -1307,6 +1347,19 @@ testVim('r', function(cm, vim, helpers) {
helpers.doKeys('v', 'j', 'h', 'r', '<Space>');
eq('wuuu \n her', cm.getValue(),'Replacing selection by space-characters failed');
}, { value: 'wordet\nanother' });
testVim('r_visual_block', function(cm, vim, helpers) {
cm.setCursor(2, 3);
helpers.doKeys('<C-v>', 'k', 'k', 'h', 'h', 'r', 'l');
eq('1lll\n5lll\nalllefg', cm.getValue());
helpers.doKeys('<C-v>', 'l', 'j', 'r', '<Space>');
eq('1 l\n5 l\nalllefg', cm.getValue());
cm.setCursor(2, 0);
helpers.doKeys('o');
helpers.doInsertModeKeys('Esc');
cm.replaceRange('\t\t', cm.getCursor());
helpers.doKeys('<C-v>', 'h', 'h', 'r', 'r');
eq('1 l\n5 l\nalllefg\nrrrrrrrr', cm.getValue());
}, {value: '1234\n5678\nabcdefg'});
testVim('R', function(cm, vim, helpers) {
cm.setCursor(0, 1);
helpers.doKeys('R');
Expand Down Expand Up @@ -1573,6 +1626,50 @@ testVim('visual_line', function(cm, vim, helpers) {
helpers.doKeys('l', 'V', 'l', 'j', 'j', 'd');
eq(' 4\n 5', cm.getValue());
}, { value: ' 1\n 2\n 3\n 4\n 5' });
testVim('visual_block', function(cm, vim, helpers) {
// test the block selection with lines of different length
// i.e. extending the selection
// till the end of the longest line.
helpers.doKeys('<C-v>', 'l', 'j', 'j', '6', 'l', 'd');
helpers.doKeys('d', 'd', 'd', 'd');
eq('', cm.getValue());
// check for left side selection in case
// of moving up to a shorter line.
cm.replaceRange('hello world\n{\nthis is\nsparta!', cm.getCursor());
cm.setCursor(3, 4);
helpers.doKeys('<C-v>', 'l', 'k', 'k', 'd');
eq('hello world\n{\nt is\nsta!', cm.getValue());
cm.replaceRange('12345\n67891\nabcde', {line: 0, ch: 0}, {line: cm.lastLine(), ch: 6});
cm.setCursor(1, 2);
helpers.doKeys('<C-v>', '2', 'l', 'k');
// circle around the anchor
// and check the selections
var selections = cm.getSelections();
eq('345891', selections.join(''));
helpers.doKeys('4', 'h');
selections = cm.getSelections();
eq('123678', selections.join(''));
helpers.doKeys('j', 'j');
selections = cm.getSelections();
eq('678abc', selections.join(''));
helpers.doKeys('4', 'l');
selections = cm.getSelections();
eq('891cde', selections.join(''));
// switch between visual modes
cm.setCursor(1, 1);
// blockwise to characterwise visual
helpers.doKeys('<C-v>', '<C-v>', 'j', 'l', 'v');
selections = cm.getSelections();
eq('7891\nabc', selections.join(''));
// characterwise to blockwise
helpers.doKeys('<C-v>');
selections = cm.getSelections();
eq('78bc', selections.join(''));
// blockwise to linewise visual
helpers.doKeys('V');
selections = cm.getSelections();
eq('67891\nabcde', selections.join(''));
}, {value: '1234\n5678\nabcdefg'});
testVim('visual_marks', function(cm, vim, helpers) {
helpers.doKeys('l', 'v', 'l', 'l', 'j', 'j', 'v');
// Test visual mode marks
Expand Down Expand Up @@ -1600,7 +1697,8 @@ testVim('reselect_visual', function(cm, vim, helpers) {
eq('123456\n2345\nbar', cm.getValue());
cm.setCursor(0, 0);
helpers.doKeys('g', 'v');
helpers.assertCursorAt(1, 3);
// here the fake cursor is at (1, 3)
helpers.assertCursorAt(1, 4);
eqPos(makeCursor(1, 0), cm.getCursor('anchor'));
helpers.doKeys('v');
cm.setCursor(2, 0);
Expand All @@ -1613,18 +1711,33 @@ testVim('reselect_visual', function(cm, vim, helpers) {
eq('123456\n2345\nbar', cm.getValue());
}, { value: '123456\nfoo\nbar' });
testVim('reselect_visual_line', function(cm, vim, helpers) {
helpers.doKeys('l', 'V', 'l', 'j', 'j', 'V', 'g', 'v', 'd');
eq(' foo\n and\n bar', cm.getValue());
cm.setCursor(0, 0);
helpers.doKeys('l', 'V', 'j', 'j', 'V', 'g', 'v', 'd');
eq('foo\nand\nbar', cm.getValue());
cm.setCursor(1, 0);
helpers.doKeys('V', 'y', 'j');
helpers.doKeys('V', 'p' , 'g', 'v', 'd');
eq(' foo\n bar', cm.getValue());
}, { value: ' hello\n this\n is \n foo\n and\n bar' });
eq('foo\nand', cm.getValue());
}, { value: 'hello\nthis\nis\nfoo\nand\nbar' });
testVim('reselect_visual_block', function(cm, vim, helpers) {
cm.setCursor(1, 2);
helpers.doKeys('<C-v>', 'k', 'h', '<C-v>');
cm.setCursor(2, 1);
helpers.doKeys('v', 'l', 'g', 'v');
helpers.assertCursorAt(0, 1);
// Ensure selection is done with visual block mode rather than one
// continuous range.
eq(cm.getSelections().join(''), '23oo')
helpers.doKeys('g', 'v');
helpers.assertCursorAt(2, 3);
// Ensure selection of deleted range
cm.setCursor(1, 1);
helpers.doKeys('v', '<C-v>', 'j', 'd', 'g', 'v');
eq(cm.getSelections().join(''), 'or');
}, { value: '123456\nfoo\nbar' });
testVim('s_normal', function(cm, vim, helpers) {
cm.setCursor(0, 1);
helpers.doKeys('s');
helpers.doInsertModeKeys('Esc');
helpers.assertCursorAt(0, 0);
eq('ac', cm.getValue());
}, { value: 'abc'});
testVim('s_visual', function(cm, vim, helpers) {
Expand All @@ -1634,19 +1747,28 @@ testVim('s_visual', function(cm, vim, helpers) {
helpers.assertCursorAt(0, 0);
eq('ac', cm.getValue());
}, { value: 'abc'});
testVim('o_visual', function(cm,vim,helpers) {
testVim('o_visual', function(cm, vim, helpers) {
cm.setCursor(0,0);
helpers.doKeys('v','l','l','l','o');
helpers.assertCursorAt(0,0);
helpers.doKeys('v','v','j','j','j','o');
helpers.assertCursorAt(0,0);
helpers.doKeys('o');
helpers.doKeys('O');
helpers.doKeys('l','l')
helpers.assertCursorAt(3, 3);
helpers.doKeys('d');
eq('p',cm.getValue());
}, { value: 'abcd\nefgh\nijkl\nmnop'});
testVim('uppercase/lowercase_visual', function(cm, vim, helpers) {
testVim('o_visual_block', function(cm, vim, helpers) {
cm.setCursor(0, 1);
helpers.doKeys('<C-v>','3','j','l','l', 'o');
helpers.assertCursorAt(0, 1);
helpers.doKeys('O');
helpers.assertCursorAt(0, 4);
helpers.doKeys('o');
helpers.assertCursorAt(3, 1);
}, { value: 'abcd\nefgh\nijkl\nmnop'});
testVim('changeCase_visual', function(cm, vim, helpers) {
cm.setCursor(0, 0);
helpers.doKeys('v', 'l', 'l');
helpers.doKeys('U');
Expand All @@ -1665,6 +1787,18 @@ testVim('uppercase/lowercase_visual', function(cm, vim, helpers) {
helpers.doKeys('V', 'U', 'j', '.');
eq('ABCDEF\nGHIJKL\nMnopq\nSHORT LINE\nLONG LINE OF TEXT', cm.getValue());
}, { value: 'abcdef\nghijkl\nmnopq\nshort line\nlong line of text'});
testVim('changeCase_visual_block', function(cm, vim, helpers) {
cm.setCursor(2, 1);
helpers.doKeys('<C-v>', 'k', 'k', 'h', 'U');
eq('ABcdef\nGHijkl\nMNopq\nfoo', cm.getValue());
cm.setCursor(0, 2);
helpers.doKeys('.');
eq('ABCDef\nGHIJkl\nMNOPq\nfoo', cm.getValue());
// check when last line is shorter.
cm.setCursor(2, 2);
helpers.doKeys('.');
eq('ABCDef\nGHIJkl\nMNOPq\nfoO', cm.getValue());
}, { value: 'abcdef\nghijkl\nmnopq\nfoo'});
testVim('visual_paste', function(cm, vim, helpers) {
cm.setCursor(0, 0);
helpers.doKeys('v', 'l', 'l', 'y', 'j', 'v', 'l', 'p');
Expand Down Expand Up @@ -2013,6 +2147,14 @@ testVim('yank_register', function(cm, vim, helpers) {
});
helpers.doKeys(':');
}, { value: 'foo\nbar'});
testVim('yank_visual_block', function(cm, vim, helpers) {
cm.setCursor(0, 1);
helpers.doKeys('<C-v>', 'l', 'j', '"', 'a', 'y');
cm.openNotification = helpers.fakeOpenNotification(function(text) {
is(/a\s+oo\nar/.test(text));
});
helpers.doKeys(':');
}, { value: 'foo\nbar'});
testVim('yank_append_line_to_line_register', function(cm, vim, helpers) {
cm.setCursor(0, 0);
helpers.doKeys('"', 'a', 'y', 'y');
Expand Down