tableOfContentsProvider.ts 2.1 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

'use strict';

import * as vscode from 'vscode';

import { MarkdownEngine, IToken } from './markdownEngine';

export interface TocEntry {
	slug: string;
	text: string;
	line: number;
	location: vscode.Location;
}

19
export class TableOfContentsProvider {
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
	private toc: TocEntry[];

	public constructor(
		private engine: MarkdownEngine,
		private document: vscode.TextDocument) { }

	public getToc(): TocEntry[] {
		if (!this.toc) {
			try {
				this.toc = this.buildToc(this.document);
			} catch (e) {
				this.toc = [];
			}
		}
		return this.toc;
	}

	public lookup(fragment: string): number {
38
		const slug = TableOfContentsProvider.slugify(fragment);
39 40 41 42 43 44 45 46 47 48
		for (const entry of this.getToc()) {
			if (entry.slug === slug) {
				return entry.line;
			}
		}
		return NaN;
	}

	private buildToc(document: vscode.TextDocument): any {
		const toc: TocEntry[] = [];
49
		const tokens: IToken[] = this.engine.parse(document.uri, document.getText());
50 51 52 53

		for (const heading of tokens.filter(token => token.type === 'heading_open')) {
			const lineNumber = heading.map[0];
			const line = document.lineAt(lineNumber);
54
			const href = TableOfContentsProvider.slugify(line.text);
55 56 57
			if (href) {
				toc.push({
					slug: href,
58
					text: TableOfContentsProvider.getHeaderText(line.text),
59 60 61 62 63 64 65 66 67 68 69 70 71
					line: lineNumber,
					location: new vscode.Location(document.uri, line.range)
				});
			}
		}
		return toc;
	}

	private static getHeaderText(header: string): string {
		return header.replace(/^\s*(#)+\s*(.*?)\s*\1*$/, '$2').trim();
	}

	public static slugify(header: string): string {
72
		return encodeURI(header.trim()
73
			.toLowerCase()
74 75
			.replace(/[\]\[\!\"\#\$\%\&\'\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\_\{\|\}\~]/g, '')
			.replace(/\s+/g, '-')
76
			.replace(/^\-+/, '')
77
			.replace(/\-+$/, ''));
78 79 80
	}
}