From 508b58abbfe5bc3bf3e3d87a1df2aa99fa9684b2 Mon Sep 17 00:00:00 2001 From: Peter Date: Wed, 18 Mar 2020 15:21:57 +0100 Subject: [PATCH 1/8] Close string with quote at the end Fixes: #587 --- CHANGELOG.md | 1 + src/cursor-doc/paredit.ts | 39 ++++++++++--------- .../unit/cursor-doc/paredit-test.ts | 23 +++++++++++ 3 files changed, 45 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e929d76f6..22adb26a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ Changes to Calva. ## [Unreleased] +- [Fix: REPL Window Paredit does not close strings properly](https://github.com/BetterThanTomorrow/calva/issues/587) ## [2.0.85] - 2020-03-15 - Fix: Make lein-shadow project type use lein injections diff --git a/src/cursor-doc/paredit.ts b/src/cursor-doc/paredit.ts index 33676a5bb..117b374e3 100644 --- a/src/cursor-doc/paredit.ts +++ b/src/cursor-doc/paredit.ts @@ -350,23 +350,14 @@ export function open(doc: EditableDocument, open: string, close: string, start: } export function close(doc: EditableDocument, close: string, start: number = doc.selectionRight) { - let cursor = doc.getTokenCursor(); + const cursor = doc.getTokenCursor(start); cursor.forwardWhitespace(false); if (cursor.getToken().raw == close) { + doc.selection = new ModelEditSelection(start + close.length); + } else { doc.model.edit([ - new ModelEdit('changeRange', [start, cursor.offsetStart, ""]) + new ModelEdit('changeRange', [start, start, close]) ], { selection: new ModelEditSelection(start + close.length) }); - } else { - // one of two things are possible: - if (cursor.forwardList()) { - // we are in a matched list, just jump to the end of it. - doc.selection = new ModelEditSelection(cursor.offsetEnd); - } else { - while (cursor.forwardSexp()) { } - doc.model.edit([ - new ModelEdit('changeRange', [cursor.offsetEnd, cursor.offsetEnd, close]) - ], { selection: new ModelEditSelection(cursor.offsetEnd + close.length) }); - } } } @@ -429,12 +420,24 @@ export function stringQuote(doc: EditableDocument, start: number = doc.selection let cursor = doc.getTokenCursor(start); if (cursor.withinString()) { // inside a string, let's be clever - if (cursor.offsetEnd - 1 == start && cursor.getToken().type == "str" || cursor.getToken().type == "str-end") { - doc.selection = new ModelEditSelection(start + 1); + if (cursor.getToken().type == "close") { + if (doc.model.getText(0, start).endsWith('\\')) { + doc.model.edit([ + new ModelEdit('changeRange', [start, start, '"']) + ], { selection: new ModelEditSelection(start + 1) }); + } else { + close(doc, '"', start); + } } else { - doc.model.edit([ - new ModelEdit('changeRange', [start, start, '"']) - ], { selection: new ModelEditSelection(start + 1) }); + if (doc.model.getText(0, start).endsWith('\\')) { + doc.model.edit([ + new ModelEdit('changeRange', [start, start, '"']) + ], { selection: new ModelEditSelection(start + 1) }); + } else { + doc.model.edit([ + new ModelEdit('changeRange', [start, start, '\\"']) + ], { selection: new ModelEditSelection(start + 2) }); + } } } else { doc.model.edit([ diff --git a/src/extension-test/unit/cursor-doc/paredit-test.ts b/src/extension-test/unit/cursor-doc/paredit-test.ts index 14567d90b..33519e94b 100644 --- a/src/extension-test/unit/cursor-doc/paredit-test.ts +++ b/src/extension-test/unit/cursor-doc/paredit-test.ts @@ -441,3 +441,26 @@ describe('paredit', () => { }); + + describe('edits', () => { + it('Closes list', () => { + const doc: mock.MockDocument = new mock.MockDocument(), + text = '(str "foo")', + caret = 10; + doc.insertString(text); + doc.selection = new ModelEditSelection(caret); + paredit.close(doc, ')'); + expect(doc.model.getText(0, Infinity)).equal(text); + expect(doc.selection).deep.equal(new ModelEditSelection(caret + 1)); + }); + it('Closes quote at end of string', () => { + const doc: mock.MockDocument = new mock.MockDocument(), + text = '(str "foo")', + caret = 9; + doc.insertString(text); + doc.selection = new ModelEditSelection(caret); + paredit.stringQuote(doc); + expect(doc.model.getText(0, Infinity)).equal(text); + expect(doc.selection).deep.equal(new ModelEditSelection(caret + 1)); + }); + }); From 71078fbfa33c9aa1435a7fb0ef5465b9bacba2dc Mon Sep 17 00:00:00 2001 From: Peter Date: Wed, 18 Mar 2020 15:49:21 +0100 Subject: [PATCH 2/8] Lex quoted quotes inside strings correctly --- src/cursor-doc/clojure-lexer.ts | 2 +- .../unit/cursor-doc/clojure-lexer-test.ts | 14 +++++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/cursor-doc/clojure-lexer.ts b/src/cursor-doc/clojure-lexer.ts index c5ba9666e..1b80974a1 100644 --- a/src/cursor-doc/clojure-lexer.ts +++ b/src/cursor-doc/clojure-lexer.ts @@ -90,7 +90,7 @@ let inString = new LexicalGrammar() // end a string inString.terminal(/"/, (l, m) => ({ type: "close" })) // still within a string -inString.terminal(/(\\.|[^\\"\t\r\n ])+/, (l, m) => ({ type: "str-inside" })) +inString.terminal(/(\\.|[^"\s])+/, (l, m) => ({ type: "str-inside" })) // whitespace, excluding newlines inString.terminal(/[\t ]+/, (l, m) => ({ type: "ws" })) // newlines, we want each one as a token of its own diff --git a/src/extension-test/unit/cursor-doc/clojure-lexer-test.ts b/src/extension-test/unit/cursor-doc/clojure-lexer-test.ts index 73460a701..8df4edf63 100644 --- a/src/extension-test/unit/cursor-doc/clojure-lexer-test.ts +++ b/src/extension-test/unit/cursor-doc/clojure-lexer-test.ts @@ -246,6 +246,16 @@ describe('Scanner', () => { expect(tokens[4].type).equals('close'); expect(tokens[4].raw).equals('"'); }); + it('tokenizes quoted quotes in strings', () => { + let tokens = scanner.processLine('"\\""'); + expect(tokens[0].type).equals('open'); + expect(tokens[0].raw).equals('"'); + expect(tokens[1].type).equals('str-inside'); + expect(tokens[1].raw).equals('\\"'); + tokens = scanner.processLine('"foo\\"bar"'); + expect(tokens[1].type).equals('str-inside'); + expect(tokens[1].raw).equals('foo\\"bar'); + }); }); describe('Reported issues', () => { it('too long lines - #566', () => { @@ -282,6 +292,4 @@ describe('Scanner', () => { expect(tokens[0].raw).equals("#'foo"); }); }); -}); - - +}); \ No newline at end of file From 8b7aba00a8dff47f7b647f76daa90acbe8ff6deb Mon Sep 17 00:00:00 2001 From: Peter Date: Wed, 18 Mar 2020 15:54:06 +0100 Subject: [PATCH 3/8] Make check for inside string more robust Simplifying it based on that it is a kind of list --- src/cursor-doc/token-cursor.ts | 7 +- .../unit/cursor-doc/token-cursor-test.ts | 64 +++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/src/cursor-doc/token-cursor.ts b/src/cursor-doc/token-cursor.ts index 98c33cf99..622f35d1b 100644 --- a/src/cursor-doc/token-cursor.ts +++ b/src/cursor-doc/token-cursor.ts @@ -579,7 +579,12 @@ export class LispTokenCursor extends TokenCursor { * Indicates if the current token is inside a string */ withinString() { - return this.getToken().type == 'str-inside' || this.getPrevToken().type == 'str-inside'; + const cursor = this.clone(); + cursor.backwardList(); + if (cursor.getPrevToken().type === 'open' && cursor.getPrevToken().raw === '"') { + return true; + }; + return false; } /** diff --git a/src/extension-test/unit/cursor-doc/token-cursor-test.ts b/src/extension-test/unit/cursor-doc/token-cursor-test.ts index 2eebf7a57..a8999f339 100644 --- a/src/extension-test/unit/cursor-doc/token-cursor-test.ts +++ b/src/extension-test/unit/cursor-doc/token-cursor-test.ts @@ -47,12 +47,38 @@ describe('Token Cursor', () => { cursor.backwardList(); expect(cursor.offsetStart).equal(5); }); + it('backwardUpList: (a(b(c•#f•(#b •|[:f :b :z])•#z•1))) => (a(b(c•|#f•(#b •[:f :b :z])•#z•1)))', () => { const cursor: LispTokenCursor = doc.getTokenCursor(15); cursor.backwardUpList(); expect(cursor.offsetStart).equal(7); }); + // TODO: Figure out why adding these tests make other test break! + xdescribe('Navigation in and around strings', () => { + it('backwardList moves to start of string', () => { + const doc = new mock.MockDocument(); + doc.insertString('(str [] "", "foo" "f b b" " f b b " "\"" \")'); + const cursor: LispTokenCursor = doc.getTokenCursor(21); + cursor.backwardList(); + expect(cursor.offsetStart).equal(19); + }); + it('forwardList moves to end of string', () => { + const doc = new mock.MockDocument(); + doc.insertString('(str [] "", "foo" "f b b" " f b b " "\"" \")'); + const cursor: LispTokenCursor = doc.getTokenCursor(21); + cursor.forwardList(); + expect(cursor.offsetStart).equal(27); + }); + it('backwardSexpr inside string moves past quoted characters', () => { + const doc = new mock.MockDocument(); + doc.insertString('(str [] "foo \" bar")'); + const cursor: LispTokenCursor = doc.getTokenCursor(15); + cursor.backwardSexp(); + expect(cursor.offsetStart).equal(13); + }); + }) + describe('Current Form', () => { const docText = '(aaa (bbb (ccc •#foo•(#bar •#baz•[:a :b :c]•x•#(a b c))•#baz•yyy• z z z •foo• • bar)))'.replace(/•/g, '\n'), doc: mock.MockDocument = new mock.MockDocument(); @@ -114,4 +140,42 @@ describe('Token Cursor', () => { expect(cursor.rangeForCurrentForm(1)).deep.equal([0, 2]); }); }); + describe('Location State', () => { + it('Knows when inside string', () => { + const doc = new mock.MockDocument(); + doc.insertString('(str [] "", "foo" "f b b" " f b b " "\"" \")'); + const withinEmpty = doc.getTokenCursor(9); + expect(withinEmpty.withinString()).equal(true); + const adjacentOutsideLeft = doc.getTokenCursor(8); + expect(adjacentOutsideLeft.withinString()).equal(false); + const adjacentOutsideRight = doc.getTokenCursor(10); + expect(adjacentOutsideRight.withinString()).equal(false); + const noStringWS = doc.getTokenCursor(11); + expect(noStringWS.withinString()).equal(false); + const leftOfFirstWord = doc.getTokenCursor(13); + expect(leftOfFirstWord.withinString()).equal(true); + const rightOfLastWord = doc.getTokenCursor(16); + expect(rightOfLastWord.withinString()).equal(true); + const inWord = doc.getTokenCursor(14); + expect(inWord.withinString()).equal(true); + const spaceBetweenWords = doc.getTokenCursor(21); + expect(spaceBetweenWords.withinString()).equal(true); + const spaceBeforeFirstWord = doc.getTokenCursor(33); + expect(spaceBeforeFirstWord.withinString()).equal(true); + const spaceAfterLastWord = doc.getTokenCursor(41); + expect(spaceAfterLastWord.withinString()).equal(true); + const beforeQuotedStringQuote = doc.getTokenCursor(46); + expect(beforeQuotedStringQuote.withinString()).equal(true); + // const inQuotedStringQuote = doc.getTokenCursor(47); + // expect(inQuotedStringQuote.withinString()).equal(true); + // const afterQuotedStringQuote = doc.getTokenCursor(48); + // expect(afterQuotedStringQuote.withinString()).equal(true); + // const beforeLiteralQuote = doc.getTokenCursor(50); + // expect(beforeLiteralQuote.withinString()).equal(false); + // const inLiteralQuote = doc.getTokenCursor(51); + // expect(inLiteralQuote.withinString()).equal(false); + // const afterLiteralQuote = doc.getTokenCursor(52); + // expect(afterLiteralQuote.withinString()).equal(false); + }); + }); }); From f9ab9d4745005be5bdc60d4d312272c3046104ac Mon Sep 17 00:00:00 2001 From: Peter Date: Wed, 18 Mar 2020 20:31:54 +0100 Subject: [PATCH 4/8] Fix test fixtures for quoted quotes --- .../unit/cursor-doc/token-cursor-test.ts | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/extension-test/unit/cursor-doc/token-cursor-test.ts b/src/extension-test/unit/cursor-doc/token-cursor-test.ts index a8999f339..e7804b250 100644 --- a/src/extension-test/unit/cursor-doc/token-cursor-test.ts +++ b/src/extension-test/unit/cursor-doc/token-cursor-test.ts @@ -55,24 +55,24 @@ describe('Token Cursor', () => { }); // TODO: Figure out why adding these tests make other test break! - xdescribe('Navigation in and around strings', () => { + describe('Navigation in and around strings', () => { it('backwardList moves to start of string', () => { const doc = new mock.MockDocument(); - doc.insertString('(str [] "", "foo" "f b b" " f b b " "\"" \")'); + doc.insertString('(str [] "", "foo" "f b b" " f b b " "\\"" \\")'); const cursor: LispTokenCursor = doc.getTokenCursor(21); cursor.backwardList(); expect(cursor.offsetStart).equal(19); }); it('forwardList moves to end of string', () => { const doc = new mock.MockDocument(); - doc.insertString('(str [] "", "foo" "f b b" " f b b " "\"" \")'); + doc.insertString('(str [] "", "foo" "f b b" " f b b " "\\"" \\")'); const cursor: LispTokenCursor = doc.getTokenCursor(21); cursor.forwardList(); expect(cursor.offsetStart).equal(27); }); it('backwardSexpr inside string moves past quoted characters', () => { const doc = new mock.MockDocument(); - doc.insertString('(str [] "foo \" bar")'); + doc.insertString('(str [] "foo \\" bar")'); const cursor: LispTokenCursor = doc.getTokenCursor(15); cursor.backwardSexp(); expect(cursor.offsetStart).equal(13); @@ -143,7 +143,7 @@ describe('Token Cursor', () => { describe('Location State', () => { it('Knows when inside string', () => { const doc = new mock.MockDocument(); - doc.insertString('(str [] "", "foo" "f b b" " f b b " "\"" \")'); + doc.insertString('(str [] "", "foo" "f b b" " f b b " "\\"" \\")'); const withinEmpty = doc.getTokenCursor(9); expect(withinEmpty.withinString()).equal(true); const adjacentOutsideLeft = doc.getTokenCursor(8); @@ -166,16 +166,16 @@ describe('Token Cursor', () => { expect(spaceAfterLastWord.withinString()).equal(true); const beforeQuotedStringQuote = doc.getTokenCursor(46); expect(beforeQuotedStringQuote.withinString()).equal(true); - // const inQuotedStringQuote = doc.getTokenCursor(47); - // expect(inQuotedStringQuote.withinString()).equal(true); - // const afterQuotedStringQuote = doc.getTokenCursor(48); - // expect(afterQuotedStringQuote.withinString()).equal(true); - // const beforeLiteralQuote = doc.getTokenCursor(50); - // expect(beforeLiteralQuote.withinString()).equal(false); - // const inLiteralQuote = doc.getTokenCursor(51); - // expect(inLiteralQuote.withinString()).equal(false); - // const afterLiteralQuote = doc.getTokenCursor(52); - // expect(afterLiteralQuote.withinString()).equal(false); + const inQuotedStringQuote = doc.getTokenCursor(47); + expect(inQuotedStringQuote.withinString()).equal(true); + const afterQuotedStringQuote = doc.getTokenCursor(48); + expect(afterQuotedStringQuote.withinString()).equal(true); + const beforeLiteralQuote = doc.getTokenCursor(50); + expect(beforeLiteralQuote.withinString()).equal(false); + const inLiteralQuote = doc.getTokenCursor(51); + expect(inLiteralQuote.withinString()).equal(false); + const afterLiteralQuote = doc.getTokenCursor(52); + expect(afterLiteralQuote.withinString()).equal(false); }); }); }); From a40c45ca51dd205469deefb4e94e24f0adab77e9 Mon Sep 17 00:00:00 2001 From: Peter Date: Wed, 18 Mar 2020 15:21:57 +0100 Subject: [PATCH 5/8] Close string with quote at the end Fixes: #587 --- CHANGELOG.md | 1 + src/cursor-doc/paredit.ts | 39 ++++++++++--------- .../unit/cursor-doc/paredit-test.ts | 23 +++++++++++ 3 files changed, 45 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e929d76f6..22adb26a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ Changes to Calva. ## [Unreleased] +- [Fix: REPL Window Paredit does not close strings properly](https://github.com/BetterThanTomorrow/calva/issues/587) ## [2.0.85] - 2020-03-15 - Fix: Make lein-shadow project type use lein injections diff --git a/src/cursor-doc/paredit.ts b/src/cursor-doc/paredit.ts index 33676a5bb..117b374e3 100644 --- a/src/cursor-doc/paredit.ts +++ b/src/cursor-doc/paredit.ts @@ -350,23 +350,14 @@ export function open(doc: EditableDocument, open: string, close: string, start: } export function close(doc: EditableDocument, close: string, start: number = doc.selectionRight) { - let cursor = doc.getTokenCursor(); + const cursor = doc.getTokenCursor(start); cursor.forwardWhitespace(false); if (cursor.getToken().raw == close) { + doc.selection = new ModelEditSelection(start + close.length); + } else { doc.model.edit([ - new ModelEdit('changeRange', [start, cursor.offsetStart, ""]) + new ModelEdit('changeRange', [start, start, close]) ], { selection: new ModelEditSelection(start + close.length) }); - } else { - // one of two things are possible: - if (cursor.forwardList()) { - // we are in a matched list, just jump to the end of it. - doc.selection = new ModelEditSelection(cursor.offsetEnd); - } else { - while (cursor.forwardSexp()) { } - doc.model.edit([ - new ModelEdit('changeRange', [cursor.offsetEnd, cursor.offsetEnd, close]) - ], { selection: new ModelEditSelection(cursor.offsetEnd + close.length) }); - } } } @@ -429,12 +420,24 @@ export function stringQuote(doc: EditableDocument, start: number = doc.selection let cursor = doc.getTokenCursor(start); if (cursor.withinString()) { // inside a string, let's be clever - if (cursor.offsetEnd - 1 == start && cursor.getToken().type == "str" || cursor.getToken().type == "str-end") { - doc.selection = new ModelEditSelection(start + 1); + if (cursor.getToken().type == "close") { + if (doc.model.getText(0, start).endsWith('\\')) { + doc.model.edit([ + new ModelEdit('changeRange', [start, start, '"']) + ], { selection: new ModelEditSelection(start + 1) }); + } else { + close(doc, '"', start); + } } else { - doc.model.edit([ - new ModelEdit('changeRange', [start, start, '"']) - ], { selection: new ModelEditSelection(start + 1) }); + if (doc.model.getText(0, start).endsWith('\\')) { + doc.model.edit([ + new ModelEdit('changeRange', [start, start, '"']) + ], { selection: new ModelEditSelection(start + 1) }); + } else { + doc.model.edit([ + new ModelEdit('changeRange', [start, start, '\\"']) + ], { selection: new ModelEditSelection(start + 2) }); + } } } else { doc.model.edit([ diff --git a/src/extension-test/unit/cursor-doc/paredit-test.ts b/src/extension-test/unit/cursor-doc/paredit-test.ts index 14567d90b..33519e94b 100644 --- a/src/extension-test/unit/cursor-doc/paredit-test.ts +++ b/src/extension-test/unit/cursor-doc/paredit-test.ts @@ -441,3 +441,26 @@ describe('paredit', () => { }); + + describe('edits', () => { + it('Closes list', () => { + const doc: mock.MockDocument = new mock.MockDocument(), + text = '(str "foo")', + caret = 10; + doc.insertString(text); + doc.selection = new ModelEditSelection(caret); + paredit.close(doc, ')'); + expect(doc.model.getText(0, Infinity)).equal(text); + expect(doc.selection).deep.equal(new ModelEditSelection(caret + 1)); + }); + it('Closes quote at end of string', () => { + const doc: mock.MockDocument = new mock.MockDocument(), + text = '(str "foo")', + caret = 9; + doc.insertString(text); + doc.selection = new ModelEditSelection(caret); + paredit.stringQuote(doc); + expect(doc.model.getText(0, Infinity)).equal(text); + expect(doc.selection).deep.equal(new ModelEditSelection(caret + 1)); + }); + }); From 23f4c8d22e2e40ff9d797db43f24b584153d7059 Mon Sep 17 00:00:00 2001 From: Peter Date: Wed, 18 Mar 2020 15:49:21 +0100 Subject: [PATCH 6/8] Lex quoted quotes inside strings correctly --- src/cursor-doc/clojure-lexer.ts | 2 +- .../unit/cursor-doc/clojure-lexer-test.ts | 14 +++++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/cursor-doc/clojure-lexer.ts b/src/cursor-doc/clojure-lexer.ts index c5ba9666e..1b80974a1 100644 --- a/src/cursor-doc/clojure-lexer.ts +++ b/src/cursor-doc/clojure-lexer.ts @@ -90,7 +90,7 @@ let inString = new LexicalGrammar() // end a string inString.terminal(/"/, (l, m) => ({ type: "close" })) // still within a string -inString.terminal(/(\\.|[^\\"\t\r\n ])+/, (l, m) => ({ type: "str-inside" })) +inString.terminal(/(\\.|[^"\s])+/, (l, m) => ({ type: "str-inside" })) // whitespace, excluding newlines inString.terminal(/[\t ]+/, (l, m) => ({ type: "ws" })) // newlines, we want each one as a token of its own diff --git a/src/extension-test/unit/cursor-doc/clojure-lexer-test.ts b/src/extension-test/unit/cursor-doc/clojure-lexer-test.ts index 73460a701..8df4edf63 100644 --- a/src/extension-test/unit/cursor-doc/clojure-lexer-test.ts +++ b/src/extension-test/unit/cursor-doc/clojure-lexer-test.ts @@ -246,6 +246,16 @@ describe('Scanner', () => { expect(tokens[4].type).equals('close'); expect(tokens[4].raw).equals('"'); }); + it('tokenizes quoted quotes in strings', () => { + let tokens = scanner.processLine('"\\""'); + expect(tokens[0].type).equals('open'); + expect(tokens[0].raw).equals('"'); + expect(tokens[1].type).equals('str-inside'); + expect(tokens[1].raw).equals('\\"'); + tokens = scanner.processLine('"foo\\"bar"'); + expect(tokens[1].type).equals('str-inside'); + expect(tokens[1].raw).equals('foo\\"bar'); + }); }); describe('Reported issues', () => { it('too long lines - #566', () => { @@ -282,6 +292,4 @@ describe('Scanner', () => { expect(tokens[0].raw).equals("#'foo"); }); }); -}); - - +}); \ No newline at end of file From e19ae9ff7ffcbd337f75edfe642aacbd35546e99 Mon Sep 17 00:00:00 2001 From: Peter Date: Wed, 18 Mar 2020 15:54:06 +0100 Subject: [PATCH 7/8] Make check for inside string more robust Simplifying it based on that it is a kind of list --- src/cursor-doc/token-cursor.ts | 7 +- .../unit/cursor-doc/token-cursor-test.ts | 64 +++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/src/cursor-doc/token-cursor.ts b/src/cursor-doc/token-cursor.ts index 98c33cf99..622f35d1b 100644 --- a/src/cursor-doc/token-cursor.ts +++ b/src/cursor-doc/token-cursor.ts @@ -579,7 +579,12 @@ export class LispTokenCursor extends TokenCursor { * Indicates if the current token is inside a string */ withinString() { - return this.getToken().type == 'str-inside' || this.getPrevToken().type == 'str-inside'; + const cursor = this.clone(); + cursor.backwardList(); + if (cursor.getPrevToken().type === 'open' && cursor.getPrevToken().raw === '"') { + return true; + }; + return false; } /** diff --git a/src/extension-test/unit/cursor-doc/token-cursor-test.ts b/src/extension-test/unit/cursor-doc/token-cursor-test.ts index 2eebf7a57..a8999f339 100644 --- a/src/extension-test/unit/cursor-doc/token-cursor-test.ts +++ b/src/extension-test/unit/cursor-doc/token-cursor-test.ts @@ -47,12 +47,38 @@ describe('Token Cursor', () => { cursor.backwardList(); expect(cursor.offsetStart).equal(5); }); + it('backwardUpList: (a(b(c•#f•(#b •|[:f :b :z])•#z•1))) => (a(b(c•|#f•(#b •[:f :b :z])•#z•1)))', () => { const cursor: LispTokenCursor = doc.getTokenCursor(15); cursor.backwardUpList(); expect(cursor.offsetStart).equal(7); }); + // TODO: Figure out why adding these tests make other test break! + xdescribe('Navigation in and around strings', () => { + it('backwardList moves to start of string', () => { + const doc = new mock.MockDocument(); + doc.insertString('(str [] "", "foo" "f b b" " f b b " "\"" \")'); + const cursor: LispTokenCursor = doc.getTokenCursor(21); + cursor.backwardList(); + expect(cursor.offsetStart).equal(19); + }); + it('forwardList moves to end of string', () => { + const doc = new mock.MockDocument(); + doc.insertString('(str [] "", "foo" "f b b" " f b b " "\"" \")'); + const cursor: LispTokenCursor = doc.getTokenCursor(21); + cursor.forwardList(); + expect(cursor.offsetStart).equal(27); + }); + it('backwardSexpr inside string moves past quoted characters', () => { + const doc = new mock.MockDocument(); + doc.insertString('(str [] "foo \" bar")'); + const cursor: LispTokenCursor = doc.getTokenCursor(15); + cursor.backwardSexp(); + expect(cursor.offsetStart).equal(13); + }); + }) + describe('Current Form', () => { const docText = '(aaa (bbb (ccc •#foo•(#bar •#baz•[:a :b :c]•x•#(a b c))•#baz•yyy• z z z •foo• • bar)))'.replace(/•/g, '\n'), doc: mock.MockDocument = new mock.MockDocument(); @@ -114,4 +140,42 @@ describe('Token Cursor', () => { expect(cursor.rangeForCurrentForm(1)).deep.equal([0, 2]); }); }); + describe('Location State', () => { + it('Knows when inside string', () => { + const doc = new mock.MockDocument(); + doc.insertString('(str [] "", "foo" "f b b" " f b b " "\"" \")'); + const withinEmpty = doc.getTokenCursor(9); + expect(withinEmpty.withinString()).equal(true); + const adjacentOutsideLeft = doc.getTokenCursor(8); + expect(adjacentOutsideLeft.withinString()).equal(false); + const adjacentOutsideRight = doc.getTokenCursor(10); + expect(adjacentOutsideRight.withinString()).equal(false); + const noStringWS = doc.getTokenCursor(11); + expect(noStringWS.withinString()).equal(false); + const leftOfFirstWord = doc.getTokenCursor(13); + expect(leftOfFirstWord.withinString()).equal(true); + const rightOfLastWord = doc.getTokenCursor(16); + expect(rightOfLastWord.withinString()).equal(true); + const inWord = doc.getTokenCursor(14); + expect(inWord.withinString()).equal(true); + const spaceBetweenWords = doc.getTokenCursor(21); + expect(spaceBetweenWords.withinString()).equal(true); + const spaceBeforeFirstWord = doc.getTokenCursor(33); + expect(spaceBeforeFirstWord.withinString()).equal(true); + const spaceAfterLastWord = doc.getTokenCursor(41); + expect(spaceAfterLastWord.withinString()).equal(true); + const beforeQuotedStringQuote = doc.getTokenCursor(46); + expect(beforeQuotedStringQuote.withinString()).equal(true); + // const inQuotedStringQuote = doc.getTokenCursor(47); + // expect(inQuotedStringQuote.withinString()).equal(true); + // const afterQuotedStringQuote = doc.getTokenCursor(48); + // expect(afterQuotedStringQuote.withinString()).equal(true); + // const beforeLiteralQuote = doc.getTokenCursor(50); + // expect(beforeLiteralQuote.withinString()).equal(false); + // const inLiteralQuote = doc.getTokenCursor(51); + // expect(inLiteralQuote.withinString()).equal(false); + // const afterLiteralQuote = doc.getTokenCursor(52); + // expect(afterLiteralQuote.withinString()).equal(false); + }); + }); }); From 62e9f13ad7bc1e5439d2108bf0197af42361fb14 Mon Sep 17 00:00:00 2001 From: Peter Date: Wed, 18 Mar 2020 20:31:54 +0100 Subject: [PATCH 8/8] Fix test fixtures for quoted quotes --- .../unit/cursor-doc/token-cursor-test.ts | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/extension-test/unit/cursor-doc/token-cursor-test.ts b/src/extension-test/unit/cursor-doc/token-cursor-test.ts index a8999f339..e7804b250 100644 --- a/src/extension-test/unit/cursor-doc/token-cursor-test.ts +++ b/src/extension-test/unit/cursor-doc/token-cursor-test.ts @@ -55,24 +55,24 @@ describe('Token Cursor', () => { }); // TODO: Figure out why adding these tests make other test break! - xdescribe('Navigation in and around strings', () => { + describe('Navigation in and around strings', () => { it('backwardList moves to start of string', () => { const doc = new mock.MockDocument(); - doc.insertString('(str [] "", "foo" "f b b" " f b b " "\"" \")'); + doc.insertString('(str [] "", "foo" "f b b" " f b b " "\\"" \\")'); const cursor: LispTokenCursor = doc.getTokenCursor(21); cursor.backwardList(); expect(cursor.offsetStart).equal(19); }); it('forwardList moves to end of string', () => { const doc = new mock.MockDocument(); - doc.insertString('(str [] "", "foo" "f b b" " f b b " "\"" \")'); + doc.insertString('(str [] "", "foo" "f b b" " f b b " "\\"" \\")'); const cursor: LispTokenCursor = doc.getTokenCursor(21); cursor.forwardList(); expect(cursor.offsetStart).equal(27); }); it('backwardSexpr inside string moves past quoted characters', () => { const doc = new mock.MockDocument(); - doc.insertString('(str [] "foo \" bar")'); + doc.insertString('(str [] "foo \\" bar")'); const cursor: LispTokenCursor = doc.getTokenCursor(15); cursor.backwardSexp(); expect(cursor.offsetStart).equal(13); @@ -143,7 +143,7 @@ describe('Token Cursor', () => { describe('Location State', () => { it('Knows when inside string', () => { const doc = new mock.MockDocument(); - doc.insertString('(str [] "", "foo" "f b b" " f b b " "\"" \")'); + doc.insertString('(str [] "", "foo" "f b b" " f b b " "\\"" \\")'); const withinEmpty = doc.getTokenCursor(9); expect(withinEmpty.withinString()).equal(true); const adjacentOutsideLeft = doc.getTokenCursor(8); @@ -166,16 +166,16 @@ describe('Token Cursor', () => { expect(spaceAfterLastWord.withinString()).equal(true); const beforeQuotedStringQuote = doc.getTokenCursor(46); expect(beforeQuotedStringQuote.withinString()).equal(true); - // const inQuotedStringQuote = doc.getTokenCursor(47); - // expect(inQuotedStringQuote.withinString()).equal(true); - // const afterQuotedStringQuote = doc.getTokenCursor(48); - // expect(afterQuotedStringQuote.withinString()).equal(true); - // const beforeLiteralQuote = doc.getTokenCursor(50); - // expect(beforeLiteralQuote.withinString()).equal(false); - // const inLiteralQuote = doc.getTokenCursor(51); - // expect(inLiteralQuote.withinString()).equal(false); - // const afterLiteralQuote = doc.getTokenCursor(52); - // expect(afterLiteralQuote.withinString()).equal(false); + const inQuotedStringQuote = doc.getTokenCursor(47); + expect(inQuotedStringQuote.withinString()).equal(true); + const afterQuotedStringQuote = doc.getTokenCursor(48); + expect(afterQuotedStringQuote.withinString()).equal(true); + const beforeLiteralQuote = doc.getTokenCursor(50); + expect(beforeLiteralQuote.withinString()).equal(false); + const inLiteralQuote = doc.getTokenCursor(51); + expect(inLiteralQuote.withinString()).equal(false); + const afterLiteralQuote = doc.getTokenCursor(52); + expect(afterLiteralQuote.withinString()).equal(false); }); }); });