diff --git a/extensions/markdown-language-features/src/extension.ts b/extensions/markdown-language-features/src/extension.ts index 875b5d69f8a..a9d95e36ce8 100644 --- a/extensions/markdown-language-features/src/extension.ts +++ b/extensions/markdown-language-features/src/extension.ts @@ -17,7 +17,7 @@ import { MarkdownEngine } from './markdownEngine'; import { getMarkdownExtensionContributions } from './markdownExtensions'; import { ExtensionContentSecurityPolicyArbiter, PreviewSecuritySelector } from './security'; import { loadDefaultTelemetryReporter } from './telemetryReporter'; -import { stripSlugifier } from './slugify'; +import { githubSlugifier } from './slugify'; export function activate(context: vscode.ExtensionContext) { @@ -27,7 +27,7 @@ export function activate(context: vscode.ExtensionContext) { const contributions = getMarkdownExtensionContributions(); const cspArbiter = new ExtensionContentSecurityPolicyArbiter(context.globalState, context.workspaceState); - const engine = new MarkdownEngine(contributions, stripSlugifier); + const engine = new MarkdownEngine(contributions, githubSlugifier); const logger = new Logger(); const selector: vscode.DocumentSelector = [ diff --git a/extensions/markdown-language-features/src/slugify.ts b/extensions/markdown-language-features/src/slugify.ts index 768a0c64d97..93c68d8f0e6 100644 --- a/extensions/markdown-language-features/src/slugify.ts +++ b/extensions/markdown-language-features/src/slugify.ts @@ -17,19 +17,16 @@ export interface Slugifier { fromHeading(heading: string): Slug; } -export const stripSlugifier: Slugifier = new class implements Slugifier { - private readonly specialChars: any = { 'à': 'a', 'ä': 'a', 'ã': 'a', 'á': 'a', 'â': 'a', 'æ': 'a', 'å': 'a', 'ë': 'e', 'è': 'e', 'é': 'e', 'ê': 'e', 'î': 'i', 'ï': 'i', 'ì': 'i', 'í': 'i', 'ò': 'o', 'ó': 'o', 'ö': 'o', 'ô': 'o', 'ø': 'o', 'ù': 'o', 'ú': 'u', 'ü': 'u', 'û': 'u', 'ñ': 'n', 'ç': 'c', 'ß': 's', 'ÿ': 'y', 'œ': 'o', 'ŕ': 'r', 'ś': 's', 'ń': 'n', 'ṕ': 'p', 'ẃ': 'w', 'ǵ': 'g', 'ǹ': 'n', 'ḿ': 'm', 'ǘ': 'u', 'ẍ': 'x', 'ź': 'z', 'ḧ': 'h', '·': '-', '/': '-', '_': '-', ',': '-', ':': '-', ';': '-', 'З': '3', 'з': '3' }; - - public fromHeading(heading: string): Slug { - const slugifiedHeading = encodeURI(heading.trim() - .toLowerCase() - .replace(/./g, c => this.specialChars[c] || c) - .replace(/[\]\[\!\'\#\$\%\&\'\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\_\{\|\}\~\`]/g, '') - .replace(/\s+/g, '-') // Replace whitespace with - - .replace(/[^\w\-]+/g, '') // Remove remaining non-word chars - .replace(/^\-+/, '') // Remove leading - - .replace(/\-+$/, '') // Remove trailing - +export const githubSlugifier: Slugifier = new class implements Slugifier { + fromHeading(heading: string): Slug { + const slugifiedHeading = encodeURI( + heading.trim() + .toLowerCase() + .replace(/\s+/g, '-') // Replace whitespace with - + .replace(/[^\w\-]+/gu, '') // Remove non-word chars + .replace(/^\-+/, '') // Remove leading - + .replace(/\-+$/, '') // Remove trailing - ); return new Slug(slugifiedHeading); } -}; \ No newline at end of file +}; diff --git a/extensions/markdown-language-features/src/tableOfContentsProvider.ts b/extensions/markdown-language-features/src/tableOfContentsProvider.ts index c13a52c60d2..30b9de85744 100644 --- a/extensions/markdown-language-features/src/tableOfContentsProvider.ts +++ b/extensions/markdown-language-features/src/tableOfContentsProvider.ts @@ -5,7 +5,7 @@ import * as vscode from 'vscode'; import { MarkdownEngine } from './markdownEngine'; -import { Slug, stripSlugifier } from './slugify'; +import { Slug, githubSlugifier } from './slugify'; export interface TocEntry { readonly slug: Slug; @@ -36,7 +36,7 @@ export class TableOfContentsProvider { public async lookup(fragment: string): Promise { const toc = await this.getToc(); - const slug = stripSlugifier.fromHeading(fragment); + const slug = githubSlugifier.fromHeading(fragment); return toc.find(entry => entry.slug.equals(slug)); } @@ -48,7 +48,7 @@ export class TableOfContentsProvider { const lineNumber = heading.map[0]; const line = document.lineAt(lineNumber); toc.push({ - slug: stripSlugifier.fromHeading(line.text), + slug: githubSlugifier.fromHeading(line.text), text: TableOfContentsProvider.getHeaderText(line.text), level: TableOfContentsProvider.getHeaderLevel(heading.markup), line: lineNumber, diff --git a/extensions/markdown-language-features/src/test/engine.ts b/extensions/markdown-language-features/src/test/engine.ts index fbd2322cc38..860bafad7cc 100644 --- a/extensions/markdown-language-features/src/test/engine.ts +++ b/extensions/markdown-language-features/src/test/engine.ts @@ -6,7 +6,7 @@ import * as vscode from 'vscode'; import { MarkdownEngine } from '../markdownEngine'; import { MarkdownContributions } from '../markdownExtensions'; -import { stripSlugifier } from '../slugify'; +import { githubSlugifier } from '../slugify'; const emptyContributions = new class implements MarkdownContributions { readonly previewScripts: vscode.Uri[] = []; @@ -16,5 +16,5 @@ const emptyContributions = new class implements MarkdownContributions { }; export function createNewMarkdownEngine(): MarkdownEngine { - return new MarkdownEngine(emptyContributions, stripSlugifier); + return new MarkdownEngine(emptyContributions, githubSlugifier); } diff --git a/extensions/markdown-language-features/src/test/tableOfContentsProvider.test.ts b/extensions/markdown-language-features/src/test/tableOfContentsProvider.test.ts index d2f6c180b69..fd98088c8e8 100644 --- a/extensions/markdown-language-features/src/test/tableOfContentsProvider.test.ts +++ b/extensions/markdown-language-features/src/test/tableOfContentsProvider.test.ts @@ -75,18 +75,17 @@ suite('markdown.TableOfContentsProvider', () => { assert.strictEqual(await provider.lookup('fo o'), undefined); }); - test('should normalize special characters #44779', async () => { + test('should handle special characters #44779', async () => { const doc = new InMemoryDocument(testFileName, `# Indentação\n`); const provider = new TableOfContentsProvider(createNewMarkdownEngine(), doc); - assert.strictEqual((await provider.lookup('indentacao'))!.line, 0); + assert.strictEqual((await provider.lookup('indentação'))!.line, 0); }); - test('should map special З, #37079', async () => { - const doc = new InMemoryDocument(testFileName, `### Заголовок Header 3`); + test('should handle special characters 2, #48482', async () => { + const doc = new InMemoryDocument(testFileName, `# Инструкция - Делай Раз, Делай Два\n`); const provider = new TableOfContentsProvider(createNewMarkdownEngine(), doc); - assert.strictEqual((await provider.lookup('Заголовок-header-3'))!.line, 0); - assert.strictEqual((await provider.lookup('3аголовок-header-3'))!.line, 0); + assert.strictEqual((await provider.lookup('инструкция---делай-раз-делай-два'))!.line, 0); }); });