vscode-texinfo/src/context/folding_range.ts

169 lines
6.3 KiB
TypeScript
Raw Normal View History

2020-10-13 20:15:27 +00:00
/**
2020-10-24 21:45:32 +00:00
* context/folding_range.ts
2020-10-13 20:15:27 +00:00
*
* @author CismonX <admin@cismon.net>
* @license MIT
*/
import * as vscode from 'vscode';
2020-10-24 21:45:32 +00:00
import { FoldingRange, Range } from '../utils/types';
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-24 21:45:32 +00:00
export default class FoldingRangeContext {
2020-10-13 20:15:27 +00:00
2020-10-14 22:08:10 +00:00
/**
* Get VSCode folding ranges from the context.
*/
2020-10-17 13:41:03 +00:00
get values() {
return this.foldingRanges ??= this.calculateFoldingRanges();
2020-10-14 19:22:32 +00:00
}
2020-10-20 20:07:44 +00:00
private foldingRanges?: FoldingRange[];
2020-10-14 19:22:32 +00:00
private commentRange?: Range;
2020-10-13 20:15:27 +00:00
private headerStart?: number;
private closingChapter?: number;
private closingSection?: number;
private closingSubsection?: number;
2020-10-20 20:07:44 +00:00
/**
* Update folding range context based on document change event.
*
* @param events Events describing the changes in the document.
*/
update(events: readonly vscode.TextDocumentContentChangeEvent[]) {
if (this.foldingRanges === undefined) return false;
for (const event of events) {
const updatedLines = event.text.split(this.document.eol === vscode.EndOfLine.LF ? '\n' : '\r\n').length;
// Clear cached folding range when line count changes.
if (updatedLines !== 1 || event.range.start.line !== event.range.end.line) {
this.foldingRanges = undefined;
return true;
}
}
return false;
2020-10-13 20:15:27 +00:00
}
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() {
2020-10-20 20:07:44 +00:00
this.foldingRanges = [];
2020-10-15 18:43:07 +00:00
this.headerStart = undefined;
const closingBlocks = <{ name: string, line: number }[]>[];
let lastLine = this.document.lineCount - 1;
let verbatim = false;
for (let idx = lastLine; idx >= 0; --idx) {
2020-10-15 18:43:07 +00:00
const line = this.document.lineAt(idx).text;
2020-10-20 20:07:44 +00:00
if (!line.startsWith('@')) continue;
if (!verbatim) {
if (line === '@bye') {
lastLine = idx;
// Abort anything after `@bye`.
2020-10-20 20:07:44 +00:00
this.foldingRanges = [];
this.commentRange = undefined;
this.headerStart = undefined;
continue;
}
2020-10-20 20:07:44 +00:00
if (this.processComment(line, idx)) continue;
2020-10-13 20:15:27 +00:00
}
2020-10-15 18:43:07 +00:00
// Process block.
if (line.startsWith('@end ')) {
2020-10-20 20:07:44 +00:00
if (verbatim) continue;
const name = line.substring(5);
name === 'verbatim' && (verbatim = true);
closingBlocks.push({ name: name, line: idx });
continue;
}
if (!verbatim && this.processNode(line, idx, lastLine)) continue;
const closingBlock = closingBlocks.pop();
if (closingBlock === undefined) continue;
if (line.substring(1, closingBlock.name.length + 2).trim() === closingBlock.name) {
this.addRange(idx, closingBlock.line, { name: closingBlock.name });
// If `verbatim == true` goes here, this line must be the `@verbatim` line.
verbatim = false;
2020-10-15 18:43:07 +00:00
} else {
closingBlocks.push(closingBlock);
2020-10-15 18:43:07 +00:00
}
2020-10-24 21:45:32 +00:00
}
if (this.commentRange !== undefined) {
this.addRange(this.commentRange.start, this.commentRange.end, { kind: vscode.FoldingRangeKind.Comment });
this.commentRange = undefined;
2020-10-13 20:15:27 +00:00
}
2020-10-17 13:41:03 +00:00
return this.foldingRanges;
2020-10-13 20:15:27 +00:00
}
private processComment(lineText: string, lineNum: number) {
2020-10-24 21:45:32 +00:00
if (!lineText.startsWith('@c')) return false;
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 {
this.addRange(lineNum, this.headerStart, { kind: vscode.FoldingRangeKind.Region });
this.headerStart = undefined;
2020-10-13 20:15:27 +00:00
}
return true;
2020-10-24 21:45:32 +00:00
}
if (this.commentRange === undefined) {
this.commentRange = { start: lineNum, end: lineNum };
} else if (this.commentRange.start - 1 === lineNum) {
this.commentRange.start = lineNum;
} else {
2020-10-20 20:07:44 +00:00
this.addRange(this.commentRange.start, this.commentRange.end, { kind: vscode.FoldingRangeKind.Comment });
this.commentRange = undefined;
2020-10-13 20:15:27 +00:00
}
2020-10-24 21:45:32 +00:00
return true;
2020-10-13 20:15:27 +00:00
}
2020-10-24 21:45:32 +00:00
constructor(private readonly document: vscode.TextDocument) {}
private processNode(lineText: string, lineNum: number, lastLineNum: number) {
if (lineText.startsWith('@subsection ')) {
const detail = lineText.substring(12);
this.addRange(lineNum, this.closingSubsection ?? lastLineNum, { name: 'subsection', detail: detail });
this.closingSubsection = this.getLastTextLine(lineNum - 1);
return true;
} else if (lineText.startsWith('@section ')) {
const detail = lineText.substring(9);
this.addRange(lineNum, this.closingSection ?? lastLineNum, { name: 'section', detail: detail });
this.closingSubsection = this.closingSection = this.getLastTextLine(lineNum - 1);
return true;
} else if (lineText.startsWith('@chapter ')) {
const detail = lineText.substring(9);
this.addRange(lineNum, this.closingChapter ?? lastLineNum, { name: 'chapter', detail: detail });
this.closingSubsection = this.closingSection = this.closingChapter = this.getLastTextLine(lineNum - 1);
return true;
}
return false;
}
private getLastTextLine(lineNum: number, limit = 3) {
for (let idx = lineNum; idx > lineNum - limit; --idx) {
const line = this.document.lineAt(idx).text;
if (line.startsWith('@node ')) return idx - 1;
if (line === '') return idx;
}
return lineNum;
}
private addRange(start: number, end: number, extraArgs: {
name?: string,
detail?: string,
kind?: vscode.FoldingRangeKind
}) {
(this.foldingRanges ??= [])
.push(new FoldingRange(extraArgs.name ?? '', extraArgs.detail ?? '', start, end, extraArgs.kind));
2020-10-13 20:15:27 +00:00
}
2020-10-14 22:08:10 +00:00
}