Refactor code so that line length <= 79.

This commit is contained in:
CismonX 2021-05-25 17:00:27 +08:00
parent 4145c8a64f
commit 96ae0b8f24
Signed by: cismonx
GPG Key ID: 3094873E29A482FB
19 changed files with 1432 additions and 489 deletions

View File

@ -81,10 +81,10 @@ developers.
We believe that Texinfo deserves more users, for it is an excellent format for We believe that Texinfo deserves more users, for it is an excellent format for
writing software manuals, as well as other technical materials. We chose Visual writing software manuals, as well as other technical materials. We chose Visual
Studio Code, one of the most popular code editors as of year 2020, and developed Studio Code, one of the most popular code editors as of year 2020, and
this very extension, vscode-texinfo, which provides some useful features for developed this very extension, vscode-texinfo, which provides some useful
Visual Studio Code regarding the Texinfo format, in the hope that more features for Visual Studio Code regarding the Texinfo format, in the hope that
developers can benefit from it. more developers can benefit from it.
vscode-texinfo is free software. You can redistribute it and/or modify it under vscode-texinfo is free software. You can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software the terms of the GNU General Public License as published by the Free Software
@ -100,8 +100,8 @@ version 3 of the License}, or (at your option) any later version.
@section Contributing @section Contributing
This project is hosted on @url{https://sv.gnu.org/p/vscode-texinfo, Savannah}. This project is hosted on @url{https://sv.gnu.org/p/vscode-texinfo, Savannah}.
Any kind of contribution is welcome, including bug reports, patches, and general Any kind of contribution is welcome, including bug reports, patches, and
discussions regarding the usage of vscode-texinfo. general discussions regarding the usage of vscode-texinfo.
Before you submit something, please make sure that you have read this manual, Before you submit something, please make sure that you have read this manual,
and no one else has posted the same content. and no one else has posted the same content.
@ -146,13 +146,15 @@ proprietary Visual Studio Marketplace.
Before you install a @code{.vsix} file downloaded from a third party, Before you install a @code{.vsix} file downloaded from a third party,
you should check whether the file matches a trusted signature: you should check whether the file matches a trusted signature:
@set vsix-name texinfo-@value{VERSION}.vsix
@ifhtml @ifhtml
@example @example
wget -O cismonx.gpg.asc "https://sv.gnu.org/people/viewgpg.php?user_id=214244" wget -O cismonx.gpg.asc "https://sv.gnu.org/people/viewgpg.php?user_id=214244"
gpg --import cismonx.gpg.asc gpg --import cismonx.gpg.asc
wget https://dl.sv.gnu.org/releases/vscode-texinfo/texinfo-0.2.0.vsix.sig.asc wget https://dl.sv.gnu.org/releases/vscode-texinfo/@value{vsix-name}.sig.asc
gpg --verify texinfo-0.2.0.vsix.sig.asc texinfo-0.2.0.vsix gpg --verify @value{vsix-name}.sig.asc @value{vsix-name}
@end example @end example
@end ifhtml @end ifhtml
@ -163,8 +165,8 @@ wget -O cismonx.gpg.asc \
gpg --import cismonx.gpg.asc gpg --import cismonx.gpg.asc
wget "https://dl.sv.gnu.org/releases/vscode-texinfo/ wget "https://dl.sv.gnu.org/releases/vscode-texinfo/
texinfo-0.2.0.vsix.sig.asc" @value{vsix-name}.sig.asc"
gpg --verify texinfo-0.2.0.vsix.sig.asc texinfo-0.2.0.vsix gpg --verify @value{vsix-name}.sig.asc @value{vsix-name}
@end example @end example
@end ifnothtml @end ifnothtml
@ -223,8 +225,8 @@ suffix @code{.texi}, @code{.txi} or @code{.texinfo}, this process should be
automatic. If not, find and click the status bar item with ``Select Language automatic. If not, find and click the status bar item with ``Select Language
Mode'' tooltip, then choose ``Texinfo'' in the menu which just popped up. Mode'' tooltip, then choose ``Texinfo'' in the menu which just popped up.
If syntax highlighting does not look satisfactory, try another color theme where If syntax highlighting does not look satisfactory, try another color theme
keyword/operator colors are distinct. Some good examples are Solarized where keyword/operator colors are distinct. Some good examples are Solarized
Light/Dark, Monokai, etc. Light/Dark, Monokai, etc.
For details about how to @url{@value{vscode-docs-url} For details about how to @url{@value{vscode-docs-url}
@ -265,10 +267,10 @@ code snippets is disabled by default. You can re-enable it on by switching off
@quotation Note @quotation Note
Code completion provided by vscode-texinfo does not recognize much of Texinfo's Code completion provided by vscode-texinfo does not recognize much of Texinfo's
semantics, and completion may appear in contexts where it should not exist. This semantics, and completion may appear in contexts where it should not exist.
is a known bug (which cannot be fixed in near future, unless a This is a known bug (which cannot be fixed in near future, unless a
@url{https://microsoft.github.io/language-server-protocol/, language server} for @url{https://microsoft.github.io/language-server-protocol/, language server}
Texinfo is implemented, which is not trivial). for Texinfo is implemented, which is not trivial).
@end quotation @end quotation
@ -286,14 +288,15 @@ Three types of code blocks can be recognized by vscode-texinfo:
@item Consecutive lines of comments @item Consecutive lines of comments
@end itemize @end itemize
While editing a Texinfo document, you can collapse or expand a code block either While editing a Texinfo document, you can collapse or expand a code block
by clicking the folding icon to the left of the first line of the block, or by either by clicking the folding icon to the left of the first line of the block,
invoking a corresponding command. See the Visual Studio Code User Guide for or by invoking a corresponding command. See the Visual Studio Code User Guide
@url{@value{vscode-docs-url}/editor/codebasics#_folding, details}. for @url{@value{vscode-docs-url}/editor/codebasics#_folding, details}.
@quotation Note @quotation Note
Due to performance issues, the block hierarchy of a Texinfo document is Due to performance issues, the block hierarchy of a Texinfo document is
re-calculated only when total line count changes, or when the document is saved. re-calculated only when total line count changes, or when the document is
saved.
@end quotation @end quotation
@ -314,10 +317,10 @@ See the Visual Studio Code User Guide for more information about
Some more advanced features of vscode-texinfo is available if GNU Texinfo is Some more advanced features of vscode-texinfo is available if GNU Texinfo is
correctly installed and configured on your device. correctly installed and configured on your device.
@url{https://www.gnu.org/software/texinfo, GNU Texinfo} is the official (and the @url{https://www.gnu.org/software/texinfo, GNU Texinfo} is the official (and
only known) full implementation of Texinfo. On most platforms, it can be easily the only known) full implementation of Texinfo. On most platforms, it can be
installed using a package manager. For example, if you're using a Debian-based easily installed using a package manager. For example, if you're using a
GNU/Linux distribution, you can install GNU Texinfo with: Debian-based GNU/Linux distribution, you can install GNU Texinfo with:
@example @example
sudo apt-get install texinfo sudo apt-get install texinfo
@ -329,8 +332,8 @@ plain text, etc.
To specify the location of @code{makeinfo}, edit the configuration item To specify the location of @code{makeinfo}, edit the configuration item
@code{texinfo.makeinfo}. If it's not located in @code{$PATH}, an absolute path @code{texinfo.makeinfo}. If it's not located in @code{$PATH}, an absolute path
should be specified. Also note that the path should not contain any command line should be specified. Also note that the path should not contain any command
arguments. line arguments.
To check whether GNU Texinfo is correctly installed and configured, see To check whether GNU Texinfo is correctly installed and configured, see
@ref{Version Indicator}. @ref{Version Indicator}.
@ -346,20 +349,20 @@ To check whether GNU Texinfo is correctly installed and configured, see
@section Version Indicator @section Version Indicator
The version indicator is a status bar item with text ``GNU Texinfo''. It is The version indicator is a status bar item with text ``GNU Texinfo''. It is
located on the right side of the status bar, which is shown when the active text located on the right side of the status bar, which is shown when the active
editor contains a Texinfo document. text editor contains a Texinfo document.
If you see a @b{check icon} and the version of GNU Texinfo, then If you see a @b{check icon} and the version of GNU Texinfo, then
congratulations, you're all set. If a @b{cross icon} is displayed, it means congratulations, you're all set. If a @b{cross icon} is displayed, it means
that GNU Texinfo is @emph{not} correctly installed and configured. that GNU Texinfo is @emph{not} correctly installed and configured.
If a @b{warning icon} is displayed, it means that the currently installed GNU If a @b{warning icon} is displayed, it means that the currently installed GNU
Texinfo is outdated, or has an unrecognizable version number. In that case, some Texinfo is outdated, or has an unrecognizable version number. In that case,
features may not work as expected. some features may not work as expected.
@quotation Note @quotation Note
The version indicator does not automatically refresh since the activation of the The version indicator does not automatically refresh since the activation of
extension. To manually trigger a refresh, click the status bar item. the extension. To manually trigger a refresh, click the status bar item.
@end quotation @end quotation
@ -371,9 +374,9 @@ to see how the document looks like when displayed online.
In the active text editor which contains a Texinfo document, click the ``Show In the active text editor which contains a Texinfo document, click the ``Show
Preview'' button on the top right of the editor. A webview will be created in a Preview'' button on the top right of the editor. A webview will be created in a
split editor (if not already), and the HTML preview will be displayed there. The split editor (if not already), and the HTML preview will be displayed there.
``Show Preview'' command is also available in command palette, and has a default The ``Show Preview'' command is also available in command palette, and has a
@code{Ctrl+K V} key binding (on GNU/Linux). default @code{Ctrl+K V} key binding (on GNU/Linux).
The HTML used for preview is generated by @code{makeinfo --html --nosplit}, and The HTML used for preview is generated by @code{makeinfo --html --nosplit}, and
Texinfo source is read from disk, instead of taken from a Texinfo source is read from disk, instead of taken from a
@ -402,11 +405,12 @@ means that the preview is being updated.
You can use a custom CSS to make the HTML preview look prettier. To configure You can use a custom CSS to make the HTML preview look prettier. To configure
this, edit the configuration option @code{texinfo.preview.customCSS}. The CSS this, edit the configuration option @code{texinfo.preview.customCSS}. The CSS
file can either be an online or a local (starting with @code{file://}) resource. file can either be an online or a local (starting with @code{file://})
resource.
A good example is @url{https://www.gnu.org/software/gnulib/manual.css}, which is A good example is @url{https://www.gnu.org/software/gnulib/manual.css}, which
popular among manuals of GNU projects. (Note: May require some tinkering when is popular among manuals of GNU projects. (Note: May require some tinkering
used with darker editor themes) when used with darker editor themes)
@node Goto Node @node Goto Node

View File

@ -72,7 +72,7 @@
"max-len": [ "max-len": [
"warn", "warn",
{ {
"code": 120 "code": 79
} }
], ],
"@typescript-eslint/explicit-module-boundary-types": "off", "@typescript-eslint/explicit-module-boundary-types": "off",

View File

@ -25,7 +25,8 @@ import GlobalContext from './global_context';
import { prompt } from './utils/misc'; import { prompt } from './utils/misc';
/** /**
* Manage mappings between Texinfo documents and corresponding document-specific contexts. * Manage mappings between Texinfo documents and corresponding
* document-specific contexts.
*/ */
export default class ContextMapping implements vscode.Disposable { export default class ContextMapping implements vscode.Disposable {
@ -38,30 +39,45 @@ export default class ContextMapping implements vscode.Disposable {
getDocumentContext(document: vscode.TextDocument) { getDocumentContext(document: vscode.TextDocument) {
let documentContext = this._map.get(document); let documentContext = this._map.get(document);
if (documentContext === undefined) { if (documentContext === undefined) {
documentContext = new DocumentContext(this._globalContext, document); documentContext
= new DocumentContext(this._globalContext, document);
this._map.set(document, documentContext); this._map.set(document, documentContext);
} }
return documentContext; return documentContext;
} }
dispose() { dispose() {
this._map.forEach(documentContext => documentContext.getPreview()?.close()); this._map
.forEach(documentContext => documentContext.getPreview()?.close());
} }
constructor(private readonly _globalContext: GlobalContext) { constructor(private readonly _globalContext: GlobalContext) {
_globalContext.subscribe( _globalContext.subscribe(
vscode.commands.registerTextEditorCommand('texinfo.preview.show', this._showPreview.bind(this)), vscode.commands.registerTextEditorCommand(
vscode.commands.registerCommand('texinfo.preview.goto', this._gotoPreview.bind(this)), 'texinfo.preview.show',
vscode.workspace.onDidChangeTextDocument(this._onDocumentUpdate.bind(this)), this._showPreview.bind(this),
vscode.workspace.onDidCloseTextDocument(this._onDocumentClose.bind(this)), ),
vscode.workspace.onDidSaveTextDocument(this._onDocumentSave.bind(this)), vscode.commands.registerCommand(
'texinfo.preview.goto',
this._gotoPreview.bind(this),
),
vscode.workspace.onDidChangeTextDocument(
this._onDocumentUpdate.bind(this),
),
vscode.workspace.onDidCloseTextDocument(
this._onDocumentClose.bind(this),
),
vscode.workspace.onDidSaveTextDocument(
this._onDocumentSave.bind(this),
),
); );
} }
private readonly _map = new Map<vscode.TextDocument, DocumentContext>(); private readonly _map = new Map<vscode.TextDocument, DocumentContext>();
private _tryGetDocumentContext(document: vscode.TextDocument) { private _tryGetDocumentContext(document: vscode.TextDocument) {
return document.languageId === 'texinfo' ? this.getDocumentContext(document) : undefined; return document.languageId === 'texinfo'
? this.getDocumentContext(document) : undefined;
} }
/** /**
@ -100,9 +116,12 @@ export default class ContextMapping implements vscode.Disposable {
*/ */
private async _showPreview(editor: vscode.TextEditor) { private async _showPreview(editor: vscode.TextEditor) {
const document = editor.document; const document = editor.document;
// Only show preview for saved files, as we're not gonna send document content to `makeinfo` via STDIN. // Only show preview for saved files, as we're not gonna send
// document content to `makeinfo` via STDIN.
// Instead, the file will be loaded from disk. // Instead, the file will be loaded from disk.
if (document.isUntitled && !await prompt('Save this document to display preview.', 'Save')) { if (document.isUntitled &&
!await prompt('Save this document to display preview.', 'Save')
) {
return; return;
} }
if (document.isDirty && !await document.save()) { if (document.isDirty && !await document.save()) {

View File

@ -46,7 +46,10 @@ export default class DocumentContext {
this._preview = undefined; this._preview = undefined;
} }
constructor(readonly globalContext: GlobalContext, readonly document: vscode.TextDocument) {} constructor(
readonly globalContext: GlobalContext,
readonly document: vscode.TextDocument,
) {}
private _preview?: PreviewContext; private _preview?: PreviewContext;
} }

View File

@ -55,15 +55,19 @@ export default class DocumentSymbolContext {
} }
} }
function foldingRangeToSymbols(ranges: readonly Optional<FoldingRange>[], start: number, end: number) { function foldingRangeToSymbols(
ranges: readonly Optional<FoldingRange>[],
start: number,
end: number,
) {
const symbols = <vscode.DocumentSymbol[]>[]; const symbols = <vscode.DocumentSymbol[]>[];
for (let idx = start; idx < end; ++idx) { for (let idx = start; idx < end; ++idx) {
const node = ranges[idx]; const node = ranges[idx];
if (node === undefined) continue; if (node === undefined) continue;
const range = lineNumToRange(idx, node.end); const range = lineNumToRange(idx, node.end);
const selectionRange = lineNumToRange(idx); const selectionRange = lineNumToRange(idx);
const symbol = new vscode.DocumentSymbol('@' + node.name, node.detail, vscode.SymbolKind.String, const symbol = new vscode.DocumentSymbol('@' + node.name, node.detail,
range, selectionRange); vscode.SymbolKind.String, range, selectionRange);
symbol.children = foldingRangeToSymbols(ranges, idx + 1, node.end); symbol.children = foldingRangeToSymbols(ranges, idx + 1, node.end);
symbols.push(symbol); symbols.push(symbol);
idx = node.end; idx = node.end;

View File

@ -27,8 +27,8 @@ import DocumentContext from './document';
/** /**
* Stores information about folding ranges for a document. * Stores information about folding ranges for a document.
* *
* Actually, more than folding ranges (e.g. code lens) is handled within this context, so I believe * Actually, more than folding ranges (e.g. code lens) is handled within
* we should use another name... * this context, so perhaps we should use another name...
*/ */
export default class FoldingRangeContext { export default class FoldingRangeContext {
@ -58,7 +58,9 @@ export default class FoldingRangeContext {
const eol = this._document.eol === vscode.EndOfLine.LF ? '\n' : '\r\n'; const eol = this._document.eol === vscode.EndOfLine.LF ? '\n' : '\r\n';
for (const event of events) { for (const event of events) {
// Clear cached folding range when line count changes. // Clear cached folding range when line count changes.
if (event.text.split(eol).length !== 1 || event.range.start.line !== event.range.end.line) { if (event.text.split(eol).length !== 1 ||
event.range.start.line !== event.range.end.line
) {
this._foldingRanges = undefined; this._foldingRanges = undefined;
this._nodes = []; this._nodes = [];
return true; return true;
@ -80,8 +82,10 @@ export default class FoldingRangeContext {
/** /**
* Regex for matching subsection/section/chapter (-like) commands. * Regex for matching subsection/section/chapter (-like) commands.
*/ */
private static readonly _nodeFormat = RegExp('^@(?:(node)|(subsection|unnumberedsubsec|appendixsubsec|subheading)' + private static readonly _nodeFormat = RegExp('^@(?:(node)|' +
'|(section|unnumberedsec|appendixsec|heading)|(chapter|unnumbered|appendix|majorheading|chapheading)) (.*)$'); '(subsection|unnumberedsubsec|appendixsubsec|subheading)|' +
'(section|unnumberedsec|appendixsec|heading)|' +
'(chapter|unnumbered|appendix|majorheading|chapheading)) (.*)$');
private _foldingRanges?: FoldingRange[]; private _foldingRanges?: FoldingRange[];
@ -100,8 +104,14 @@ export default class FoldingRangeContext {
detail?: string, detail?: string,
kind?: vscode.FoldingRangeKind kind?: vscode.FoldingRangeKind
}) { }) {
(this._foldingRanges ??= []) const items = {
.push({ name: extraArgs.name ?? '', detail: extraArgs.detail ?? '', start, end, kind: extraArgs.kind }); name: extraArgs.name ?? '',
detail: extraArgs.detail ?? '',
start,
end,
kind: extraArgs.kind,
};
(this._foldingRanges ??= []).push(items);
} }
/** /**
@ -144,16 +154,19 @@ export default class FoldingRangeContext {
if (!verbatim && this._processNode(line, idx, lastLine)) continue; if (!verbatim && this._processNode(line, idx, lastLine)) continue;
const closingBlock = closingBlocks.pop(); const closingBlock = closingBlocks.pop();
if (closingBlock === undefined) continue; if (closingBlock === undefined) continue;
if (line.substring(1, closingBlock.name.length + 2).trim() === closingBlock.name) { const name = closingBlock.name;
this._addRange(idx, closingBlock.line, { name: closingBlock.name }); if (line.substring(1, name.length + 2).trim() === name) {
// If `verbatim == true` goes here, this line must be the `@verbatim` line. this._addRange(idx, closingBlock.line, { name: name });
// If `verbatim == true` goes here,
// this line must be the `@verbatim` line.
verbatim = false; verbatim = false;
} else { } else {
closingBlocks.push(closingBlock); closingBlocks.push(closingBlock);
} }
} }
if (this._commentRange !== undefined) { if (this._commentRange !== undefined) {
this._addRange(this._commentRange.start, this._commentRange.end, { kind: vscode.FoldingRangeKind.Comment }); this._addRange(this._commentRange.start, this._commentRange.end,
{ kind: vscode.FoldingRangeKind.Comment });
} }
return this._foldingRanges; return this._foldingRanges;
} }
@ -162,7 +175,8 @@ export default class FoldingRangeContext {
this._commentRange = undefined; this._commentRange = undefined;
this._headerStart = undefined; this._headerStart = undefined;
this._nodes = []; this._nodes = [];
this._closingSubsection = this._closingSection = this._closingChapter = undefined; this._closingSubsection = this._closingSection = this._closingChapter
= undefined;
} }
private _getLastTextLine(lineNum: number, limit = 3) { private _getLastTextLine(lineNum: number, limit = 3) {
@ -176,7 +190,7 @@ export default class FoldingRangeContext {
private _processComment(lineText: string, lineNum: number) { private _processComment(lineText: string, lineNum: number) {
if (!lineText.startsWith('@c')) return false; if (!lineText.startsWith('@c')) return false;
if (!lineText.startsWith(' ', 2) && !lineText.startsWith('omment ', 2)) { if (lineText.charAt(2) != ' ' && !lineText.startsWith('omment ', 2)) {
return false; return false;
} }
// Check for opening/closing header. // Check for opening/closing header.
@ -184,7 +198,8 @@ export default class FoldingRangeContext {
if (this._headerStart === undefined) { if (this._headerStart === undefined) {
this._headerStart = lineNum; this._headerStart = lineNum;
} else { } else {
this._addRange(lineNum, this._headerStart, { kind: vscode.FoldingRangeKind.Region }); this._addRange(lineNum, this._headerStart,
{ kind: vscode.FoldingRangeKind.Region });
this._headerStart = undefined; this._headerStart = undefined;
} }
return true; return true;
@ -194,13 +209,18 @@ export default class FoldingRangeContext {
} else if (this._commentRange.start - 1 === lineNum) { } else if (this._commentRange.start - 1 === lineNum) {
this._commentRange.start = lineNum; this._commentRange.start = lineNum;
} else { } else {
this._addRange(this._commentRange.start, this._commentRange.end, { kind: vscode.FoldingRangeKind.Comment }); this._addRange(this._commentRange.start, this._commentRange.end,
{ kind: vscode.FoldingRangeKind.Comment });
this._commentRange = undefined; this._commentRange = undefined;
} }
return true; return true;
} }
private _processNode(lineText: string, lineNum: number, lastLineNum: number) { private _processNode(
lineText: string,
lineNum: number,
lastLineNum: number,
) {
const result = lineText.match(FoldingRangeContext._nodeFormat); const result = lineText.match(FoldingRangeContext._nodeFormat);
if (result === null) return false; if (result === null) return false;
// Node identifier. // Node identifier.
@ -214,20 +234,25 @@ export default class FoldingRangeContext {
} }
// Subsection level node. // Subsection level node.
if (result[2] !== undefined) { if (result[2] !== undefined) {
this._addRange(lineNum, this._closingSubsection ?? lastLineNum, { name: result[2], detail: result[5] }); this._addRange(lineNum, this._closingSubsection ?? lastLineNum,
{ name: result[2], detail: result[5] });
this._closingSubsection = this._getLastTextLine(lineNum - 1); this._closingSubsection = this._getLastTextLine(lineNum - 1);
return true; return true;
} }
// Section level node. // Section level node.
if (result[3] !== undefined) { if (result[3] !== undefined) {
this._addRange(lineNum, this._closingSection ?? lastLineNum, { name: result[3], detail: result[5] }); this._addRange(lineNum, this._closingSection ?? lastLineNum,
this._closingSubsection = this._closingSection = this._getLastTextLine(lineNum - 1); { name: result[3], detail: result[5] });
this._closingSubsection = this._closingSection
= this._getLastTextLine(lineNum - 1);
return true; return true;
} }
// Chapter level node. // Chapter level node.
if (result[4] !== undefined) { if (result[4] !== undefined) {
this._addRange(lineNum, this._closingChapter ?? lastLineNum, { name: result[4], detail: result[5] }); this._addRange(lineNum, this._closingChapter ?? lastLineNum,
this._closingSubsection = this._closingSection = this._closingChapter = this._getLastTextLine(lineNum - 1); { name: result[4], detail: result[5] });
this._closingSubsection = this._closingSection
= this._closingChapter = this._getLastTextLine(lineNum - 1);
return true; return true;
} }
return false; return false;

View File

@ -39,7 +39,8 @@ export default class PreviewContext {
} }
goto(nodeName: string) { goto(nodeName: string) {
this._panel.webview.postMessage({ command: 'goto', value: getNodeHtmlRef(nodeName) }); const message = { command: 'goto', value: getNodeHtmlRef(nodeName) };
this._webview.postMessage(message);
} }
show() { show() {
@ -53,11 +54,14 @@ export default class PreviewContext {
} }
this._updating = true; this._updating = true;
this._pendingUpdate = false; this._pendingUpdate = false;
// Inform the user that the preview is updating if `makeinfo` takes too long. // Inform the user that the preview is updating, when `makeinfo`
// takes too long.
setTimeout(() => this._updating && this._updateTitle(), 500); setTimeout(() => this._updating && this._updateTitle(), 500);
const initFile = this._globalContext.extensionPath + '/ext/html-preview.pm'; const initFile = this._globalContext.path + '/ext/html-preview.pm';
const converter = new Converter(this._document.fileName, initFile, this._globalContext.options, this._logger); const converter = new Converter(this._document.fileName, initFile,
const { data, error } = await converter.toHTML(path => this._panel.webview.asWebviewUri(path), this._script); this._globalContext.options, this._logger);
const { data, error } = await converter
.toHTML(path => this._webview.asWebviewUri(path), this._script);
if (error) { if (error) {
this._logger.log(error); this._logger.log(error);
this._diagnosis.update(this._document, error); this._diagnosis.update(this._document, error);
@ -65,10 +69,11 @@ export default class PreviewContext {
this._diagnosis.delete(this._document); this._diagnosis.delete(this._document);
} }
if (data === undefined) { if (data === undefined) {
prompt(`Failed to show preview for ${this._document.fileName}.`, 'Show log', true) prompt(`Failed to show preview for ${this._document.fileName}.`,
'Show log', true)
.then(result => result && this._logger.show()); .then(result => result && this._logger.show());
} else { } else {
this._panel.webview.html = data; this._webview.html = data;
} }
this._updating = false; this._updating = false;
this._updateTitle(); this._updateTitle();
@ -76,8 +81,14 @@ export default class PreviewContext {
} }
constructor(private readonly _documentContext: DocumentContext) { constructor(private readonly _documentContext: DocumentContext) {
this._panel = vscode.window.createWebviewPanel('texinfo.preview', '', vscode.ViewColumn.Beside, const options = {
{ enableFindWidget: true, retainContextWhenHidden: true, enableScripts: true }); enableFindWidget: true,
retainContextWhenHidden: true,
enableScripts: true,
};
this._panel = vscode.window.createWebviewPanel('texinfo.preview', '',
vscode.ViewColumn.Beside, options);
this._webview = this._panel.webview;
this._disposables.push(this._panel.onDidDispose(() => this.close())); this._disposables.push(this._panel.onDidDispose(() => this.close()));
this._updateTitle(); this._updateTitle();
this.updateWebview(); this.updateWebview();
@ -91,6 +102,7 @@ export default class PreviewContext {
private readonly _disposables = <vscode.Disposable[]>[]; private readonly _disposables = <vscode.Disposable[]>[];
private readonly _panel: vscode.WebviewPanel; private readonly _panel: vscode.WebviewPanel;
private readonly _webview: vscode.Webview;
/** /**
* Whether a preview update request is pending. * Whether a preview update request is pending.
@ -103,7 +115,8 @@ export default class PreviewContext {
private _updating = false; private _updating = false;
/** /**
* Generate script used for jumping to the corresponding location of preview with code lens. * Generate script used for jumping to the corresponding location of
* preview with code lens.
*/ */
private get _script() { private get _script() {
if (!this._globalContext.options.enableCodeLens) return undefined; if (!this._globalContext.options.enableCodeLens) return undefined;

View File

@ -47,7 +47,7 @@ export default class Diagnosis implements vscode.Disposable {
const fileName = document.uri.path; const fileName = document.uri.path;
const diagnostics = logText.split('\n') const diagnostics = logText.split('\n')
.filter(line => line.startsWith(fileName)) .filter(line => line.startsWith(fileName))
.map(line => logLineToDiagnostic(line.substring(fileName.length + 1))) .map(line => logToDiagnostic(line.substring(fileName.length + 1)))
.filter(isDefined); .filter(isDefined);
this._diagnostics.set(document.uri, diagnostics); this._diagnostics.set(document.uri, diagnostics);
} }
@ -56,7 +56,8 @@ export default class Diagnosis implements vscode.Disposable {
this._diagnostics.dispose(); this._diagnostics.dispose();
} }
private readonly _diagnostics = vscode.languages.createDiagnosticCollection('texinfo'); private readonly _diagnostics
= vscode.languages.createDiagnosticCollection('texinfo');
} }
/** /**
@ -65,11 +66,12 @@ export default class Diagnosis implements vscode.Disposable {
* @param lineText * @param lineText
* @returns * @returns
*/ */
function logLineToDiagnostic(lineText: string) { function logToDiagnostic(lineText: string) {
const lineNum = parseInt(lineText) - 1; const lineNum = parseInt(lineText) - 1;
// Ignore error that does not correspond a line in document. // Ignore error that does not correspond a line in document.
if (isNaN(lineNum)) return undefined; if (isNaN(lineNum)) return undefined;
const message = lineText.substring(lineNum.toString().length + 2); const message = lineText.substring(lineNum.toString().length + 2);
const severity = message.startsWith('warning:') ? vscode.DiagnosticSeverity.Warning : undefined; const severity = message.startsWith('warning:')
? vscode.DiagnosticSeverity.Warning : undefined;
return new vscode.Diagnostic(lineNumToRange(lineNum), message, severity); return new vscode.Diagnostic(lineNumToRange(lineNum), message, severity);
} }

View File

@ -40,10 +40,12 @@ export default class GlobalContext {
readonly indicator = new Indicator(this); readonly indicator = new Indicator(this);
readonly logger = new Logger; readonly logger = new Logger;
readonly extensionPath = this.context.extensionPath; readonly path = this.context.extensionPath;
/** /**
* Note: `Options`' no singleton. Do not wire directly, always use `globalContext.options` instead. * Note: `Options`' no singleton.
*
* Do not wire directly, always use `globalContext.options` instead.
*/ */
get options() { get options() {
return this._options ??= new Options; return this._options ??= new Options;
@ -54,12 +56,18 @@ export default class GlobalContext {
} }
constructor(private readonly context: vscode.ExtensionContext) { constructor(private readonly context: vscode.ExtensionContext) {
this.subscribe(this.contextMapping, this.diagnosis, this.indicator, this.logger, this.subscribe(
vscode.languages.registerCodeLensProvider('texinfo', new CodeLensProvider(this)), this.contextMapping, this.diagnosis, this.indicator, this.logger,
vscode.languages.registerCompletionItemProvider('texinfo', new CompletionItemProvider(this), '@'), vscode.languages.registerCodeLensProvider('texinfo',
vscode.languages.registerDocumentSymbolProvider('texinfo', new DocumentSymbolProvider(this)), new CodeLensProvider(this)),
vscode.languages.registerFoldingRangeProvider('texinfo', new FoldingRangeProvider(this)), vscode.languages.registerCompletionItemProvider('texinfo',
vscode.workspace.onDidChangeConfiguration(() => this._options = undefined), new CompletionItemProvider(this), '@'),
vscode.languages.registerDocumentSymbolProvider('texinfo',
new DocumentSymbolProvider(this)),
vscode.languages.registerFoldingRangeProvider('texinfo',
new FoldingRangeProvider(this)),
vscode.workspace.onDidChangeConfiguration(
() => this._options = undefined),
); );
} }

View File

@ -38,15 +38,19 @@ export default class Indicator implements vscode.Disposable {
constructor(private readonly globalContext: GlobalContext) { constructor(private readonly globalContext: GlobalContext) {
globalContext.subscribe( globalContext.subscribe(
vscode.commands.registerCommand('texinfo.indicator.click', this._click.bind(this)), vscode.commands.registerCommand('texinfo.indicator.click',
vscode.window.onDidChangeActiveTextEditor(this._refresh.bind(this)), this._click.bind(this)),
vscode.window.onDidChangeActiveTextEditor(
this._refresh.bind(this)),
); );
this._updateStatus().then(() => this._refresh(vscode.window.activeTextEditor)); this._updateStatus()
.then(() => this._refresh(vscode.window.activeTextEditor));
} }
private _canDisplayPreview = false; private _canDisplayPreview = false;
private readonly _statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 100); private readonly _statusBarItem = vscode.window.createStatusBarItem(
vscode.StatusBarAlignment.Right, 100);
/** /**
* Calls when the status bar item is clicked. * Calls when the status bar item is clicked.
@ -70,18 +74,21 @@ export default class Indicator implements vscode.Disposable {
} }
/** /**
* Update the installation status of GNU Texinfo, by checking `makeinfo --version`. * Update the installation status of GNU Texinfo,
* by checking `makeinfo --version`.
*/ */
private async _updateStatus() { private async _updateStatus() {
const options = this.globalContext.options; const options = this.globalContext.options;
const output = await exec(options.makeinfo, ['--version'], options.maxSize); const output = await exec(options.makeinfo, ['--version'],
options.maxSize);
const result = output.data?.match(/\(GNU texinfo\) (.*)\n/); const result = output.data?.match(/\(GNU texinfo\) (.*)\n/);
let tooltip = '', icon: string, version = ''; let tooltip = '', icon: string, version = '';
if (result && result[1]) { if (result && result[1]) {
version = result[1]; version = result[1];
if (!isNaN(+version) && +version < 6.7) { if (!isNaN(+version) && +version < 6.7) {
icon = '$(warning)'; icon = '$(warning)';
tooltip = `GNU Texinfo (${options.makeinfo}) is outdated (${version} < 6.7).`; tooltip = `GNU Texinfo (${options.makeinfo}) ` +
`is outdated (${version} < 6.7).`;
} else { } else {
// Unrecognizable version. Assume it is okay. // Unrecognizable version. Assume it is okay.
icon = '$(check)'; icon = '$(check)';
@ -89,7 +96,8 @@ export default class Indicator implements vscode.Disposable {
this._canDisplayPreview = true; this._canDisplayPreview = true;
} else { } else {
icon = '$(close)'; icon = '$(close)';
tooltip = `GNU Texinfo (${options.makeinfo}) is not correctly installed or configured.`; tooltip = `GNU Texinfo (${options.makeinfo}) ` +
`is not correctly installed or configured.`;
this._canDisplayPreview = false; this._canDisplayPreview = false;
} }
this._statusBarItem.command = 'texinfo.indicator.click'; this._statusBarItem.command = 'texinfo.indicator.click';

View File

@ -27,7 +27,8 @@ import * as vscode from 'vscode';
export default class Logger implements vscode.Disposable { export default class Logger implements vscode.Disposable {
log(message: string) { log(message: string) {
const dateTime = new Date().toLocaleString(undefined, { hour12: false }); const dateTime = new Date()
.toLocaleString(undefined, { hour12: false });
this._outputChannel.appendLine(`[ ${dateTime} ]\n${message}`); this._outputChannel.appendLine(`[ ${dateTime} ]\n${message}`);
} }
@ -39,5 +40,6 @@ export default class Logger implements vscode.Disposable {
this._outputChannel.dispose(); this._outputChannel.dispose();
} }
private readonly _outputChannel = vscode.window.createOutputChannel('Texinfo'); private readonly _outputChannel
= vscode.window.createOutputChannel('Texinfo');
} }

View File

@ -80,7 +80,8 @@ export default class Options {
return this._getArray('preview.variables'); return this._getArray('preview.variables');
} }
private readonly _configuration = vscode.workspace.getConfiguration('texinfo'); private readonly _configuration
= vscode.workspace.getConfiguration('texinfo');
private _getArray(section: string): readonly string[] { private _getArray(section: string): readonly string[] {
return this._configuration.get(section, []); return this._configuration.get(section, []);

View File

@ -30,7 +30,8 @@ export default class CodeLensProvider implements vscode.CodeLensProvider {
provideCodeLenses(document: vscode.TextDocument) { provideCodeLenses(document: vscode.TextDocument) {
if (!this._globalContext.options.enableCodeLens) return undefined; if (!this._globalContext.options.enableCodeLens) return undefined;
if (!this._globalContext.indicator.canDisplayPreview) return undefined; if (!this._globalContext.indicator.canDisplayPreview) return undefined;
return this._globalContext.contextMapping.getDocumentContext(document).foldingRange.nodeValues; return this._globalContext.contextMapping.getDocumentContext(document)
.foldingRange.nodeValues;
} }
constructor(private readonly _globalContext: GlobalContext) {} constructor(private readonly _globalContext: GlobalContext) {}

File diff suppressed because it is too large Load Diff

View File

@ -25,10 +25,12 @@ import GlobalContext from '../global_context';
/** /**
* Provide document symbol information for Texinfo documents. * Provide document symbol information for Texinfo documents.
*/ */
export default class DocumentSymbolProvider implements vscode.DocumentSymbolProvider { export default class DocumentSymbolProvider
implements vscode.DocumentSymbolProvider
{
provideDocumentSymbols(document: vscode.TextDocument) { provideDocumentSymbols(document: vscode.TextDocument) {
return this._globalContext.contextMapping.getDocumentContext(document).documentSymbol.documentSymbols; return this._globalContext.contextMapping.getDocumentContext(document)
.documentSymbol.documentSymbols;
} }
constructor(private readonly _globalContext: GlobalContext) {} constructor(private readonly _globalContext: GlobalContext) {}

View File

@ -25,10 +25,12 @@ import GlobalContext from '../global_context';
/** /**
* Provide folding range info for Texinfo documents. * Provide folding range info for Texinfo documents.
*/ */
export default class FoldingRangeProvider implements vscode.FoldingRangeProvider { export default class FoldingRangeProvider
implements vscode.FoldingRangeProvider
{
provideFoldingRanges(document: vscode.TextDocument) { provideFoldingRanges(document: vscode.TextDocument) {
return this._globalContext.contextMapping.getDocumentContext(document).foldingRange.foldingRanges; return this._globalContext.contextMapping.getDocumentContext(document)
.foldingRange.foldingRanges;
} }
constructor(private readonly _globalContext: GlobalContext) {} constructor(private readonly _globalContext: GlobalContext) {}

View File

@ -32,18 +32,25 @@ import { Operator } from './types';
export default class Converter { export default class Converter {
async toHTML(imgTransformer: Operator<vscode.Uri>, insertScript?: string) { async toHTML(imgTransformer: Operator<vscode.Uri>, insertScript?: string) {
const newPath = imgTransformer(vscode.Uri.file(path.dirname(this._path))).toString() + '/'; const pathUri = vscode.Uri.file(path.dirname(this._path));
const options = ['-o-', '--no-split', '--html', `--error-limit=${this._options.errorLimit}`, const newPath = imgTransformer(pathUri).toString() + '/';
`--init-file=${this._initFile}`, '-D', `__vscode_texinfo_image_uri_base ${newPath}`]; const options = ['-o-', '--no-split', '--html',
`--error-limit=${this._options.errorLimit}`,
`--init-file=${this._initFile}`,
'-D', `__vscode_texinfo_image_uri_base ${newPath}`,
];
this._options.noHeaders && options.push('--no-headers'); this._options.noHeaders && options.push('--no-headers');
this._options.noNumberSections && options.push('--no-number-sections'); this._options.noNumberSections && options.push('--no-number-sections');
this._options.noValidation && options.push('--no-validate'); this._options.noValidation && options.push('--no-validate');
this._options.noWarnings && options.push('--no-warn'); this._options.noWarnings && options.push('--no-warn');
insertScript !== undefined && options.push('-c', `EXTRA_HEAD <script>${insertScript}</script>`); if (insertScript !== undefined) {
options.push('-c', `EXTRA_HEAD <script>${insertScript}</script>`);
}
this._addIncludePaths(this._options.includePaths, options); this._addIncludePaths(this._options.includePaths, options);
this._defineVariables(this._options.variables, options); this._defineVariables(this._options.variables, options);
this._includeCustomCSS(this._options.customCSS, options); this._includeCustomCSS(this._options.customCSS, options);
return await exec(this._options.makeinfo, options.concat(this._path), this._options.maxSize); return await exec(this._options.makeinfo, options.concat(this._path),
this._options.maxSize);
} }
constructor( constructor(
@ -78,7 +85,8 @@ export default class Converter {
throw URIError; throw URIError;
} }
} catch (e) { } catch (e) {
this._logger.log(`Cannot load custom CSS. Invalid URI: '${cssFileURI}'`); this._logger
.log(`Cannot load custom CSS. Invalid URI: '${cssFileURI}'`);
} }
} }
} }

View File

@ -32,9 +32,18 @@ import { ExecResult } from './types';
* @returns The output data, or `undefined` if execution fails. * @returns The output data, or `undefined` if execution fails.
*/ */
export function exec(path: string, args: string[], maxBuffer: number) { export function exec(path: string, args: string[], maxBuffer: number) {
return new Promise<ExecResult>(resolve => child_process.execFile(path, args, return new Promise<ExecResult>(
{ env: { ...process.env, LC_MESSAGES: 'en_US' }, maxBuffer: maxBuffer }, (error, stdout, stderr) => resolve => child_process.execFile(path, args,
resolve(error ? { error: stderr ? stderr : error.message } : { data: stdout, error: stderr }))); {
env: { ...process.env, LC_MESSAGES: 'en_US' },
maxBuffer: maxBuffer,
},
(error, stdout, stderr) => resolve(error
? { error: stderr ? stderr : error.message }
: { data: stdout, error: stderr }
)
)
);
} }
/** /**
@ -46,7 +55,8 @@ export function exec(path: string, args: string[], maxBuffer: number) {
* @returns Whether the user clicked the button. * @returns Whether the user clicked the button.
*/ */
export async function prompt(message: string, label: string, error = false) { export async function prompt(message: string, label: string, error = false) {
const func = error ? vscode.window.showErrorMessage : vscode.window.showInformationMessage; const func = error ?
vscode.window.showErrorMessage : vscode.window.showInformationMessage;
return label === await func(message, label); return label === await func(message, label);
} }
@ -68,7 +78,8 @@ export function lineNumToRange(startLine: number, endLine = startLine) {
* @param charCode ASCII code of character. * @param charCode ASCII code of character.
*/ */
export function isAlpha(charCode: number) { export function isAlpha(charCode: number) {
return charCode >= 97 && charCode <= 122 || charCode >= 65 && charCode <= 90; return (charCode >= 97 && charCode <= 122)
|| (charCode >= 65 && charCode <= 90);
} }
/** /**
@ -77,16 +88,17 @@ export function isAlpha(charCode: number) {
* @param charCode ASCII code of character. * @param charCode ASCII code of character.
*/ */
export function isAlnum(charCode: number) { export function isAlnum(charCode: number) {
return isAlpha(charCode) || charCode >= 48 && charCode <= 57; return isAlpha(charCode) || (charCode >= 48 && charCode <= 57);
} }
/** /**
* Get corresponding HTML cross-reference name by node name. * Get corresponding HTML cross-reference name by node name.
* *
* See section *HTML Cross-reference Node Name Expansion* in the Texinfo manual. * See section *HTML Cross-reference Node Name Expansion* in
* the Texinfo manual.
* *
* TODO: Node name is not displayed verbatim, leading to wrong HTML xref when containing commands. * TODO: Node name is not displayed verbatim, leading to wrong HTML xref when
* Fix this when migrating to LSP. * containing commands. Fix this when migrating to LSP.
* *
* @param nodeName * @param nodeName
*/ */
@ -100,5 +112,6 @@ export function getNodeHtmlRef(nodeName: string) {
.join('')) .join(''))
.join('-'); .join('-');
const firstCharCode = result.charCodeAt(0); const firstCharCode = result.charCodeAt(0);
return isAlpha(firstCharCode) ? result : 'g_t_00' + firstCharCode.toString(16) + result.substring(1); return isAlpha(firstCharCode) ? result : 'g_t_00'
+ firstCharCode.toString(16) + result.substring(1);
} }

View File

@ -21,19 +21,33 @@
import * as vscode from 'vscode'; import * as vscode from 'vscode';
export type CompletionItem = vscode.CompletionItem & { snippet?: boolean }; export type CompletionItem = vscode.CompletionItem & {
snippet?: boolean,
};
export type ExecResult = { data?: string, error: string }; export type ExecResult = {
data?: string,
error: string,
};
export type FoldingRange = vscode.FoldingRange & { name: string, detail: string }; export type FoldingRange = vscode.FoldingRange & {
name: string,
detail: string,
};
export type NamedLine = { name: string, line: number }; export type NamedLine = {
name: string,
line: number,
};
export type Operator<T> = (arg: T) => T; export type Operator<T> = (arg: T) => T;
export type Optional<T> = T | undefined; export type Optional<T> = T | undefined;
export type Range = { start: number, end: number }; export type Range = {
start: number,
end: number,
};
export function isDefined<T>(value: Optional<T>): value is T { export function isDefined<T>(value: Optional<T>): value is T {
return value !== undefined; return value !== undefined;