From 2c6bad25010733600505003e2a80b85bf6fd4562 Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 27 Nov 2018 02:08:51 +0900 Subject: [PATCH] [MFM] Improve hashtag detection --- src/mfm/parser.ts | 20 +++++++++++++------- test/mfm.ts | 29 ++++++++++++++++++++++++++--- 2 files changed, 39 insertions(+), 10 deletions(-) diff --git a/src/mfm/parser.ts b/src/mfm/parser.ts index 1e9b25b72..322d39493 100644 --- a/src/mfm/parser.ts +++ b/src/mfm/parser.ts @@ -30,17 +30,23 @@ function makeNodeWithChildren(name: string, children: Node[], props?: any): Node } function getTrailingPosition(x: string): number { - let pendingBracket = 0; + const brackets = [ + ['(', ')'], + ['「', '」'], + ]; + const pendingBrackets = [] as any; const end = x.split('').findIndex(char => { - if (char == ')') { - if (pendingBracket > 0) { - pendingBracket--; + const closeMatch = brackets.map(x => x[1]).indexOf(char); + const openMatch = brackets.map(x => x[0]).indexOf(char); + if (closeMatch != -1) { + if (pendingBrackets[closeMatch] > 0) { + pendingBrackets[closeMatch]--; return false; } else { return true; } - } else if (char == '(') { - pendingBracket++; + } else if (openMatch != -1) { + pendingBrackets[openMatch] = (pendingBrackets[openMatch] || 0) + 1; return false; } else { return false; @@ -156,7 +162,7 @@ const mfm = P.createLanguage({ let hashtag = match[1]; hashtag = hashtag.substr(0, getTrailingPosition(hashtag)); if (hashtag.match(/^[0-9]+$/)) return P.makeFailure(i, 'not a hashtag'); - if (!['\n', ' ', '(', null, undefined].includes(input[i - 1])) return P.makeFailure(i, 'require space before "#"'); + if (!['\n', ' ', '(', '「', null, undefined].includes(input[i - 1])) return P.makeFailure(i, 'require space before "#"'); return P.makeSuccess(i + ('#' + hashtag).length, makeNode('hashtag', { hashtag: hashtag })); }), //#endregion diff --git a/test/mfm.ts b/test/mfm.ts index 98184c9ad..4e29a6926 100644 --- a/test/mfm.ts +++ b/test/mfm.ts @@ -213,21 +213,44 @@ describe('Text', () => { }); it('with brackets', () => { - const tokens = analyze('(#foo)'); + const tokens1 = analyze('(#foo)'); assert.deepEqual([ text('('), node('hashtag', { hashtag: 'foo' }), text(')'), + ], tokens1); + + const tokens2 = analyze('「#foo」'); + assert.deepEqual([ + text('「'), + node('hashtag', { hashtag: 'foo' }), + text('」'), + ], tokens2); + }); + + it('with mixed brackets', () => { + const tokens = analyze('「#foo(bar)」'); + assert.deepEqual([ + text('「'), + node('hashtag', { hashtag: 'foo(bar)' }), + text('」'), ], tokens); }); it('with brackets (space before)', () => { - const tokens = analyze('(bar #foo)'); + const tokens1 = analyze('(bar #foo)'); assert.deepEqual([ text('(bar '), node('hashtag', { hashtag: 'foo' }), text(')'), - ], tokens); + ], tokens1); + + const tokens2 = analyze('「bar #foo」'); + assert.deepEqual([ + text('「bar '), + node('hashtag', { hashtag: 'foo' }), + text('」'), + ], tokens2); }); it('disallow number only', () => {