diff --git a/README.md b/README.md
index e4ccaa6..2dda15b 100644
--- a/README.md
+++ b/README.md
@@ -7,32 +7,37 @@ Texinfo language support for Visual Studio Code.
## Features
-
-Syntax Highlighting
-(Screenshots here...)
-
+* **Syntax Highlighting**
+ * Provided by the same [TextMate grammar](https://github.com/Alhadis/language-texinfo/blob/v1.0.0/grammars/texinfo.cson) as [used in GitHub](https://github.com/github/linguist/pull/4589).
+* **Code Completion**
+ * Completion for most @\-commands.
+ * Code snippets for blocks and commands with arguments.
+* **Folding**
+ * Fold on blocks, headers and multiline comments.
+* **Preview**
+ * Display HTML preview in a webview.
+ * Texinfo to HTML conversion is provided by [GNU Texinfo](https://www.gnu.org/software/texinfo/).
-Code Completion
-(Screenshots here...)
-
+Screenshots:
-
-Folding
-(Screenshots here...)
-
-
-
-Display Preview
-(Screenshots here...)
## Requirements
-The "Display Preview" feature depends on the `makeinfo` command-line tool, which is part of [GNU Texinfo](https://www.gnu.org/software/texinfo/).
+* Visual Studio Code version >= 1.40.0. Legacy versions may also work.
+* A latest version of GNU Texinfo.
## Extension Settings
See `File -> Preferences -> Settings -> Extensions -> Texinfo` for details. The settings are self-explanatory.
## Notes
+
+* If syntax highlighting is not satisfactory, try another color theme where keyword/operator colors are distinct (e.g. Solarized Light/Dark, Monokai).
+* Preview content is updated on document save rather than document change.
+* For macOS users: Version of preinstalled GNU Texinfo is very old, use a latest one instead. This can be easily done by `brew install texinfo` and change extension setting `texinfo.makeinfo` value.
+
+## Future Plans
+
+* Implement [Language Server Protocol](https://microsoft.github.io/language-server-protocol/) for the Texinfo language, preferably by extending GNU Texinfo, to alleviate the limitations of the current implementaion.
diff --git a/src/folding.ts b/src/folding.ts
index 644b2e8..981b7a5 100644
--- a/src/folding.ts
+++ b/src/folding.ts
@@ -13,7 +13,7 @@ import * as vscode from 'vscode';
export class FoldingRangeProvider implements vscode.FoldingRangeProvider {
provideFoldingRanges(document: vscode.TextDocument) {
- return FoldingRangeContext.get(document)?.foldingRanges;
+ return FoldingRangeContext.get(document).foldingRanges;
}
}
@@ -31,17 +31,17 @@ export class FoldingRangeContext {
*/
static open(document: vscode.TextDocument) {
if (document.languageId === 'texinfo') {
- new FoldingRangeContext(document);
+ FoldingRangeContext.get(document);
}
}
/**
- * Get existing folding range context of a document.
+ * Get existing folding range context of a document, or create one if not exist.
*
* @param document
*/
static get(document: vscode.TextDocument) {
- return FoldingRangeContext.map.get(document);
+ return FoldingRangeContext.map.get(document) ?? new FoldingRangeContext(document);
}
/**
@@ -53,7 +53,7 @@ export class FoldingRangeContext {
if (event.document.languageId !== 'texinfo') {
return;
}
- FoldingRangeContext.get(event.document)?.update(event.contentChanges);
+ FoldingRangeContext.get(event.document).update(event.contentChanges);
}
/**
@@ -76,22 +76,20 @@ export class FoldingRangeContext {
* Get VSCode folding ranges from the context.
*/
get foldingRanges() {
- return this.ranges.values;
+ if (this.bufferedFoldingRanges === undefined) {
+ this.calculateFoldingRanges();
+ }
+ return this.bufferedFoldingRanges;
}
- private readonly ranges = new FoldingRangeContainer();
+ private bufferedFoldingRanges?: vscode.FoldingRange[];
private commentRange?: [number, number];
private headerStart?: number;
- private closingBlocks = [];
-
private constructor(private readonly document: vscode.TextDocument) {
FoldingRangeContext.map.set(document, this);
- console.log(Date.now());
- this.updateFoldingRanges(0, this.document.lineCount - 1);
- console.log(Date.now());
}
/**
@@ -100,24 +98,37 @@ export class FoldingRangeContext {
* @param start Starting line number.
* @param end Ending line number.
*/
- private updateFoldingRanges(start: number, end: number) {
- for (let idx = end; idx >= start; --idx) {
- const line = this.document.lineAt(idx);
- const lineText = line.text;
- const lineNum = line.lineNumber;
- if (!lineText.startsWith('@')) {
+ private calculateFoldingRanges() {
+ this.commentRange = undefined;
+ 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('@')) {
continue;
}
- if (this.processComment(lineText, lineNum)) {
+ if (this.processComment(line, idx)) {
continue;
}
- this.processBlock(lineText, lineNum);
+ // Process block.
+ if (line.startsWith('end ', 1)) {
+ closingBlocks.push({ name: line.substring(5), line: idx });
+ } else {
+ const closingBlock = closingBlocks.pop();
+ if (closingBlock === undefined) {
+ return;
+ }
+ if (line.substring(1, closingBlock.name.length + 2).trim() === closingBlock.name) {
+ this.insertRange(idx, closingBlock.line);
+ } else {
+ closingBlocks.push(closingBlock);
+ }
+ }
}
if (this.commentRange !== undefined) {
- if (this.commentRange[1] - this.commentRange[0] > 1) {
- this.ranges.insert(this.commentRange[0], this.commentRange[1]);
- }
+ this.insertRange(this.commentRange[0], this.commentRange[1], vscode.FoldingRangeKind.Comment);
}
+ return this.bufferedFoldingRanges;
}
private processComment(lineText: string, lineNum: number) {
@@ -130,7 +141,7 @@ export class FoldingRangeContext {
if (this.headerStart === undefined) {
this.headerStart = lineNum;
} else {
- this.ranges.insert(lineNum, this.headerStart);
+ this.insertRange(lineNum, this.headerStart);
this.headerStart = undefined;
}
return true;
@@ -140,7 +151,7 @@ export class FoldingRangeContext {
} else if (this.commentRange[0] - 1 === lineNum) {
this.commentRange[0] = lineNum;
} else {
- this.ranges.insert(this.commentRange[0], this.commentRange[1], vscode.FoldingRangeKind.Comment);
+ this.insertRange(this.commentRange[0], this.commentRange[1], vscode.FoldingRangeKind.Comment);
this.commentRange = [lineNum, lineNum];
}
return true;
@@ -148,109 +159,26 @@ export class FoldingRangeContext {
return false;
}
- private processBlock(lineText: string, lineNum: number) {
- if (lineText.startsWith('end ', 1)) {
- this.closingBlocks.push({ name: lineText.substring(5), line: lineNum });
- } else {
- const closingBlock = this.closingBlocks.pop();
- if (closingBlock === undefined) {
- return;
- }
- if (lineText.substring(1, closingBlock.name.length + 2).trim() === closingBlock.name) {
- this.ranges.insert(lineNum, closingBlock.line);
- } else {
- this.closingBlocks.push(closingBlock);
- }
+ private insertRange(start: number, end: number, kind?: vscode.FoldingRangeKind) {
+ if (this.bufferedFoldingRanges === undefined) {
+ this.bufferedFoldingRanges = [];
}
+ this.bufferedFoldingRanges.push(new vscode.FoldingRange(start, end, kind));
}
+ /**
+ * Update folding range context based on document change event.
+ *
+ * @param events Events describing the changes in the document.
+ */
private update(events: readonly vscode.TextDocumentContentChangeEvent[]) {
- console.log(events);
- }
-}
-
-/**
- * Represents a Texinfo block marked "closing" by `@end` command.
- */
-interface ClosingBlock {
-
- /**
- * The name of the block.
- */
- name: string;
-
- /**
- * The terminating line number of the block.
- */
- line: number;
-}
-
-/**
- * Container which stores multiple ranges.
- *
- * Used for incremental calculation of VSCode folding ranges to prevent full rescan on edit,
- * which could be catastrophic when dealing with large documents.
- */
-class FoldingRangeContainer {
-
- private nodes = [];
-
- private bufferedValues?: vscode.FoldingRange[];
-
- /**
- * Insert a new range to the container.
- *
- * The new range **SHOULD NOT** overlap with existing ranges.
- *
- * @param start Start of range.
- * @param end End of range
- * @param kind Type of VSCode folding range.
- */
- insert(start: number, end: number, kind?: vscode.FoldingRangeKind) {
- if (this.nodes.length < end) {
- this.nodes.push(...Array.from({ length: end - this.nodes.length + 1 }, () => new FoldingRangeNode()));
- }
- this.bufferedValues = undefined;
- this.nodes[start].end = end;
- this.nodes[start].kind = kind;
- this.nodes[end].start = start;
- this.nodes[end].kind = kind;
- }
-
- /**
- * Get VSCode folding ranges from the container.
- */
- get values() {
- if (this.bufferedValues !== undefined) {
- return this.bufferedValues;
- }
- const values = [];
- this.nodes.forEach((node, idx) => {
- if (node.end !== undefined) {
- values.push(new vscode.FoldingRange(idx, node.end, node.kind));
+ 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;
}
- });
- return (this.bufferedValues = values);
+ }
}
}
-
-/**
- * Node of a folding range which represents a line in the document.
- */
-class FoldingRangeNode {
-
- /**
- * Corresponding start node index.
- */
- start?: number;
-
- /**
- * Corresponding end node index.
- */
- end?: number;
-
- /**
- * Type of VSCode folding range.
- */
- kind?: vscode.FoldingRangeKind;
-}