diff --git a/src/debugger/util.ts b/src/debugger/util.ts index 3a63915e3..b1363d5b0 100644 --- a/src/debugger/util.ts +++ b/src/debugger/util.ts @@ -1,3 +1,4 @@ +import { Token } from "../cursor-doc/clojure-lexer"; import { LispTokenCursor } from "../cursor-doc/token-cursor"; function moveCursorPastStringInList(tokenCursor: LispTokenCursor, s: string): void { @@ -18,6 +19,30 @@ function moveCursorPastStringInList(tokenCursor: LispTokenCursor, s: string): vo } } +function syntaxQuoteBegins(token: Token) { + // Check if we just entered a syntax quote, since we have to account for how syntax quoted forms are read + // `(. .) is read as (seq (concat (list .) (list .))). + if (/.*\`(\[|\{|\()$/.test(token.raw)) { + return true; + } + return false; +} + +function syntaxQuoteEnds(token: Token) { + // A syntax quote is ending - this happens if ~ or ~@ precedes a form + if (token.raw.match(/~@?/)) { + return true; + } + return false; +} + +function listBegins(token: Token) { + if(token.raw.endsWith('(')) { + return true; + } + return false; +} + function moveTokenCursorToBreakpoint(tokenCursor: LispTokenCursor, debugResponse: any): LispTokenCursor { const errorMessage = "Error finding position of breakpoint"; @@ -32,29 +57,22 @@ function moveTokenCursorToBreakpoint(tokenCursor: LispTokenCursor, debugResponse } const previousToken = tokenCursor.getPrevToken(); - // Check if we just entered a syntax quote, since we have to account for how syntax quoted forms are read - // `(. .) is read as (seq (concat (list .) (list .))). - if (/.*\`(\[|\{|\()$/.test(previousToken.raw)) { - inSyntaxQuote = true; - } - - if (inSyntaxQuote) { - i++; // Ignore this coor and move to the next + inSyntaxQuote = syntaxQuoteBegins(previousToken); - // A syntax quote is ending - this happens if ~ or ~@ precedes a form - if (previousToken.raw.match(/~@?/)) { - inSyntaxQuote = false; - } + if (syntaxQuoteEnds(previousToken)) { + inSyntaxQuote = false; } if (inSyntaxQuote) { - if (!previousToken.raw.endsWith('(')) { - // Non-list seqs like `[] and `{} are read with an extra (apply vector ...) or (apply hash-map ...) + // Ignore this coor and move to the next + i++; + // Now we're inside the `concat` form, but we need to ignore the actual `concat` symbol + i++; + // Non-list seqs like `[] and `{} are read with an extra (apply vector ...) or (apply hash-map ...) + if (!listBegins(previousToken)) { // Ignore this coor too i++; } - // Now we're inside the `concat` form, but we need to ignore the actual `concat` symbol - coor[i]--; } // #() expands to (fn* ([] ...)) and this is what coor is calculated with, so ignore this coor and move to the next diff --git a/src/extension-test/unit/debugger/test-files/syntax-quoted-list-1.clj b/src/extension-test/unit/debugger/test-files/syntax-quoted-list-1.clj new file mode 100644 index 000000000..b4b07ae5d --- /dev/null +++ b/src/extension-test/unit/debugger/test-files/syntax-quoted-list-1.clj @@ -0,0 +1,13 @@ +;; 3, 1, 1 +(defn fooz [x y] + `(#break :x| ~x :y ~y)) + +(comment + "Expanded form" + (defn fooz [x y] + (clojure.core/seq + (clojure.core/concat + (clojure.core/list :x) + (clojure.core/list x) + (clojure.core/list :y) + (clojure.core/list y))))) \ No newline at end of file diff --git a/src/extension-test/unit/debugger/test-files/syntax-quoted-map-1.clj b/src/extension-test/unit/debugger/test-files/syntax-quoted-map-1.clj new file mode 100644 index 000000000..e30828ee9 --- /dev/null +++ b/src/extension-test/unit/debugger/test-files/syntax-quoted-map-1.clj @@ -0,0 +1,19 @@ +;; 3, 2, 2, 1, 2, 1 +(defn foo [] + (let [a "foo" + b "bar"] + `{:a #break ~a| :b ~b})) + +(comment + "Expanded Form" + (defn foo [] + (let [a "foo" + b "bar"] + (clojure.core/apply + clojure.core/hash-map + (clojure.core/seq + (clojure.core/concat + (clojure.core/list :a) + (clojure.core/list a) + (clojure.core/list :b) + (clojure.core/list b))))))) \ No newline at end of file diff --git a/src/extension-test/unit/debugger/test-files/syntax-quoted-vector-1.clj b/src/extension-test/unit/debugger/test-files/syntax-quoted-vector-1.clj new file mode 100644 index 000000000..c2cd7079f --- /dev/null +++ b/src/extension-test/unit/debugger/test-files/syntax-quoted-vector-1.clj @@ -0,0 +1,15 @@ +;; 3, 1, 1 +(defn fooz [x y] + `[#break :x| ~x :y ~y]) + +(comment + "Expanded form" + (defn fooz [x y] + (clojure.core/apply + clojure.core/vector + (clojure.core/seq + (clojure.core/concat + (clojure.core/list :x) + (clojure.core/list x) + (clojure.core/list :y) + (clojure.core/list y)))))) \ No newline at end of file diff --git a/src/extension-test/unit/debugger/util-test.ts b/src/extension-test/unit/debugger/util-test.ts index ec6354d97..2140b57de 100644 --- a/src/extension-test/unit/debugger/util-test.ts +++ b/src/extension-test/unit/debugger/util-test.ts @@ -32,6 +32,7 @@ describe('Debugger Util', async () => { }; }); + // Cider's test cases: https://github.com/clojure-emacs/cider/blob/f1c2a797291fd3d2a44cb32372852950d5ecf8a2/test/cider-debug-tests.el#L82 describe('moveTokenCursorToBreakpoint', () => { function expectBreakpointToBeFound(fileName: string) { @@ -43,28 +44,48 @@ describe('Debugger Util', async () => { expect(tokenCursor.getPrevToken().raw.endsWith('|')).toBe(true); } - it('simple example', () => { + function expectBreakpointToBeFound2(code: string, coor: number[]) { + doc.insertString(code); + const tokenCursor = doc.getTokenCursor(0); + moveTokenCursorToBreakpoint(tokenCursor, { coor }); + expect(tokenCursor.getPrevToken().raw.endsWith('|')).toBe(true); + } + + it('navigates the clojure sexpresions guided by the given coordinates', () => { expectBreakpointToBeFound('simple.clj'); + expectBreakpointToBeFound2('(defn a [] (let [x 1] (inc x|)) {:a 1, :b 2})', [3, 2, 1]); }); - it('function shorthand', () => { + it('handles function shorthand', () => { expectBreakpointToBeFound('fn-shorthand.clj'); }); - it('map', () => { + it('handles map', () => { expectBreakpointToBeFound('map.clj'); }); - it('metadata symbol', () => { + it('handles metadata symbol', () => { expectBreakpointToBeFound('metadata-symbol.clj'); }); - it('ignored forms', () => { + it('handles ignored forms', () => { expectBreakpointToBeFound('ignored-forms.clj'); }); - it('syntax quote', () => { + it('handles syntax quote', () => { expectBreakpointToBeFound('syntax-quote.clj'); }); + + it('handles syntax quoted map 1', () => { + expectBreakpointToBeFound('syntax-quoted-map-1.clj'); + }); + + it('handles syntax quoted list 1', () => { + expectBreakpointToBeFound('syntax-quoted-list-1.clj'); + }); + + it('handles syntax quoted vector 1', () => { + expectBreakpointToBeFound('syntax-quoted-vector-1.clj'); + }); }); });