CodeMirror Comment Toggle Bug Demo
by Nick Iaconis
HTML
<script src="https://codemirror.net/lib/codemirror.js"></script>
<link rel="stylesheet" href="https://codemirror.net/lib/codemirror.css">
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css">
<script src="https://codemirror.net/addon/comment/comment.js"></script>
<script src="https://codemirror.net/mode/sql/sql.js"></script>
<div id="root">
<h2>
Expected Behavior
</h2>
<p>
It should be possible to toggle line comments for lines starting with a keyword specified in either of <span class="code">parserConfig.keywords</span> or <span class="code">parserConfig.client</span>.
</p>
<p>
This example uses the default config specified by the <span class="code">"sql"</span> mode. In this case, <span class="code">parserConfig.keywords</span> contains the keyword "count".
</p>
<textarea id="editor1">SELECT
foo,
count(*) -- can toggle line comment (class: cm-keyword)
FROM table
WHERE
bar='a
multi-line
string';</textarea>
<h2>
Actual Behavior
</h2>
<p>
It is not possible to toggle line comments for lines starting with a keyword specified in <span class="code">parserConfig.client</span>.
</p>
<p>
This example uses a custom config. In this case, <span class="code">parserConfig.client</span> contains the keyword "count".
</p>
<textarea id="editor2">SELECT
foo,
count(*) -- cannot toggle line comment (class: cm-string-2)
FROM table
WHERE
bar='a
multi-line
string';</textarea>
<h2>
Analysis
</h2>
<p>
This appears to happen because the function <span class="code">probablyInsideString</span> returns true for tokens of type <span class="code">string-2</span>, even though multi-line strings have a token type of <span class="code">string</span> in practice.
</p>
<h2>
Recommendation
</h2>
<p>
To fix this, <span class="code">probablyInsideString</span> should be updated to return false for tokens of type <span class="code">string-2</span>.
</p>
<h2>
CodeMirror Version
...
CSS
body, html {
padding: 15px;
}
#editor1, #editor2 {
border: 1px solid #DDD;
}
.marked-text {
/* background: red; */
}
.code {
padding: 2px;
font-family: Menlo,Monaco,Consolas,"Courier New",monospace;
font-size: 13px;
color: #333;
background-color: #f5f5f5;
border: 1px solid #ccc;
border-radius: 4px;
word-wrap: break-word;
}
JavaScript
const root = document.getElementById('root');
const isMac = /Mac/i.test(navigator.platform);
const sqlKeywords1 = "alter and as asc begin between by create delete desc distinct drop from group having in insert into is join like not on or order select set table union update values where limit";
const sqlKeywords2 = "count";
const editor1 = CodeMirror.fromTextArea(document.getElementById("editor1"), {
lineNumbers: true,
indentUnit: 4,
extraKeys: {
[isMac ? 'Cmd-/' : 'Ctrl-/']: toggleComment,
},
});
editor1.setOption('mode', 'sql');
const editor2 = CodeMirror.fromTextArea(document.getElementById("editor2"), {
lineNumbers: true,
indentUnit: 4,
extraKeys: {
[isMac ? 'Cmd-/' : 'Ctrl-/']: toggleComment,
},
});
const parserConfig = {
id: 'custom-sql',
name: 'sql',
client: set(sqlKeywords2),
keywords: set(sqlKeywords1),
};
const mimeType = `text/${parserConfig.id}`;
CodeMirror.defineMIME(mimeType, parserConfig);
editor2.setOption('mode', mimeType);
function toggleComment(cm) {
let lineComment = '--';
// Passing a lineComment of "--" won't uncomment a "//" comment, so detect when "//" is being used
const pos = cm.getCursor();
const tokens = cm.getLineTokens(pos.line, true);
for (const token of tokens) {
if (token.type === 'comment') {
const commentType = token.string.substr(0, 2);
if (commentType === '//') {
// don't take C style block comments (/* ... */) into account for cmd-/
lineComment = commentType;
}
break;
}
}
cm.toggleComment({
indent: true,
lineComment
});
}
function set(str) {
var obj = {}, words = str.split(" ");
for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
return obj;
}
const version = document.createElement('p');
version.innerText = CodeMirror.version;
root.appendChild(version);