vscode-texinfo/src/folding.ts

184 lines
5.8 KiB
TypeScript
Raw Normal View History

2020-10-13 20:15:27 +00:00
/**
* folding.ts
*
* @author CismonX <admin@cismon.net>
* @license MIT
*/
import * as vscode from 'vscode';
/**
* Provide folding range info for Texinfo source code.
*/
export class FoldingRangeProvider implements vscode.FoldingRangeProvider {
provideFoldingRanges(document: vscode.TextDocument) {
2020-10-15 18:43:07 +00:00
return FoldingRangeContext.get(document).foldingRanges;
2020-10-13 20:15:27 +00:00
}
}
2020-10-14 22:08:10 +00:00
/**
* Stores information about folding ranges for a document.
*/
2020-10-13 20:15:27 +00:00
export class FoldingRangeContext {
private static readonly map = new Map<vscode.TextDocument, FoldingRangeContext>();
2020-10-14 22:08:10 +00:00
/**
* Initialize folding range context for a document.
*
* @param document
*/
2020-10-13 20:15:27 +00:00
static open(document: vscode.TextDocument) {
if (document.languageId === 'texinfo') {
2020-10-15 18:43:07 +00:00
FoldingRangeContext.get(document);
2020-10-13 20:15:27 +00:00
}
}
2020-10-14 22:08:10 +00:00
/**
2020-10-15 18:43:07 +00:00
* Get existing folding range context of a document, or create one if not exist.
2020-10-14 22:08:10 +00:00
*
* @param document
*/
2020-10-13 20:15:27 +00:00
static get(document: vscode.TextDocument) {
2020-10-15 18:43:07 +00:00
return FoldingRangeContext.map.get(document) ?? new FoldingRangeContext(document);
2020-10-13 20:15:27 +00:00
}
2020-10-14 22:08:10 +00:00
/**
* Update the folding range context of a document based on its change event.
*
* @param event Change event of a document.
*/
2020-10-13 20:15:27 +00:00
static update(event: vscode.TextDocumentChangeEvent) {
if (event.document.languageId !== 'texinfo') {
return;
}
2020-10-15 18:43:07 +00:00
FoldingRangeContext.get(event.document).update(event.contentChanges);
2020-10-13 20:15:27 +00:00
}
2020-10-14 22:08:10 +00:00
/**
* Destroy the folding range context of a document.
*
* @param document
*/
2020-10-13 20:15:27 +00:00
static close(document: vscode.TextDocument) {
FoldingRangeContext.map.delete(document);
}
2020-10-14 22:08:10 +00:00
/**
* Destroy all existing folding range contexts.
*/
2020-10-13 20:15:27 +00:00
static clear() {
FoldingRangeContext.map.clear();
}
2020-10-14 22:08:10 +00:00
/**
* Get VSCode folding ranges from the context.
*/
get foldingRanges() {
2020-10-15 18:43:07 +00:00
if (this.bufferedFoldingRanges === undefined) {
this.calculateFoldingRanges();
}
return this.bufferedFoldingRanges;
2020-10-14 19:22:32 +00:00
}
2020-10-15 18:43:07 +00:00
private bufferedFoldingRanges?: vscode.FoldingRange[];
2020-10-14 19:22:32 +00:00
2020-10-15 20:16:13 +00:00
private commentRange?: { start: number, end: number };
2020-10-13 20:15:27 +00:00
private headerStart?: number;
private constructor(private readonly document: vscode.TextDocument) {
FoldingRangeContext.map.set(document, this);
}
2020-10-14 22:08:10 +00:00
/**
* Calculate and update folding ranges for the document.
*
* @param start Starting line number.
* @param end Ending line number.
*/
2020-10-15 18:43:07 +00:00
private calculateFoldingRanges() {
this.headerStart = undefined;
const closingBlocks = <{ name: string, line: number }[]>[];
for (let idx = this.document.lineCount - 1; idx >= 0; --idx) {
const line = this.document.lineAt(idx).text;
if (!line.startsWith('@')) {
2020-10-13 20:15:27 +00:00
continue;
}
2020-10-15 18:43:07 +00:00
if (this.processComment(line, idx)) {
2020-10-13 20:15:27 +00:00
continue;
}
2020-10-15 18:43:07 +00:00
// Process block.
if (line.startsWith('end ', 1)) {
closingBlocks.push({ name: line.substring(5), line: idx });
} else {
const closingBlock = closingBlocks.pop();
if (closingBlock === undefined) {
2020-10-15 20:16:13 +00:00
continue;
2020-10-15 18:43:07 +00:00
}
if (line.substring(1, closingBlock.name.length + 2).trim() === closingBlock.name) {
this.insertRange(idx, closingBlock.line);
} else {
closingBlocks.push(closingBlock);
}
}
2020-10-13 20:15:27 +00:00
}
if (this.commentRange !== undefined) {
2020-10-15 20:16:13 +00:00
this.insertRange(this.commentRange.start, this.commentRange.end, vscode.FoldingRangeKind.Comment);
2020-10-13 20:15:27 +00:00
}
2020-10-15 20:16:13 +00:00
this.commentRange = undefined;
2020-10-13 20:15:27 +00:00
}
private processComment(lineText: string, lineNum: number) {
if (lineText.startsWith('c', 1)) {
if (!lineText.startsWith(' ', 2) && !lineText.startsWith('omment ', 2)) {
return false;
}
// Check for opening/closing header.
if (lineText.startsWith('%**', lineText[2] === ' ' ? 3 : 9)) {
if (this.headerStart === undefined) {
this.headerStart = lineNum;
} else {
2020-10-15 18:43:07 +00:00
this.insertRange(lineNum, this.headerStart);
2020-10-13 20:15:27 +00:00
this.headerStart = undefined;
}
2020-10-14 22:08:10 +00:00
return true;
2020-10-13 20:15:27 +00:00
}
if (this.commentRange === undefined) {
2020-10-15 20:16:13 +00:00
this.commentRange = { start: lineNum, end: lineNum };
} else if (this.commentRange.start - 1 === lineNum) {
this.commentRange.start = lineNum;
2020-10-13 20:15:27 +00:00
} else {
2020-10-15 20:16:13 +00:00
this.insertRange(this.commentRange.start, this.commentRange.end, vscode.FoldingRangeKind.Comment);
this.commentRange = { start: lineNum, end: lineNum };
2020-10-13 20:15:27 +00:00
}
return true;
}
return false;
}
2020-10-15 18:43:07 +00:00
private insertRange(start: number, end: number, kind?: vscode.FoldingRangeKind) {
if (this.bufferedFoldingRanges === undefined) {
this.bufferedFoldingRanges = [];
2020-10-13 20:15:27 +00:00
}
2020-10-15 18:43:07 +00:00
this.bufferedFoldingRanges.push(new vscode.FoldingRange(start, end, kind));
2020-10-13 20:15:27 +00:00
}
/**
2020-10-15 18:43:07 +00:00
* Update folding range context based on document change event.
2020-10-14 22:08:10 +00:00
*
2020-10-15 18:43:07 +00:00
* @param events Events describing the changes in the document.
2020-10-14 22:08:10 +00:00
*/
2020-10-15 18:43:07 +00:00
private update(events: readonly vscode.TextDocumentContentChangeEvent[]) {
for (const event of events) {
const range = event.range;
const updatedLines = event.text.split(this.document.eol === vscode.EndOfLine.LF ? '\n' : '\r\n').length;
// Clear range buffer when line number changes.
if (updatedLines !== 1 || range.start.line !== range.end.line) {
this.bufferedFoldingRanges = undefined;
2020-10-14 22:08:10 +00:00
}
2020-10-15 18:43:07 +00:00
}
2020-10-14 22:08:10 +00:00
}
}