diff --git a/grammars/MagicPython.cson b/grammars/MagicPython.cson
index 6486dba4..0e32aa04 100644
--- a/grammars/MagicPython.cson
+++ b/grammars/MagicPython.cson
@@ -294,6 +294,23 @@ repository:
'''
}
+ {
+ name: "keyword.control.flow.python"
+ comment: '''
+ `match` and `case` are only soft keywords. Match if
+ followed by `:` or anything that allows for line continuation.
+
+ '''
+ match: '''
+ (?x)
+ (?:^\\s*)\\b(
+ match | case
+ )\\b(?:
+ (?=.*[:\\\\]) | (?=\\s*[\\(\\[\\{])
+ )
+
+ '''
+ }
{
name: "storage.modifier.declaration.python"
match: '''
diff --git a/grammars/MagicPython.tmLanguage b/grammars/MagicPython.tmLanguage
index 5a07f545..a8ddad8e 100644
--- a/grammars/MagicPython.tmLanguage
+++ b/grammars/MagicPython.tmLanguage
@@ -453,6 +453,22 @@ it's probably control flow like:
| from | elif | else | if | except | pass | raise
| return | try | while | with
)\b
+
+
+
+ name
+ keyword.control.flow.python
+ comment
+ `match` and `case` are only soft keywords. Match if
+followed by `:` or anything that allows for line continuation.
+
+ match
+ (?x)
+ (?:^\s*)\b(
+ match | case
+ )\b(?:
+ (?=.*[:\\]) | (?=\s*[\(\[\{])
+ )
diff --git a/grammars/src/MagicPython.syntax.yaml b/grammars/src/MagicPython.syntax.yaml
index 9c80c8b3..a8596b53 100644
--- a/grammars/src/MagicPython.syntax.yaml
+++ b/grammars/src/MagicPython.syntax.yaml
@@ -381,6 +381,17 @@ repository:
| from | elif | else | if | except | pass | raise
| return | try | while | with
)\b
+ - name: keyword.control.flow.python
+ comment: |
+ `match` and `case` are only soft keywords. Match if
+ followed by `:` or anything that allows for line continuation.
+ match: |
+ (?x)
+ (?:^\s*)\b(
+ match | case
+ )\b(?:
+ (?=.*[:\\]) | (?=\s*[\(\[\{])
+ )
- name: storage.modifier.declaration.python
match: |
(?x)
diff --git a/test/atom-spec/python-spec.js b/test/atom-spec/python-spec.js
index 032c3682..d268f730 100644
--- a/test/atom-spec/python-spec.js
+++ b/test/atom-spec/python-spec.js
@@ -13020,6 +13020,214 @@ describe("Grammar Tests", function() {
expect(tokens[8][11].scopes).toEqual(["source.python","constant.language.python"]);
});
+ it("test/statements/match1.py",
+ function() {
+ tokens = grammar.tokenizeLines("match status:\n case 100:\n pass\n case \\\n 200:\n pass\n case (\n 300\n ):\n pass\n case [\n 400\n ]:\n pass\n case {\n 500: \"\",\n }:\n pass\n case Point(x, y) as p:\n pass\n case 600 | 700:\n pass\n case _:")
+ expect(tokens[0][0].value).toBe("match");
+ expect(tokens[0][0].scopes).toEqual(["source.python","keyword.control.flow.python"]);
+ expect(tokens[0][1].value).toBe(" ");
+ expect(tokens[0][1].scopes).toEqual(["source.python"]);
+ expect(tokens[0][2].value).toBe("status");
+ expect(tokens[0][2].scopes).toEqual(["source.python"]);
+ expect(tokens[0][3].value).toBe(":");
+ expect(tokens[0][3].scopes).toEqual(["source.python","punctuation.separator.colon.python"]);
+ expect(tokens[1][0].value).toBe(" case");
+ expect(tokens[1][0].scopes).toEqual(["source.python","keyword.control.flow.python"]);
+ expect(tokens[1][1].value).toBe(" ");
+ expect(tokens[1][1].scopes).toEqual(["source.python"]);
+ expect(tokens[1][2].value).toBe("100");
+ expect(tokens[1][2].scopes).toEqual(["source.python","constant.numeric.dec.python"]);
+ expect(tokens[1][3].value).toBe(":");
+ expect(tokens[1][3].scopes).toEqual(["source.python","punctuation.separator.colon.python"]);
+ expect(tokens[2][0].value).toBe(" ");
+ expect(tokens[2][0].scopes).toEqual(["source.python"]);
+ expect(tokens[2][1].value).toBe("pass");
+ expect(tokens[2][1].scopes).toEqual(["source.python","keyword.control.flow.python"]);
+ expect(tokens[3][0].value).toBe(" case");
+ expect(tokens[3][0].scopes).toEqual(["source.python","keyword.control.flow.python"]);
+ expect(tokens[3][1].value).toBe(" ");
+ expect(tokens[3][1].scopes).toEqual(["source.python"]);
+ expect(tokens[3][2].value).toBe("\\");
+ expect(tokens[3][2].scopes).toEqual(["source.python","punctuation.separator.continuation.line.python"]);
+ expect(tokens[3][3].value).toBe("");
+ expect(tokens[3][3].scopes).toEqual(["source.python"]);
+ expect(tokens[4][0].value).toBe(" ");
+ expect(tokens[4][0].scopes).toEqual(["source.python"]);
+ expect(tokens[4][1].value).toBe("200");
+ expect(tokens[4][1].scopes).toEqual(["source.python","constant.numeric.dec.python"]);
+ expect(tokens[4][2].value).toBe(":");
+ expect(tokens[4][2].scopes).toEqual(["source.python","punctuation.separator.colon.python"]);
+ expect(tokens[5][0].value).toBe(" ");
+ expect(tokens[5][0].scopes).toEqual(["source.python"]);
+ expect(tokens[5][1].value).toBe("pass");
+ expect(tokens[5][1].scopes).toEqual(["source.python","keyword.control.flow.python"]);
+ expect(tokens[6][0].value).toBe(" case");
+ expect(tokens[6][0].scopes).toEqual(["source.python","keyword.control.flow.python"]);
+ expect(tokens[6][1].value).toBe(" ");
+ expect(tokens[6][1].scopes).toEqual(["source.python"]);
+ expect(tokens[6][2].value).toBe("(");
+ expect(tokens[6][2].scopes).toEqual(["source.python","punctuation.parenthesis.begin.python"]);
+ expect(tokens[7][0].value).toBe(" ");
+ expect(tokens[7][0].scopes).toEqual(["source.python"]);
+ expect(tokens[7][1].value).toBe("300");
+ expect(tokens[7][1].scopes).toEqual(["source.python","constant.numeric.dec.python"]);
+ expect(tokens[8][0].value).toBe(" ");
+ expect(tokens[8][0].scopes).toEqual(["source.python"]);
+ expect(tokens[8][1].value).toBe(")");
+ expect(tokens[8][1].scopes).toEqual(["source.python","punctuation.parenthesis.end.python"]);
+ expect(tokens[8][2].value).toBe(":");
+ expect(tokens[8][2].scopes).toEqual(["source.python","punctuation.separator.colon.python"]);
+ expect(tokens[9][0].value).toBe(" ");
+ expect(tokens[9][0].scopes).toEqual(["source.python"]);
+ expect(tokens[9][1].value).toBe("pass");
+ expect(tokens[9][1].scopes).toEqual(["source.python","keyword.control.flow.python"]);
+ expect(tokens[10][0].value).toBe(" case");
+ expect(tokens[10][0].scopes).toEqual(["source.python","keyword.control.flow.python"]);
+ expect(tokens[10][1].value).toBe(" ");
+ expect(tokens[10][1].scopes).toEqual(["source.python"]);
+ expect(tokens[10][2].value).toBe("[");
+ expect(tokens[10][2].scopes).toEqual(["source.python","punctuation.definition.list.begin.python"]);
+ expect(tokens[11][0].value).toBe(" ");
+ expect(tokens[11][0].scopes).toEqual(["source.python"]);
+ expect(tokens[11][1].value).toBe("400");
+ expect(tokens[11][1].scopes).toEqual(["source.python","constant.numeric.dec.python"]);
+ expect(tokens[12][0].value).toBe(" ");
+ expect(tokens[12][0].scopes).toEqual(["source.python"]);
+ expect(tokens[12][1].value).toBe("]");
+ expect(tokens[12][1].scopes).toEqual(["source.python","punctuation.definition.list.end.python"]);
+ expect(tokens[12][2].value).toBe(":");
+ expect(tokens[12][2].scopes).toEqual(["source.python","punctuation.separator.colon.python"]);
+ expect(tokens[13][0].value).toBe(" ");
+ expect(tokens[13][0].scopes).toEqual(["source.python"]);
+ expect(tokens[13][1].value).toBe("pass");
+ expect(tokens[13][1].scopes).toEqual(["source.python","keyword.control.flow.python"]);
+ expect(tokens[14][0].value).toBe(" case");
+ expect(tokens[14][0].scopes).toEqual(["source.python","keyword.control.flow.python"]);
+ expect(tokens[14][1].value).toBe(" ");
+ expect(tokens[14][1].scopes).toEqual(["source.python"]);
+ expect(tokens[14][2].value).toBe("{");
+ expect(tokens[14][2].scopes).toEqual(["source.python","punctuation.definition.dict.begin.python"]);
+ expect(tokens[15][0].value).toBe(" ");
+ expect(tokens[15][0].scopes).toEqual(["source.python"]);
+ expect(tokens[15][1].value).toBe("500");
+ expect(tokens[15][1].scopes).toEqual(["source.python","constant.numeric.dec.python"]);
+ expect(tokens[15][2].value).toBe(":");
+ expect(tokens[15][2].scopes).toEqual(["source.python","punctuation.separator.dict.python"]);
+ expect(tokens[15][3].value).toBe(" ");
+ expect(tokens[15][3].scopes).toEqual(["source.python"]);
+ expect(tokens[15][4].value).toBe("\"");
+ expect(tokens[15][4].scopes).toEqual(["source.python","string.quoted.single.python","punctuation.definition.string.begin.python"]);
+ expect(tokens[15][5].value).toBe("\"");
+ expect(tokens[15][5].scopes).toEqual(["source.python","string.quoted.single.python","punctuation.definition.string.end.python"]);
+ expect(tokens[15][6].value).toBe(",");
+ expect(tokens[15][6].scopes).toEqual(["source.python","punctuation.separator.element.python"]);
+ expect(tokens[16][0].value).toBe(" ");
+ expect(tokens[16][0].scopes).toEqual(["source.python"]);
+ expect(tokens[16][1].value).toBe("}");
+ expect(tokens[16][1].scopes).toEqual(["source.python","punctuation.definition.dict.end.python"]);
+ expect(tokens[16][2].value).toBe(":");
+ expect(tokens[16][2].scopes).toEqual(["source.python","punctuation.separator.colon.python"]);
+ expect(tokens[17][0].value).toBe(" ");
+ expect(tokens[17][0].scopes).toEqual(["source.python"]);
+ expect(tokens[17][1].value).toBe("pass");
+ expect(tokens[17][1].scopes).toEqual(["source.python","keyword.control.flow.python"]);
+ expect(tokens[18][0].value).toBe(" case");
+ expect(tokens[18][0].scopes).toEqual(["source.python","keyword.control.flow.python"]);
+ expect(tokens[18][1].value).toBe(" ");
+ expect(tokens[18][1].scopes).toEqual(["source.python"]);
+ expect(tokens[18][2].value).toBe("Point");
+ expect(tokens[18][2].scopes).toEqual(["source.python","meta.function-call.python","meta.function-call.generic.python"]);
+ expect(tokens[18][3].value).toBe("(");
+ expect(tokens[18][3].scopes).toEqual(["source.python","meta.function-call.python","punctuation.definition.arguments.begin.python"]);
+ expect(tokens[18][4].value).toBe("x");
+ expect(tokens[18][4].scopes).toEqual(["source.python","meta.function-call.python","meta.function-call.arguments.python"]);
+ expect(tokens[18][5].value).toBe(",");
+ expect(tokens[18][5].scopes).toEqual(["source.python","meta.function-call.python","meta.function-call.arguments.python","punctuation.separator.arguments.python"]);
+ expect(tokens[18][6].value).toBe(" ");
+ expect(tokens[18][6].scopes).toEqual(["source.python","meta.function-call.python","meta.function-call.arguments.python"]);
+ expect(tokens[18][7].value).toBe("y");
+ expect(tokens[18][7].scopes).toEqual(["source.python","meta.function-call.python","meta.function-call.arguments.python"]);
+ expect(tokens[18][8].value).toBe(")");
+ expect(tokens[18][8].scopes).toEqual(["source.python","meta.function-call.python","punctuation.definition.arguments.end.python"]);
+ expect(tokens[18][9].value).toBe(" ");
+ expect(tokens[18][9].scopes).toEqual(["source.python"]);
+ expect(tokens[18][10].value).toBe("as");
+ expect(tokens[18][10].scopes).toEqual(["source.python","keyword.control.flow.python"]);
+ expect(tokens[18][11].value).toBe(" ");
+ expect(tokens[18][11].scopes).toEqual(["source.python"]);
+ expect(tokens[18][12].value).toBe("p");
+ expect(tokens[18][12].scopes).toEqual(["source.python"]);
+ expect(tokens[18][13].value).toBe(":");
+ expect(tokens[18][13].scopes).toEqual(["source.python","punctuation.separator.colon.python"]);
+ expect(tokens[19][0].value).toBe(" ");
+ expect(tokens[19][0].scopes).toEqual(["source.python"]);
+ expect(tokens[19][1].value).toBe("pass");
+ expect(tokens[19][1].scopes).toEqual(["source.python","keyword.control.flow.python"]);
+ expect(tokens[20][0].value).toBe(" case");
+ expect(tokens[20][0].scopes).toEqual(["source.python","keyword.control.flow.python"]);
+ expect(tokens[20][1].value).toBe(" ");
+ expect(tokens[20][1].scopes).toEqual(["source.python"]);
+ expect(tokens[20][2].value).toBe("600");
+ expect(tokens[20][2].scopes).toEqual(["source.python","constant.numeric.dec.python"]);
+ expect(tokens[20][3].value).toBe(" ");
+ expect(tokens[20][3].scopes).toEqual(["source.python"]);
+ expect(tokens[20][4].value).toBe("|");
+ expect(tokens[20][4].scopes).toEqual(["source.python","keyword.operator.bitwise.python"]);
+ expect(tokens[20][5].value).toBe(" ");
+ expect(tokens[20][5].scopes).toEqual(["source.python"]);
+ expect(tokens[20][6].value).toBe("700");
+ expect(tokens[20][6].scopes).toEqual(["source.python","constant.numeric.dec.python"]);
+ expect(tokens[20][7].value).toBe(":");
+ expect(tokens[20][7].scopes).toEqual(["source.python","punctuation.separator.colon.python"]);
+ expect(tokens[21][0].value).toBe(" ");
+ expect(tokens[21][0].scopes).toEqual(["source.python"]);
+ expect(tokens[21][1].value).toBe("pass");
+ expect(tokens[21][1].scopes).toEqual(["source.python","keyword.control.flow.python"]);
+ expect(tokens[22][0].value).toBe(" case");
+ expect(tokens[22][0].scopes).toEqual(["source.python","keyword.control.flow.python"]);
+ expect(tokens[22][1].value).toBe(" ");
+ expect(tokens[22][1].scopes).toEqual(["source.python"]);
+ expect(tokens[22][2].value).toBe("_");
+ expect(tokens[22][2].scopes).toEqual(["source.python"]);
+ expect(tokens[22][3].value).toBe(":");
+ expect(tokens[22][3].scopes).toEqual(["source.python","punctuation.separator.colon.python"]);
+ });
+
+ it("test/statements/match2.py",
+ function() {
+ tokens = grammar.tokenizeLines("match = 1\nif match == 2:\n pass")
+ expect(tokens[0][0].value).toBe("match");
+ expect(tokens[0][0].scopes).toEqual(["source.python"]);
+ expect(tokens[0][1].value).toBe(" ");
+ expect(tokens[0][1].scopes).toEqual(["source.python"]);
+ expect(tokens[0][2].value).toBe("=");
+ expect(tokens[0][2].scopes).toEqual(["source.python","keyword.operator.assignment.python"]);
+ expect(tokens[0][3].value).toBe(" ");
+ expect(tokens[0][3].scopes).toEqual(["source.python"]);
+ expect(tokens[0][4].value).toBe("1");
+ expect(tokens[0][4].scopes).toEqual(["source.python","constant.numeric.dec.python"]);
+ expect(tokens[1][0].value).toBe("if");
+ expect(tokens[1][0].scopes).toEqual(["source.python","keyword.control.flow.python"]);
+ expect(tokens[1][1].value).toBe(" ");
+ expect(tokens[1][1].scopes).toEqual(["source.python"]);
+ expect(tokens[1][2].value).toBe("match");
+ expect(tokens[1][2].scopes).toEqual(["source.python"]);
+ expect(tokens[1][3].value).toBe(" ");
+ expect(tokens[1][3].scopes).toEqual(["source.python"]);
+ expect(tokens[1][4].value).toBe("==");
+ expect(tokens[1][4].scopes).toEqual(["source.python","keyword.operator.comparison.python"]);
+ expect(tokens[1][5].value).toBe(" ");
+ expect(tokens[1][5].scopes).toEqual(["source.python"]);
+ expect(tokens[1][6].value).toBe("2");
+ expect(tokens[1][6].scopes).toEqual(["source.python","constant.numeric.dec.python"]);
+ expect(tokens[1][7].value).toBe(":");
+ expect(tokens[1][7].scopes).toEqual(["source.python","punctuation.separator.colon.python"]);
+ expect(tokens[2][0].value).toBe(" ");
+ expect(tokens[2][0].scopes).toEqual(["source.python"]);
+ expect(tokens[2][1].value).toBe("pass");
+ expect(tokens[2][1].scopes).toEqual(["source.python","keyword.control.flow.python"]);
+ });
+
it("test/statements/nonlocal1.py",
function() {
tokens = grammar.tokenizeLines("nonlocal a, b, c")
diff --git a/test/statements/match1.py b/test/statements/match1.py
new file mode 100644
index 00000000..e0cebf7b
--- /dev/null
+++ b/test/statements/match1.py
@@ -0,0 +1,110 @@
+match status:
+ case 100:
+ pass
+ case \
+ 200:
+ pass
+ case (
+ 300
+ ):
+ pass
+ case [
+ 400
+ ]:
+ pass
+ case {
+ 500: "",
+ }:
+ pass
+ case Point(x, y) as p:
+ pass
+ case 600 | 700:
+ pass
+ case _:
+
+
+
+match : keyword.control.flow.python, source.python
+ : source.python
+status : source.python
+: : punctuation.separator.colon.python, source.python
+ case : keyword.control.flow.python, source.python
+ : source.python
+100 : constant.numeric.dec.python, source.python
+: : punctuation.separator.colon.python, source.python
+ : source.python
+pass : keyword.control.flow.python, source.python
+ case : keyword.control.flow.python, source.python
+ : source.python
+\ : punctuation.separator.continuation.line.python, source.python
+ : source.python
+ : source.python
+200 : constant.numeric.dec.python, source.python
+: : punctuation.separator.colon.python, source.python
+ : source.python
+pass : keyword.control.flow.python, source.python
+ case : keyword.control.flow.python, source.python
+ : source.python
+( : punctuation.parenthesis.begin.python, source.python
+ : source.python
+300 : constant.numeric.dec.python, source.python
+ : source.python
+) : punctuation.parenthesis.end.python, source.python
+: : punctuation.separator.colon.python, source.python
+ : source.python
+pass : keyword.control.flow.python, source.python
+ case : keyword.control.flow.python, source.python
+ : source.python
+[ : punctuation.definition.list.begin.python, source.python
+ : source.python
+400 : constant.numeric.dec.python, source.python
+ : source.python
+] : punctuation.definition.list.end.python, source.python
+: : punctuation.separator.colon.python, source.python
+ : source.python
+pass : keyword.control.flow.python, source.python
+ case : keyword.control.flow.python, source.python
+ : source.python
+{ : punctuation.definition.dict.begin.python, source.python
+ : source.python
+500 : constant.numeric.dec.python, source.python
+: : punctuation.separator.dict.python, source.python
+ : source.python
+" : punctuation.definition.string.begin.python, source.python, string.quoted.single.python
+" : punctuation.definition.string.end.python, source.python, string.quoted.single.python
+, : punctuation.separator.element.python, source.python
+ : source.python
+} : punctuation.definition.dict.end.python, source.python
+: : punctuation.separator.colon.python, source.python
+ : source.python
+pass : keyword.control.flow.python, source.python
+ case : keyword.control.flow.python, source.python
+ : source.python
+Point : meta.function-call.generic.python, meta.function-call.python, source.python
+( : meta.function-call.python, punctuation.definition.arguments.begin.python, source.python
+x : meta.function-call.arguments.python, meta.function-call.python, source.python
+, : meta.function-call.arguments.python, meta.function-call.python, punctuation.separator.arguments.python, source.python
+ : meta.function-call.arguments.python, meta.function-call.python, source.python
+y : meta.function-call.arguments.python, meta.function-call.python, source.python
+) : meta.function-call.python, punctuation.definition.arguments.end.python, source.python
+ : source.python
+as : keyword.control.flow.python, source.python
+ : source.python
+p : source.python
+: : punctuation.separator.colon.python, source.python
+ : source.python
+pass : keyword.control.flow.python, source.python
+ case : keyword.control.flow.python, source.python
+ : source.python
+600 : constant.numeric.dec.python, source.python
+ : source.python
+| : keyword.operator.bitwise.python, source.python
+ : source.python
+700 : constant.numeric.dec.python, source.python
+: : punctuation.separator.colon.python, source.python
+ : source.python
+pass : keyword.control.flow.python, source.python
+ case : keyword.control.flow.python, source.python
+ : source.python
+_ : source.python
+: : punctuation.separator.colon.python, source.python
diff --git a/test/statements/match2.py b/test/statements/match2.py
new file mode 100644
index 00000000..5f600910
--- /dev/null
+++ b/test/statements/match2.py
@@ -0,0 +1,21 @@
+match = 1
+if match == 2:
+ pass
+
+
+
+match : source.python
+ : source.python
+= : keyword.operator.assignment.python, source.python
+ : source.python
+1 : constant.numeric.dec.python, source.python
+if : keyword.control.flow.python, source.python
+ : source.python
+match : source.python
+ : source.python
+== : keyword.operator.comparison.python, source.python
+ : source.python
+2 : constant.numeric.dec.python, source.python
+: : punctuation.separator.colon.python, source.python
+ : source.python
+pass : keyword.control.flow.python, source.python