Skip to content

Commit

Permalink
make quotes check more generic; add auto quote insertion for javascri…
Browse files Browse the repository at this point in the history
…pt (backtick)
  • Loading branch information
mkslanc committed Feb 22, 2023
1 parent f8b1ca7 commit b331e0d
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 49 deletions.
68 changes: 39 additions & 29 deletions src/mode/behaviour/behaviour_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,25 +53,25 @@ module.exports = {
exec("addCursorBelow", 2);

exec("insertstring", 1, "if ");

// pairing (
// pairing (
exec("insertstring", 1, "(");
testValue("if ()");
testSelection(0, 4);
exec("insertstring", 1, ")");
testValue("if ()");
testSelection(0, 5);

// pairing [
// pairing [
exec("gotoleft", 1);
exec("insertstring", 1, "[");
testValue("if ([])");
testSelection(0, 5);

exec("insertstring", 1, "]");
testValue("if ([])");
testSelection(0, 6);

// test deletion
exec("gotoleft", 1);
exec("backspace", 1);
Expand All @@ -82,22 +82,22 @@ module.exports = {
exec("insertstring", 1, "{");
testValue("if (){}");
testSelection(0, 6);

exec("insertstring", 1, "}");
testValue("if (){}");
testSelection(0, 7);

exec("gotolinestart", 1);
exec("insertstring", 1, "(");
testValue("(if (){}");
exec("backspace", 1);

editor.setValue("");
exec("insertstring", 1, "{");
assert.equal(editor.getValue(), "{");
exec("insertstring", 1, "\n");
assert.equal(editor.getValue(), "{\n \n}");

editor.setValue("");
exec("insertstring", 1, "(");
exec("insertstring", 1, '"');
Expand All @@ -106,7 +106,7 @@ module.exports = {
exec("backspace", 1);
exec("insertstring", 1, '"');
assert.equal(editor.getValue(), '("")');

editor.setValue("('foo')", 1);
exec("gotoleft", 1);
exec("selectleft", 1);
Expand All @@ -119,7 +119,7 @@ module.exports = {
exec("selectleft", 1);
exec("insertstring", 1, '"');
assert.equal(editor.getValue(), '("foo")');

editor.setValue("", 1);
exec("selectleft", 1);
exec("insertstring", 1, '"');
Expand All @@ -128,8 +128,8 @@ module.exports = {
exec("insertstring", 1, 'n');
exec("insertstring", 1, '"');
assert.equal(editor.getValue(), '"\\n"');

editor.setValue("");
editor.setValue("");
exec("insertstring", 1, '`');
assert.equal(editor.getValue(), '``');
exec("insertstring", 1, 'n');
Expand All @@ -154,13 +154,13 @@ module.exports = {
assert.equal(editor.session.getLine(2), " ");
editor.session.setValue(["<OuterTag",
" <xyzrt"
].join("\n"));
].join("\n"));
exec("golinedown", 1);
exec("gotolineend", 1);
exec("selectleft", 3);
exec("insertstring", 1, '>');
assert.equal(editor.session.getLine(1), " <xy></xy>");

editor.setValue(["<a x='11'",
"<b a='",
" ",
Expand All @@ -180,24 +180,24 @@ module.exports = {
" > ",
"'> >"
].join("\n"));

editor.setValue("");
"<div x='1'>".split("").forEach(function(ch) {
exec("insertstring", 1, ch);
});
assert.equal(editor.getValue(), "<div x='1'></div>");
exec("insertstring", 1, ">");
assert.equal(editor.getValue(), "<div x='1'>></div>");

editor.setValue("<div '", 1);
exec("selectleft", 1);
exec("insertstring", 1, '"');
assert.equal(editor.getValue(), "<div \"");

exec("selectleft", 1);
exec("insertstring", 1, "'");
assert.equal(editor.getValue(), "<div '");

exec("selectleft", 1);
exec("insertstring", 1, "a");
exec("selectleft", 1);
Expand All @@ -211,13 +211,13 @@ module.exports = {
exec("selectleft", 1);
exec("insertstring", 1, "'");
assert.equal(editor.getValue(), "<div '");

editor.setWrapBehavioursEnabled(true);
editor.setValue("<div a", 1);
exec("selectleft", 1);
exec("insertstring", 1, "'");
assert.equal(editor.getValue(), "<div 'a'");

editor.setValue("<div a=></div>", 1);
exec("gotoleft", 7);
exec("insertstring", 1, '"');
Expand All @@ -226,7 +226,7 @@ module.exports = {
exec("gotoright", 1);
exec("insertstring", 1, "\n");
assert.equal(editor.getValue(), "<div a=\"\">\n \n</div>");

exec("undo", 1);
assert.equal(editor.getValue(), "<div a=\"\"></div>");
exec("gotoleft", 1);
Expand All @@ -238,26 +238,26 @@ module.exports = {
assert.equal(editor.getValue(), "<div a=></div>");
exec("backspace", 1);
assert.equal(editor.getValue(), "<div a></div>");

editor.setValue(" <div><div>", 1);
editor.selection.moveTo(0, 9);
exec("insertstring", 1, "\n");
assert.equal(editor.getValue(), " <div>\n <div>");

editor.setValue(" <div></div>", 1);
exec("insertstring", 1, "\n");
assert.equal(editor.getValue(), " <div></div>\n ");

editor.setValue(" <br><br>", 1);
editor.selection.moveTo(0, 8);
exec("insertstring", 1, "\n");
assert.equal(editor.getValue(), " <br>\n <br>");

editor.setValue("<div a='x", 1);
exec("gotoleft", 1);
exec("insertstring", 1, ">");
assert.equal(editor.getValue(), "<div a='>x");

editor.setValue("");
"<!DOCTYPE html></div><link><a>".split("").forEach(function(ch) {
exec("insertstring", 1, ch);
Expand All @@ -274,7 +274,7 @@ module.exports = {
exec("backspace", 2);
exec("insertstring", 1, "'");
assert.equal(editor.getValue(), "'");

editor.session.setMode(new JavaScriptMode);
editor.setValue("");
exec("insertstring", 1, '"');
Expand All @@ -287,6 +287,16 @@ module.exports = {
exec("insertstring", 1, '`');
exec("insertstring", 1, 'b');
assert.equal(editor.getValue(), "`b`");

editor.setValue("");
exec("insertstring", 1, 'a');
exec("insertstring", 1, "'");
assert.equal(editor.getValue(), "a'");

editor.setValue("");
exec("insertstring", 1, 'b');
exec("insertstring", 1, "`");
assert.equal(editor.getValue(), "b``");
},
"test: css": function() {
editor.session.setMode(new CSSMode());
Expand Down
38 changes: 27 additions & 11 deletions src/mode/behaviour/cstyle.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,15 @@ var getWrapped = function(selection, selected, opening, closing) {
]
};
};

/**
* Creates a new Cstyle behaviour object with the specified options.
* @constructor
* @param {Object} options - The options for the Cstyle behaviour object.
* @param {boolean} options.braces - Whether to force braces auto-pairing.
* @param {Object[]} options.quotesPrefixes - An array of objects containing information about quotes prefixes.
* @param {RegExp} options.quotesPrefixes[].quotes - The regular expression used to determine which quote type the prefix applies to.
* @param {RegExp} options.quotesPrefixes[].condition - The regular expression used to determine if the character on the left of the quote is suitable.
*/
var CstyleBehaviour = function(options) {
this.add("braces", "insertion", function(state, action, editor, session, text) {
var cursor = editor.getCursorPosition();
Expand Down Expand Up @@ -223,7 +231,7 @@ var CstyleBehaviour = function(options) {
this.add("string_dquotes", "insertion", function(state, action, editor, session, text) {
var quotes = session.$mode.$quotes || defaultQuotes;
if (text.length == 1 && quotes[text]) {
if (this.lineCommentStart && this.lineCommentStart.indexOf(text) != -1)
if (this.lineCommentStart && this.lineCommentStart.indexOf(text) != -1)
return;
initContext(editor);
var quote = text;
Expand All @@ -236,16 +244,16 @@ var CstyleBehaviour = function(options) {
var line = session.doc.getLine(cursor.row);
var leftChar = line.substring(cursor.column-1, cursor.column);
var rightChar = line.substring(cursor.column, cursor.column + 1);

var token = session.getTokenAt(cursor.row, cursor.column);
var rightToken = session.getTokenAt(cursor.row, cursor.column + 1);
// We're escaped.
if (leftChar == "\\" && token && /escape/.test(token.type))
return null;

var stringBefore = token && /string|escape/.test(token.type);
var stringAfter = !rightToken || /string|escape/.test(rightToken.type);

var pair;
if (rightChar == quote) {
pair = stringBefore !== stringAfter;
Expand All @@ -261,9 +269,17 @@ var CstyleBehaviour = function(options) {
var isWordBefore = wordRe.test(leftChar);
wordRe.lastIndex = 0;
var isWordAfter = wordRe.test(rightChar);
let hasStringPrefixes = (options && options.quotePrefixes &&
Array.isArray(options.quotePrefixes)) ? options.quotePrefixes.includes(leftChar) : null;

var hasStringPrefixes = false;
if (options && options.quotesPrefixes && Array.isArray(options.quotesPrefixes)) {
for (var prefix of options.quotesPrefixes) {
if (prefix.quotes && prefix.condition && prefix.quotes instanceof RegExp
&& prefix.quotes.test(quotes[text]) && prefix.condition instanceof RegExp
&& prefix.condition.test(leftChar)) {
hasStringPrefixes = true;
break;
}
}
}
if ((!hasStringPrefixes && isWordBefore) || isWordAfter)
return null; // before or after alphanumeric
if (rightChar && !/[\s;,.})\]\\]/.test(rightChar))
Expand Down Expand Up @@ -298,11 +314,11 @@ var CstyleBehaviour = function(options) {

};


CstyleBehaviour.isSaneInsertion = function(editor, session) {
var cursor = editor.getCursorPosition();
var iterator = new TokenIterator(session, cursor.row, cursor.column);

// Don't insert in the middle of a keyword/identifier/lexical
if (!this.$matchTokenType(iterator.getCurrentToken() || "text", SAFE_INSERT_IN_TOKENS)) {
if (/[)}\]]/.test(editor.session.getLine(cursor.row)[cursor.column]))
Expand All @@ -312,7 +328,7 @@ CstyleBehaviour.isSaneInsertion = function(editor, session) {
if (!this.$matchTokenType(iterator2.getCurrentToken() || "text", SAFE_INSERT_IN_TOKENS))
return false;
}

// Only insert in front of whitespace/comments
iterator.stepForward();
return iterator.getCurrentTokenRow() !== cursor.row ||
Expand Down
11 changes: 9 additions & 2 deletions src/mode/javascript.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,16 @@ var CStyleFoldMode = require("./folding/cstyle").FoldMode;

var Mode = function() {
this.HighlightRules = JavaScriptHighlightRules;

this.$outdent = new MatchingBraceOutdent();
this.$behaviour = new CstyleBehaviour();
this.$behaviour = new CstyleBehaviour({
quotesPrefixes: [
{
quotes: new RegExp("`"),
condition: new RegExp("\\w")
}
]
});
this.foldingRules = new CStyleFoldMode();
};
oop.inherits(Mode, TextMode);
Expand Down
21 changes: 14 additions & 7 deletions src/mode/python.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,14 @@ var Range = require("../range").Range;
var Mode = function() {
this.HighlightRules = PythonHighlightRules;
this.foldingRules = new PythonFoldMode("\\:");
this.$behaviour = new CstyleBehaviour({quotePrefixes: ["f", "F", "u", "U", "r", "R"]});
this.$behaviour = new CstyleBehaviour({
quotesPrefixes: [
{
quotes: new RegExp("['\"]"),
condition: new RegExp("[ruf]", "i")
}
]
});
};
oop.inherits(Mode, TextMode);

Expand Down Expand Up @@ -45,31 +52,31 @@ oop.inherits(Mode, TextMode);
"break": 1,
"continue": 1
};

this.checkOutdent = function(state, line, input) {
if (input !== "\r\n" && input !== "\r" && input !== "\n")
return false;

var tokens = this.getTokenizer().getLineTokens(line.trim(), state).tokens;

if (!tokens)
return false;

// ignore trailing comments
do {
var last = tokens.pop();
} while (last && (last.type == "comment" || (last.type == "text" && last.value.match(/^\s+$/))));

if (!last)
return false;

return (last.type == "keyword" && outdents[last.value]);
};

this.autoOutdent = function(state, doc, row) {
// outdenting in python is slightly different because it always applies
// to the next line and only of a new line is inserted

row += 1;
var indent = this.$getIndent(doc.getLine(row));
var tab = doc.getTabString();
Expand Down

0 comments on commit b331e0d

Please sign in to comment.