Repository: st3v3nmw/obsidian-spaced-repetition (ts)

Issue:
[Bug]: Detecting inside codeblocks since last update
### Describe the bug

After the last update the both the inline and multi line flashcard indicators are being detected. 
Using default or custom indicators doesn't seem to matter.

Is this intended behavior?

If so I'll just switch the indicators to something else.

### To Reproduce

1. Create a code block using triple back ticks
2. Use inline or multiline indicator

### Expected behavior

The indicators not being detected inside codeblocks

### Screenshots

_No response_

### OS of your device

Win10

### Obsidian version

1.6.7

### Plugin version

1.12.6

### Installer version

1.6.5

### Additional context

_No response_

### Config file

_No response_
[Bug]: plugin does not detect tags/cards after recent update
### Describe the bug

plugin does not detect tags/cards after recent update

### To Reproduce

1. Updated plugin
2. No cards can be displayed

### Expected behavior

cards displayed normally

### Screenshots

_No response_

### OS of your device

Windows 11 & iOS

### Obsidian version

1.7.1

### Plugin version

1.12.6

### Installer version

1.4.13

### Additional context

_No response_

### Config file

_No response_

Pull request description:
fix: parsing code blocks & custom separators
This PR:

1. Renames _close_ in the grammar to _cloze_
2. Adds test cases for parsing {{curly}} clozes
3. Fixes #1072
    - Ignores 'cards' in ```block`` & `inline` codeblocks
    - Adds test cases for both
4. Fixes #1077 
    - When all the cloze options aren't being used, `cloze_rules` in `parser.ts` becomes empty which leads to the empty grammar rule `cloze_text` which prevents the parser from being generated. This change puts a [tombstone](https://en.wikipedia.org/wiki/Tombstone_(data_store)) `\0` instead to address this.
    - Adds test cases for this

Patch:
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index fe48d75..43cbfd7 100644
--- a/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
@@ -60,7 +60,7 @@ representative at an online or offline event.
 
 Instances of abusive, harassing, or otherwise unacceptable behavior may be
 reported to the community leaders responsible for enforcement at
-st3v3n.mw@gmail.com.
+mail@stephenmwangi.com.
 All complaints will be reviewed and investigated promptly and fairly.
 
 All community leaders are obligated to respect the privacy and security of the
diff --git a/src/algorithms/base/srs-algorithm.ts b/src/algorithms/base/srs-algorithm.ts
index 8054d5d..4e375ae 100644
--- a/src/algorithms/base/srs-algorithm.ts
+++ b/src/algorithms/base/srs-algorithm.ts
@@ -5,7 +5,7 @@ export class SrsAlgorithm {
 
     public static getInstance(): ISrsAlgorithm {
         if (!SrsAlgorithm.instance) {
-            throw Error("there is no SrsAlgorithm instance.");
+            throw new Error("there is no SrsAlgorithm instance.");
         }
         return SrsAlgorithm.instance;
     }
diff --git a/src/constants.ts b/src/constants.ts
index 76f4436..136d379 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -3,7 +3,6 @@
 export const SCHEDULING_INFO_REGEX =
     /^---\r?\n((?:.*\r?\n)*)sr-due: (.+)\r?\nsr-interval: (\d+)\r?\nsr-ease: (\d+)\r?\n((?:.*\r?\n)?)---/;
 export const YAML_FRONT_MATTER_REGEX = /^---\r?\n((?:.*\r?\n)*?)---/;
-export const NON_LETTER_SYMBOLS_REGEX = /[!-/:-@[-`{-~}\s]/g;
 
 export const MULTI_SCHEDULING_EXTRACTOR = /!([\d-]+),(\d+),(\d+)/gm;
 export const LEGACY_SCHEDULING_EXTRACTOR = /<!--SR:([\d-]+),(\d+),(\d+)-->/gm;
diff --git a/src/data-store-algorithm/data-store-algorithm.ts b/src/data-store-algorithm/data-store-algorithm.ts
index 091129b..339c747 100644
--- a/src/data-store-algorithm/data-store-algorithm.ts
+++ b/src/data-store-algorithm/data-store-algorithm.ts
@@ -5,7 +5,7 @@ export class DataStoreAlgorithm {
 
     public static getInstance(): IDataStoreAlgorithm {
         if (!DataStoreAlgorithm.instance) {
-            throw Error("there is no DataStoreAlgorithm instance.");
+            throw new Error("there is no DataStoreAlgorithm instance.");
         }
         return DataStoreAlgorithm.instance;
     }
diff --git a/src/data-stores/base/data-store.ts b/src/data-stores/base/data-store.ts
index ef9750f..9fb5c7c 100644
--- a/src/data-stores/base/data-store.ts
+++ b/src/data-stores/base/data-store.ts
@@ -17,7 +17,7 @@ export class DataStore {
 
     public static getInstance(): IDataStore {
         if (!DataStore.instance) {
-            throw Error("there is no DataStore instance.");
+            throw new Error("there is no DataStore instance.");
         }
         return DataStore.instance;
     }
diff --git a/src/parser.ts b/src/parser.ts
index fe56bc2..5423ab0 100644
--- a/src/parser.ts
+++ b/src/parser.ts
@@ -33,6 +33,20 @@ function areParserOptionsEqual(options1: ParserOptions, options2: ParserOptions)
 export function generateParser(options: ParserOptions): Parser {
     let grammar: string | null = null;
 
+    // Debug the grammar before generating the parser `generate(grammar)` from the grammar.
+    if (debugParser) {
+        if (grammar === null) {
+            grammar = generateGrammar(options);
+        }
+        console.log(
+            "The parsers grammar is provided below. You can test it with https://peggyjs.org/online.html.",
+        );
+        console.log({
+            info: "Copy the grammar by right-clicking on the property grammar and copying it as a string. Then, paste it in https://peggyjs.org/online.html.",
+            grammar: grammar,
+        });
+    }
+
     // If the parser did not already exist or if the parser options changed since last the last
     // parser was generated, we generate a new parser. Otherwise, we skip the block to save
     // some execution time.
@@ -53,56 +67,111 @@ export function generateParser(options: ParserOptions): Parser {
         }
     } else {
         if (debugParser) {
-            console.log("Parser already existed. No need to generate a new parser.");
-        }
-    }
-
-    if (debugParser) {
-        if (grammar === null) {
-            grammar = generateGrammar(options);
+            console.log("Parser already exists. No need to generate a new parser.");
         }
-        console.log(
-            "The parsers grammar is provided below. You can test it with https://peggyjs.org/online.html.",
-        );
-        console.log({
-            info: "Copy the grammar by right-clicking on the property grammar and copying it as a string. Then, paste it in https://peggyjs.org/online.html.",
-            grammar: grammar,
-        });
     }
 
     return parser;
 }
 
 function generateGrammar(options: ParserOptions): string {
-    const close_rules_list: string[] = [];
+    // Contains the grammar for cloze cards
+    let clozes_grammar = "";
 
-    if (options.convertHighlightsToClozes) close_rules_list.push("close_equal");
-    if (options.convertBoldTextToClozes) close_rules_list.push("close_star");
-    if (options.convertCurlyBracketsToClozes) close_rules_list.push("close_bracket");
+    // An array contianing the types of cards enabled by the user
+    const card_rules_list: string[] = ["html_comment", "tilde_code", "backprime_code"];
 
-    const close_rules = close_rules_list.join(" / ");
+    // Include reversed inline flashcards rule only if the user provided a non-empty marker for reversed inline flashcards
+    if (options.singleLineCardSeparator.trim() !== "") card_rules_list.push("inline_rev_card");
 
-    return `{
-// The fallback case is important if we want to test the rules with https://peggyjs.org/online.html
-const CardTypeFallBack = {
-  SingleLineBasic: 0,
-  SingleLineReversed: 1,
-  MultiLineBasic: 2,
-  MultiLineReversed: 3,
-  Cloze: 4,
-};
-
-// The fallback case is important if we want to test the rules with https://peggyjs.org/online.html
-const createParsedQuestionInfoFallBack = (cardType, text, firstLineNum, lastLineNum) => {
-  return {cardType, text, firstLineNum, lastLineNum};
-};
-
-const CardType = options.CardType ? options.CardType : CardTypeFallBack;
-const createParsedQuestionInfo = options.createParsedQuestionInfo ? options.createParsedQuestionInfo : createParsedQuestionInfoFallBack;
-
-function filterBlocks(b) {
-  return b.filter( (d) => d !== null )
+    // Include inline flashcards rule only if the user provided a non-empty marker for inline flashcards
+    if (options.singleLineCardSeparator.trim() !== "") card_rules_list.push("inline_card");
+
+    // Include reversed multiline flashcards rule only if the user provided a non-empty marker for reversed multiline flashcards
+    if (options.multilineReversedCardSeparator.trim() !== "")
+        card_rules_list.push("multiline_rev_card");
+
+    // Include multiline flashcards rule only if the user provided a non-empty marker for multiline flashcards
+    if (options.multilineCardSeparator.trim() !== "") card_rules_list.push("multiline_card");
+
+    const cloze_rules_list: string[] = [];
+    if (options.convertHighlightsToClozes) cloze_rules_list.push("cloze_equal");
+    if (options.convertBoldTextToClozes) cloze_rules_list.push("cloze_star");
+    if (options.convertCurlyBracketsToClozes) cloze_rules_list.push("cloze_bracket");
+
+    // Include cloze cards only if the user enabled at least one type of cloze cards
+    if (cloze_rules_list.length > 0) {
+        card_rules_list.push("cloze_card");
+        const cloze_rules = cloze_rules_list.join(" / ");
+        clozes_grammar = `
+cloze_card
+= $(multiline_before_cloze? cloze_line (multiline_after_cloze)? (newline annotation)?) {
+  return createParsedQuestionInfo(CardType.Cloze,text().trimEnd(),location().start.line-1,location().end.line-1);
 }
+
+cloze_line
+= ((!cloze_text (inline_code / non_newline))* cloze_text) text_line_nonterminated?
+
+multiline_before_cloze
+= (!cloze_line nonempty_text_line)+
+
+multiline_after_cloze
+= e:(!(newline separator_line) text_line1)+
+
+cloze_text
+= ${cloze_rules}
+
+cloze_equal
+= cloze_mark_equal (!cloze_mark_equal non_newline)+  cloze_mark_equal
+
+cloze_mark_equal
+= "=="
+
+cloze_star
+= cloze_mark_star (!cloze_mark_star non_newline)+  cloze_mark_star
+
+cloze_mark_star
+= "**"
+
+cloze_bracket
+= cloze_mark_bracket_open (!cloze_mark_bracket_close non_newline)+  cloze_mark_bracket_close
+
+cloze_mark_bracket_open
+= "{{"
+
+cloze_mark_bracket_close
+= "}}"
+`;
+    }
+
+    // Important: we need to include `loose_line` rule to detect any other loose line.
+    // Otherwise, we get a syntax error because the parser is likely not able to reach the end
+    // of the file, as it may encounter loose lines, which it would not know how to handle.
+    card_rules_list.push("loose_line");
+
+    const card_rules = card_rules_list.join(" / ");
+
+    return `{
+    // The fallback case is important if we want to test the rules with https://peggyjs.org/online.html
+    const CardTypeFallBack = {
+      SingleLineBasic: 0,
+      SingleLineReversed: 1,
+      MultiLineBasic: 2,
+      MultiLineReversed: 3,
+      Cloze: 4,
+    };
+
+    // The fallback case is important if we want to test the rules with https://peggyjs.org/online.html
+    const createParsedQuestionInfoFallBack = (cardType, text, firstLineNum, lastLineNum) => {
+      return {cardType, text, firstLineNum, lastLineNum};
+    };
+
+    const CardType = options.CardType ? options.CardType : CardTypeFallBack;
+    const createParsedQuestionInfo = options.createParsedQuestionInfo ? options.createParsedQuestionInfo : createParsedQuestionInfoFallBack;
+
+    function filterBlocks(b) {
+      return b.filter( (d) => d !== null )
+    }
 }
 
 main
@@ -111,7 +180,7 @@ main
 /* The input text to the parser contains arbitrary text, not just card definitions.
 Hence we fallback to matching on loose_line. The result from loose_line is filtered out by filterBlocks() */
 block
-= html_comment / tilde_code / backprime_code / inline_rev_card / inline_card / multiline_rev_card / multiline_card / close_card / loose_line
+= ${card_rules}
 
 html_comment
 = $("<!--" (!"-->" (html_comment / .))* "-->" newline?) {
@@ -139,7 +208,7 @@ inline_rev_card
 inline_rev
 = left:(!inline_rev_mark (inline_code / non_newline))+ inline_rev_mark right:text_till_newline (newline annotation)? {
     return createParsedQuestionInfo(CardType.SingleLineReversed,text(),location().start.line-1,location().end.line-1);
-  }
+}
 
 multiline_card
 = c:multiline separator_line {
@@ -196,43 +265,7 @@ multiline_rev_before
 multiline_rev_after
 = $(!separator_line text_line)+
 
-close_card
-= $(multiline_before_close? close_line (multiline_after_close)? (newline annotation)?) {
-  return createParsedQuestionInfo(CardType.Cloze,text().trimEnd(),location().start.line-1,location().end.line-1);
-}
-
-close_line
-= ((!close_text non_newline)* close_text) text_line_nonterminated?
-
-multiline_before_close
-= (!close_line nonempty_text_line)+
-
-multiline_after_close
-= e:(!(newline separator_line) text_line1)+
-
-close_text
-= ${close_rules}
-
-close_equal
-= close_mark_equal (!close_mark_equal non_newline)+  close_mark_equal
-
-close_mark_equal
-= "=="
-
-close_star
-= close_mark_star (!close_mark_star non_newline)+  close_mark_star
-
-close_mark_star
-= "**"
-
-close_bracket
-= close_mark_bracket_open (!close_mark_bracket_close non_newline)+  close_mark_bracket_close
-
-close_mark_bracket_open
-= "{{"
-
-close_mark_bracket_close
-= "}}"
+${clozes_grammar}
 
 inline_mark
 = "${options.singleLineCardSeparator}"
@@ -343,7 +376,7 @@ export function parseEx(text: string, options: ParserOptions): ParsedQuestionInf
 
     let cards: ParsedQuestionInfo[] = [];
     try {
-        if (!options) throw Error("No parser options provided.");
+        if (!options) throw new Error("No parser options provided.");
 
         const parser: Parser = generateParser(options);
 
@@ -370,8 +403,7 @@ export function parseEx(text: string, options: ParserOptions): ParsedQuestionInf
     }
 
     if (debugParser) {
-        console.log("Parsed cards:");
-        console.log(cards);
+        console.log("Parsed cards:\n", cards);
     }
 
     return cards;


Test patch:
diff --git a/tests/unit/parser.test.ts b/tests/unit/parser.test.ts
index d830318..21c5960 100644
--- a/tests/unit/parser.test.ts
+++ b/tests/unit/parser.test.ts
@@ -15,12 +15,11 @@ const parserOptions: ParserOptions = {
 
 /**
  * This function is a small wrapper around parseEx used for testing only.
+ *  It generates a parser each time, overwriting the default one.
  * Created when the actual parser changed from returning [CardType, string, number, number] to ParsedQuestionInfo.
  * It's purpose is to minimise changes to all the test cases here during the parser()->parserEx() change.
  */
 function parse(text: string, options: ParserOptions): [CardType, string, number, number][] {
-    // for testing purposes, generate parser each time, overwriting the default one
-
     const list: ParsedQuestionInfo[] = parseEx(text, options);
     const result: [CardType, string, number, number][] = [];
     for (const item of list) {
@@ -30,6 +29,7 @@ function parse(text: string, options: ParserOptions): [CardType, string, number,
 }
 
 test("Test parsing of single line basic cards", () => {
+    // standard symbols
     expect(parse("Question::Answer", parserOptions)).toEqual([
         [CardType.SingleLineBasic, "Question::Answer", 0, 0],
     ]);
@@ -49,9 +49,50 @@ test("Test parsing of single line basic cards", () => {
     expect(parse("#flashcards/science Question ::Answer", parserOptions)).toEqual([
         [CardType.SingleLineBasic, "#flashcards/science Question ::Answer", 0, 0],
     ]);
+
+    // custom symbols
+    expect(
+        parse("Question&&Answer", {
+            singleLineCardSeparator: "&&",
+            singleLineReversedCardSeparator: ":::",
+            multilineCardSeparator: "?",
+            multilineReversedCardSeparator: "??",
+            multilineCardEndMarker: "---",
+            convertHighlightsToClozes: false,
+            convertBoldTextToClozes: false,
+            convertCurlyBracketsToClozes: false,
+        }),
+    ).toEqual([[CardType.SingleLineBasic, "Question&&Answer", 0, 0]]);
+    expect(
+        parse("Question=Answer", {
+            singleLineCardSeparator: "=",
+            singleLineReversedCardSeparator: ":::",
+            multilineCardSeparator: "?",
+            multilineReversedCardSeparator: "??",
+            multilineCardEndMarker: "---",
+            convertHighlightsToClozes: false,
+            convertBoldTextToClozes: false,
+            convertCurlyBracketsToClozes: false,
+        }),
+    ).toEqual([[CardType.SingleLineBasic, "Question=Answer", 0, 0]]);
+
+    // empty string or whitespace character provided
+    expect(
+        parse("Question::Answer", {
+            singleLineCardSeparator: "",
+            singleLineReversedCardSeparator: ":::",
+            multilineCardSeparator: "?",
+            multilineReversedCardSeparator: "??",
+            multilineCardEndMarker: "---",
+            convertHighlightsToClozes: false,
+            convertBoldTextToClozes: false,
+            convertCurlyBracketsToClozes: false,
+        }),
+    ).toEqual([]);
 });
 
 test("Test parsing of single line reversed cards", () => {
+    // standard symbols
     expect(parse("Question:::Answer", parserOptions)).toEqual([
         [CardType.SingleLineReversed, "Question:::Answer", 0, 0],
     ]);
@@ -62,9 +103,38 @@ test("Test parsing of single line reversed cards", () => {
         [CardType.SingleLineReversed, "Q1:::A1", 2, 2],
         [CardType.SingleLineReversed, "Q2::: A2", 3, 3],
     ]);
+
+    // custom symbols
+    expect(
+        parse("Question&&&Answer", {
+            singleLineCardSeparator: "::",
+            singleLineReversedCardSeparator: "&&&",
+            multilineCardSeparator: "?",
+            multilineReversedCardSeparator: "??",
+            multilineCardEndMarker: "---",
+            convertHighlightsToClozes: false,
+            convertBoldTextToClozes: false,
+            convertCurlyBracketsToClozes: false,
+        }),
+    ).toEqual([[CardType.SingleLineReversed, "Question&&&Answer", 0, 0]]);
+
+    // empty string or whitespace character provided
+    expect(
+        parse("Question:::Answer", {
+            singleLineCardSeparator: ">",
+            singleLineReversedCardSeparator: "  ",
+            multilineCardSeparator: "?",
+            multilineReversedCardSeparator: "??",
+            multilineCardEndMarker: "---",
+            convertHighlightsToClozes: false,
+            convertBoldTextToClozes: false,
+            convertCurlyBracketsToClozes: false,
+        }),
+    ).toEqual([]);
 });
 
 test("Test parsing of multi line basic cards", () => {
+    // standard symbols
     expect(parse("Question\n?\nAnswer", parserOptions)).toEqual([
         [CardType.MultiLineBasic, "Question\n?\nAnswer", 0, 2],
     ]);
@@ -84,12 +154,7 @@ test("Test parsing of multi line basic cards", () => {
         [CardType.MultiLineBasic, "Question\n?\nAnswer line 1\nAnswer line 2", 0, 3],
     ]);
     expect(parse("#Title\n\nLine0\nQ1\n?\nA1\nAnswerExtra\n\nQ2\n?\nA2", parserOptions)).toEqual([
-        [
-            CardType.MultiLineBasic,
-            "Line0\nQ1\n?\nA1\nAnswerExtra",
-            /* Line0 */ 2,
-            /* AnswerExtra */ 6,
-        ],
+        [CardType.MultiLineBasic, "Line0\nQ1\n?\nA1\nAnswerExtra", 2, 6],
         [CardType.MultiLineBasic, "Q2\n?\nA2", 8, 10],
     ]);
     expect(parse("#flashcards/tag-on-previous-line\nQuestion\n?\nAnswer", parserOptions)).toEqual([
@@ -148,9 +213,38 @@ test("Test parsing of multi line basic cards", () => {
             10,
         ],
     ]);
+
+    // custom symbols
+    expect(
+        parse("Question\n@@\nAnswer\n\nsfdg", {
+            singleLineCardSeparator: "::",
+            singleLineReversedCardSeparator: ":::",
+            multilineCardSeparator: "@@",
+            multilineReversedCardSeparator: "??",
+            multilineCardEndMarker: "",
+            convertHighlightsToClozes: false,
+            convertBoldTextToClozes: false,
+            convertCurlyBracketsToClozes: false,
+        }),
+    ).toEqual([[CardType.MultiLineBasic, "Question\n@@\nAnswer", 0, 2]]);
+
+    // empty string or whitespace character provided
+    expect(
+        parse("Question\n?\nAnswer", {
+            singleLineCardSeparator: "::",
+            singleLineReversedCardSeparator: ":::",
+            multilineCardSeparator: "\n",
+            multilineReversedCardSeparator: "??",
+            multilineCardEndMarker: "---",
+            convertHighlightsToClozes: false,
+            convertBoldTextToClozes: false,
+            convertCurlyBracketsToClozes: false,
+        }),
+    ).toEqual([]);
 });
 
 test("Test parsing of multi line reversed cards", () => {
+    // standard symbols
     expect(parse("Question\n??\nAnswer", parserOptions)).toEqual([
         [CardType.MultiLineReversed, "Question\n??\nAnswer", 0, 2],
     ]);
@@ -161,12 +255,7 @@ test("Test parsing of multi line reversed cards", () => {
         [CardType.MultiLineReversed, "Question\n??\nAnswer line 1\nAnswer line 2", 0, 3],
     ]);
     expect(parse("#Title\n\nLine0\nQ1\n??\nA1\nAnswerExtra\n\nQ2\n??\nA2", parserOptions)).toEqual([
-        [
-            CardType.MultiLineReversed,
-            "Line0\nQ1\n??\nA1\nAnswerExtra",
-            /* Line0 */ 2,
-            /* AnswerExtra */ 6,
-        ],
+        [CardType.MultiLineReversed, "Line0\nQ1\n??\nA1\nAnswerExtra", 2, 6],
         [CardType.MultiLineReversed, "Q2\n??\nA2", 8, 10],
     ]);
     expect(
@@ -199,6 +288,34 @@ test("Test parsing of multi line reversed cards", () => {
         [CardType.MultiLineBasic, "Question 1\n?\nAnswer line 1\nAnswer line 2", 0, 4],
         [CardType.MultiLineReversed, "Question 2\n??\nAnswer line 1\nAnswer line 2", 6, 9],
     ]);
+
+    // custom symbols
+    expect(
+        parse("Question\n@@@\nAnswer\n---", {
+            singleLineCardSeparator: "::",
+            singleLineReversedCardSeparator: ":::",
+            multilineCardSeparator: "@@",
+            multilineReversedCardSeparator: "@@@",
+            multilineCardEndMarker: "---",
+            convertHighlightsToClozes: false,
+            convertBoldTextToClozes: false,
+            convertCurlyBracketsToClozes: false,
+        }),
+    ).toEqual([[CardType.MultiLineReversed, "Question\n@@@\nAnswer", 0, 2]]);
+
+    // empty string or whitespace character provided
+    expect(
+        parse("Question\n??\nAnswer", {
+            singleLineCardSeparator: "::",
+            singleLineReversedCardSeparator: ":::",
+            multilineCardSeparator: "?",
+            multilineReversedCardSeparator: "\t",
+            multilineCardEndMarker: "---",
+            convertHighlightsToClozes: false,
+            convertBoldTextToClozes: false,
+            convertCurlyBracketsToClozes: false,
+        }),
+    ).toEqual([]);
 });
 
 test("Test parsing of cloze cards", () => {
@@ -228,6 +345,7 @@ test("Test parsing of cloze cards", () => {
     expect(parse("srdf ==", parserOptions)).toEqual([]);
     expect(parse("lorem ipsum ==p\ndolor won==", parserOptions)).toEqual([]);
     expect(parse("lorem ipsum ==dolor won=", parserOptions)).toEqual([]);
+
     // ==highlights== turned off
     expect(
         parse("cloze ==deletion== test", {
@@ -238,7 +356,7 @@ test("Test parsing of cloze cards", () => {
             multilineCardEndMarker: "",
             convertHighlightsToClozes: false,
             convertBoldTextToClozes: true,
-            convertCurlyBracketsToClozes: false,
+            convertCurlyBracketsToClozes: true,
         }),
     ).toEqual([]);
 
@@ -268,6 +386,7 @@ test("Test parsing of cloze cards", () => {
     expect(parse("srdf **", parserOptions)).toEqual([]);
     expect(parse("lorem ipsum **p\ndolor won**", parserOptions)).toEqual([]);
     expect(parse("lorem ipsum **dolor won*", parserOptions)).toEqual([]);
+
     // **bolded** turned off
     expect(
         parse("cloze **deletion** test", {
@@ -278,15 +397,56 @@ test("Test parsing of cloze cards", () => {
             multilineCardEndMarker: "",
             convertHighlightsToClozes: true,
             convertBoldTextToClozes: false,
+            convertCurlyBracketsToClozes: true,
+        }),
+    ).toEqual([]);
+
+    // {{curly}}
+    expect(parse("cloze {{deletion}} test", parserOptions)).toEqual([
+        [CardType.Cloze, "cloze {{deletion}} test", 0, 0],
+    ]);
+    expect(parse("cloze {{deletion}} test\n<!--SR:2021-08-11,4,270-->", parserOptions)).toEqual([
+        [CardType.Cloze, "cloze {{deletion}} test\n<!--SR:2021-08-11,4,270-->", 0, 1],
+    ]);
+    expect(parse("cloze {{deletion}} test <!--SR:2021-08-11,4,270-->", parserOptions)).toEqual([
+        [CardType.Cloze, "cloze {{deletion}} test <!--SR:2021-08-11,4,270-->", 0, 0],
+    ]);
+    expect(parse("{{this}} is a {{deletion}}\n", parserOptions)).toEqual([
+        [CardType.Cloze, "{{this}} is a {{deletion}}", 0, 0],
+    ]);
+    expect(
+        parse(
+            "some text before\n\na deletion on\nsuch {{wow}}\n\n" +
+                "many text\nsuch surprise {{wow}} more {{text}}\nsome text after\n\nHmm",
+            parserOptions,
+        ),
+    ).toEqual([
+        [CardType.Cloze, "a deletion on\nsuch {{wow}}", 2, 3],
+        [CardType.Cloze, "many text\nsuch surprise {{wow}} more {{text}}\nsome text after", 5, 7],
+    ]);
+    expect(parse("srdf {{", parserOptions)).toEqual([]);
+    expect(parse("srdf }}", parserOptions)).toEqual([]);
+    expect(parse("lorem ipsum {{p\ndolor won}}", parserOptions)).toEqual([]);
+    expect(parse("lorem ipsum {{dolor won}", parserOptions)).toEqual([]);
+
+    // {{curly}} turned off
+    expect(
+        parse("cloze {{deletion}} test", {
+            singleLineCardSeparator: "::",
+            singleLineReversedCardSeparator: ":::",
+            multilineCardSeparator: "?",
+            multilineReversedCardSeparator: "??",
+            multilineCardEndMarker: "",
+            convertHighlightsToClozes: true,
+            convertBoldTextToClozes: true,
             convertCurlyBracketsToClozes: false,
         }),
     ).toEqual([]);
 
-    // both
+    // combo
     expect(parse("cloze **deletion** test ==another deletion==!", parserOptions)).toEqual([
         [CardType.Cloze, "cloze **deletion** test ==another deletion==!", 0, 0],
     ]);
-
     expect(
         parse(
             "Test 1\nTest 2\nThis is a close with ===secret=== text.\nWith this extra lines\n\nAnd more here.\nAnd even more.\n\n---\n\nTest 3\nTest 4\nThis is a close with ===super secret=== text.\nWith this extra lines\n\nAnd more here.\nAnd even more.\n\n---\n\nHere is some more text.",
@@ -315,6 +475,20 @@ test("Test parsing of cloze cards", () => {
             17,
         ],
     ]);
+
+    // all disabled
+    expect(
+        parse("cloze {{deletion}} test and **deletion** ==another deletion==!", {
+            singleLineCardSeparator: "::",
+            singleLineReversedCardSeparator: ":::",
+            multilineCardSeparator: "?",
+            multilineReversedCardSeparator: "??",
+            multilineCardEndMarker: "",
+            convertHighlightsToClozes: false,
+            convertBoldTextToClozes: false,
+            convertCurlyBracketsToClozes: false,
+        }),
+    ).toEqual([]);
 });
 
 test("Test parsing of a mix of card types", () => {
@@ -340,13 +514,28 @@ test("Test parsing of a mix of card types", () => {
             CardType.MultiLineReversed,
             "Donec dapibus ullamcorper aliquam.\n??\nDonec dapibus ullamcorper aliquam.\n<!--SR:2021-08-11,4,270-->",
             8,
-            11 /* <!--SR:2021-08-11,4,270--> */,
+            11,
         ],
     ]);
 });
 
-test("Test codeblocks", () => {
-    // no blank lines
+test("Test parsing cards with codeblocks", () => {
+    // `inline`
+    expect(
+        parse(
+            "my inline question containing `some inline code` in it::and this is answer possibly containing `inline` code.",
+            parserOptions,
+        ),
+    ).toEqual([
+        [
+            CardType.SingleLineBasic,
+            "my inline question containing `some inline code` in it::and this is answer possibly containing `inline` code.",
+            0,
+            0,
+        ],
+    ]);
+
+    // ```block```, no blank lines
     expect(
         parse(
             "How do you ... Python?\n?\n" +
@@ -359,11 +548,11 @@ test("Test codeblocks", () => {
             "How do you ... Python?\n?\n" +
                 "```\nprint('Hello World!')\nprint('Howdy?')\nlambda x: x[0]\n```",
             0,
-            6 /* ``` */,
+            6,
         ],
     ]);
 
-    // with blank lines
+    // ```block```, with blank lines
     expect(
         parse(
             "How do you ... Python?\n?\n" +
@@ -376,11 +565,11 @@ test("Test codeblocks", () => {
             "How do you ... Python?\n?\n" +
                 "```\nprint('Hello World!')\n\n\nprint('Howdy?')\n\nlambda x: x[0]\n```",
             0,
-            9 /* ``` */,
+            9,
         ],
     ]);
 
-    // general Markdown syntax
+    // nested markdown
     expect(
         parse(
             "Nested Markdown?\n?\n" +
@@ -409,12 +598,14 @@ test("Test codeblocks", () => {
                 "~~~\n" +
                 "````",
             0,
-            12 /* ``` */,
+            12,
         ],
     ]);
 });
 
 test("Test not parsing cards in HTML comments", () => {
+    expect(parse("<!--question::answer test-->", parserOptions)).toEqual([]);
+    expect(parse("<!--question:::answer test-->", parserOptions)).toEqual([]);
     expect(
         parse("<!--\nQuestion\n?\nAnswer <!--SR:!2021-08-11,4,270-->\n-->", parserOptions),
     ).toEqual([]);
@@ -426,6 +617,46 @@ test("Test not parsing cards in HTML comments", () => {
     ).toEqual([]);
     expect(parse("<!--cloze ==deletion== test-->", parserOptions)).toEqual([]);
     expect(parse("<!--cloze **deletion** test-->", parserOptions)).toEqual([]);
+    expect(parse("<!--cloze {{curly}} test-->", parserOptions)).toEqual([]);
+});
+
+test("Test not parsing 'cards' in codeblocks", () => {
+    // block
+    expect(parse("```\nCodeblockq::CodeblockA\n```", parserOptions)).toEqual([]);
+    expect(parse("```\nCodeblockq:::CodeblockA\n```", parserOptions)).toEqual([]);
+    expect(
+        parse("# Title\n\n```markdown\nsome ==highlighted text==!\n```\n\nmore!", parserOptions),
+    ).toEqual([]);
+    expect(
+        parse("# Title\n```markdown\nsome **bolded text**!\n```\n\nmore!", parserOptions),
+    ).toEqual([]);
+    expect(parse("# Title\n\n```\nfoo = {{'a': 2}}\n```\n\nmore!", parserOptions)).toEqual([]);
+
+    // inline
+    expect(parse("`Inlineq::InlineA`", parserOptions)).toEqual([]);
+    expect(
+        parse("# Title\n`if (a & b) {}`\nmore!", {
+            singleLineCardSeparator: "&",
+            singleLineReversedCardSeparator: ":::",
+            multilineCardSeparator: "?",
+            multilineReversedCardSeparator: "??",
+            multilineCardEndMarker: "",
+            convertHighlightsToClozes: true,
+            convertBoldTextToClozes: true,
+            convertCurlyBracketsToClozes: true,
+        }),
+    ).toEqual([]);
+    expect(parse("# Title\n`if a == b && c == d {}`\nmore!", parserOptions)).toEqual([]);
+    expect(parse("# Title\n\n`z = a ** b + 5 ** 7`\n\nmore!", parserOptions)).toEqual([]);
+    expect(parse("# Title\n`foo = {{'a': 2}}`\n\nmore!", parserOptions)).toEqual([]);
+
+    // combo
+    expect(
+        parse(
+            "Question::Answer\n\n```\nCodeblockq::CodeblockA\n```\n\n`Inlineq::InlineA`\n",
+            parserOptions,
+        ),
+    ).toEqual([[CardType.SingleLineBasic, "Question::Answer", 0, 0]]);
 });
 
 test("Unexpected Error case", () => {