foldingProvider.ts 3.3 KB
Newer Older
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

6
import { Token } from 'markdown-it';
7 8
import * as vscode from 'vscode';
import { MarkdownEngine } from '../markdownEngine';
9
import { TableOfContentsProvider } from '../tableOfContentsProvider';
10

11 12 13
const rangeLimit = 5000;

export default class MarkdownFoldingProvider implements vscode.FoldingRangeProvider {
14 15 16 17 18

	constructor(
		private readonly engine: MarkdownEngine
	) { }

J
Jackson Kearl 已提交
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
	private async getRegions(document: vscode.TextDocument): Promise<vscode.FoldingRange[]> {

		const isStartRegion = (t: string) => /^\s*<!--\s*#?region\b.*-->/.test(t);
		const isEndRegion = (t: string) => /^\s*<!--\s*#?endregion\b.*-->/.test(t);

		const isRegionMarker = (token: Token) => token.type === 'html_block' &&
			(isStartRegion(token.content) || isEndRegion(token.content));


		const tokens = await this.engine.parse(document.uri, document.getText());
		const regionMarkers = tokens.filter(isRegionMarker)
			.map(token => ({ line: token.map[0], isStart: isStartRegion(token.content) }));

		const nestingStack: { line: number, isStart: boolean }[] = [];
		return regionMarkers
			.map(marker => {
				if (marker.isStart) {
					nestingStack.push(marker);
				} else if (nestingStack.length && nestingStack[nestingStack.length - 1].isStart) {
					return new vscode.FoldingRange(nestingStack.pop()!.line, marker.line, vscode.FoldingRangeKind.Region);
				} else {
					// noop: invalid nesting (i.e. [end, start] or [start, end, end])
				}
				return null;
			})
			.filter((region: vscode.FoldingRange | null): region is vscode.FoldingRange => !!region);
	}

47 48
	public async provideFoldingRanges(
		document: vscode.TextDocument,
49
		_: vscode.FoldingContext,
50
		_token: vscode.CancellationToken
51
	): Promise<vscode.FoldingRange[]> {
52 53 54 55 56
		const foldables = await Promise.all([
			this.getRegions(document),
			this.getHeaderFoldingRanges(document),
			this.getBlockFoldingRanges(document)]);
		return [].concat.apply([], foldables).slice(0, rangeLimit);
M
Matt Bierner 已提交
57
	}
58

M
Matt Bierner 已提交
59 60 61
	private async getHeaderFoldingRanges(document: vscode.TextDocument) {
		const tocProvider = new TableOfContentsProvider(this.engine, document);
		const toc = await tocProvider.getToc();
62 63 64 65 66 67 68
		return toc.map(entry => {
			let endLine = entry.location.range.end.line;
			if (document.lineAt(endLine).isEmptyOrWhitespace && endLine >= entry.line + 1) {
				endLine = endLine - 1;
			}
			return new vscode.FoldingRange(entry.line, endLine);
		});
69
	}
70

71
	private async getBlockFoldingRanges(document: vscode.TextDocument): Promise<vscode.FoldingRange[]> {
72

73 74 75 76 77 78 79 80 81 82 83 84 85
		const isFoldableToken = (token: Token) => {
			switch (token.type) {
				case 'fence':
				case 'list_item_open':
					return token.map[1] > token.map[0];

				case 'html_block':
					return token.map[1] > token.map[0] + 1;

				default:
					return false;
			}
		};
86 87

		const tokens = await this.engine.parse(document.uri, document.getText());
88
		const multiLineListItems = tokens.filter(isFoldableToken);
89 90 91 92 93 94
		return multiLineListItems.map(listItem => {
			const start = listItem.map[0];
			const end = listItem.map[1] - 1;
			return new vscode.FoldingRange(start, end);
		});
	}
M
Matt Bierner 已提交
95
}