Compare commits

...

35 Commits

Author SHA1 Message Date
CismonX ed968839b6
chore: fix typos
ci/woodpecker/push/woodpecker Pipeline was successful Details
2024-04-20 08:34:46 +08:00
CismonX f8bcf86fb1
chore: prepare for v0.3.0
ci/woodpecker/push/woodpecker Pipeline was successful Details
ci/woodpecker/tag/woodpecker Pipeline was successful Details
2024-04-04 08:46:50 +08:00
CismonX 8f289fbcb8
chore: update docs
ci/woodpecker/push/woodpecker Pipeline was successful Details
2024-04-04 08:13:38 +08:00
CismonX afe3694a88
refactor: underscore-prefix kwargs
ci/woodpecker/push/woodpecker Pipeline was successful Details
so that they could be mangled by terser, producing shorter output
2024-04-04 07:01:37 +08:00
CismonX 530902128c
feat: support GNU Texinfo 7.1
* update version indicator
* add completion for several @-commands
2024-04-04 07:01:26 +08:00
CismonX 1294764698
chore: switch to woodpecker ci
and remove build status badge from README.md
2024-04-02 15:33:03 +08:00
CismonX 9de1ba26b0
chore: release logo into public domain 2024-04-02 14:31:11 +08:00
CismonX 13feb8a1f4
refactor: improve build scripts 2024-04-02 14:12:03 +08:00
CismonX ad685014d2
feat: support makeinfo customization variables 2024-04-02 13:38:42 +08:00
CismonX 3f8c23d917
chore: target ES2021
Since we're now using Node.js >= 18, it is safe to target ES2021.

With features like optional chaining, null coalescing (assignment),
the compiler could emit shorter code, leading to smaller package size.
2024-04-02 08:51:01 +08:00
CismonX 3b3478decb
chore: update dependencies 2024-04-02 08:05:38 +08:00
CismonX a5837defce
fix link
continuous-integration/drone/push Build is passing Details
continuous-integration/drone Build is passing Details
2022-02-10 02:49:27 +08:00
CismonX 5e86022e32
fix link; update gitignore
continuous-integration/drone/push Build is passing Details
2022-02-10 02:46:03 +08:00
CismonX 229d84bd2d
bugfix: snippet completion for block commands
continuous-integration/drone/push Build is passing Details
2022-02-02 14:53:46 +08:00
CismonX c3e33c3340
Update dependencies 2022-02-02 14:32:45 +08:00
CismonX 2275b94c1d
Prepare for v0.2.3
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details
2021-10-23 21:52:58 +08:00
CismonX 7820db6955
Fix locale.
continuous-integration/drone/push Build is passing Details
2021-10-23 02:58:40 +08:00
CismonX 26c8d32129
Refactor code.
continuous-integration/drone/push Build is passing Details
2021-10-23 02:36:51 +08:00
CismonX c111ec89a0
Normalize paths. 2021-10-23 02:35:49 +08:00
CismonX 3896bf44db
Update dependencies. 2021-10-23 02:31:15 +08:00
CismonX d165537d60
Prepare for v0.2.2
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details
2021-10-02 23:35:17 +08:00
CismonX 1707ece4bc
GNU Texinfo latest version: 6.7 -> 6.8 2021-10-02 23:15:57 +08:00
CismonX f5c8ae309c
Bugfix for Windows builds. 2021-10-02 22:56:50 +08:00
CismonX 2fd2a025c3
Update build scripts.
continuous-integration/drone/push Build is passing Details
2021-10-02 01:30:37 +08:00
CismonX 84a5c506fe
Update dependencies. 2021-10-02 00:47:49 +08:00
CismonX 2004131b6c
Update dependencies.
continuous-integration/drone/push Build is passing Details
2021-09-10 11:41:02 +08:00
CismonX 3ecb2a5b03
Refactor code.
continuous-integration/drone/push Build is passing Details
2021-08-12 01:23:04 +08:00
CismonX b3b9644af0
Update dependencies 2021-08-12 01:18:38 +08:00
CismonX 1b80cb315d
Update README.
continuous-integration/drone/push Build is passing Details
2021-07-25 18:08:35 +08:00
CismonX c0f0fd2ea5
Update dependencies. 2021-07-25 18:07:01 +08:00
CismonX 829a35679e
Update CI script (update node to 14.17).
continuous-integration/drone/push Build is passing Details
2021-05-25 17:01:32 +08:00
CismonX 96ae0b8f24
Refactor code so that line length <= 79. 2021-05-25 17:00:27 +08:00
CismonX 4145c8a64f
Update dependencies. 2021-05-25 12:20:19 +08:00
CismonX 6cff7138f0
Optimize build configuration. 2021-05-25 12:17:36 +08:00
CismonX 1dbafb6c97
Rename private properties. 2021-05-25 12:16:56 +08:00
30 changed files with 5606 additions and 2960 deletions

1
.gitignore vendored
View File

@ -6,6 +6,7 @@
# this notice are preserved. This file is offered as-is, without any warranty. # this notice are preserved. This file is offered as-is, without any warranty.
# #
*.asc
*.vsix *.vsix
.vscode/ .vscode/
node_modules/ node_modules/

View File

@ -8,7 +8,7 @@
**/*.map **/*.map
**/*.ts **/*.ts
.drone.yml .woodpecker.yml
.gitignore .gitignore
.gitattributes .gitattributes
.travis.yml .travis.yml

View File

@ -6,12 +6,16 @@
# this notice are preserved. This file is offered as-is, without any warranty. # this notice are preserved. This file is offered as-is, without any warranty.
# #
kind: pipeline # For history build logs,
type: docker # see <https://ci.cismon.net/repos/cismonx/vscode-texinfo>.
name: default
steps: steps:
- name: build - name: build
image: node:14.16 image: node:18-alpine
when:
- event: [push, tag, manual]
branch: primary
commands: commands:
- npm --unsafe-perm ci - apk add --no-cache perl unzip
- npm ci
- npm run package - npm run package

View File

@ -1,13 +1,31 @@
<!-- <!--
Copyright (C) 2020,2021 CismonX <admin@cismon.net> Copyright (C) 2020,2021,2022,2024 CismonX <admin@cismon.net>
Copying and distribution of this file, with or without modification, are Copying and distribution of this file, with or without modification, are
permitted in any medium without royalty, provided the copyright notice and permitted in any medium without royalty, provided the copyright notice and
this notice are preserved. This file is offered as-is, without any warranty. this notice are preserved. This file is offered as-is, without any warranty.
--> -->
# Changelog # Changelog
## v0.3.0 - 04/04/2024
* Add configuration for customization variables (`makeinfo -c KEY=VAL`).
* Support GNU Texinfo 7.1.
## v0.2.4 - 02/02/2022
* Fix a bug which breaks the snippet completion of block commands.
## v0.2.3 - 10/23/2021
* Fix a bug which prevents diagnostic info from displaying correctly in GNU Texinfo 6.8, and on Windows platform.
## v0.2.2 - 10/03/2021
* Fix a bug which breaks configuration `preview.includePaths` on Windows.
* Remove completion for commands which are deprecated in GNU Texinfo 6.8.
## v0.2.1 - 05/05/2021 ## v0.2.1 - 05/05/2021
* Fix an error in build script which produces corrupted metadata in `.vsix` package. * Fix an error in build script which produces corrupted metadata in `.vsix` package.

View File

@ -1,9 +1,9 @@
<!-- <!--
Copyright (C) 2021 CismonX <admin@cismon.net> Copyright (C) 2021 CismonX <admin@cismon.net>
Copying and distribution of this file, with or without modification, are Copying and distribution of this file, with or without modification, are
permitted in any medium without royalty, provided the copyright notice and permitted in any medium without royalty, provided the copyright notice and
this notice are preserved. This file is offered as-is, without any warranty. this notice are preserved. This file is offered as-is, without any warranty.
--> -->
# License Notice # License Notice
@ -13,34 +13,32 @@ this notice are preserved. This file is offered as-is, without any warranty.
Project files listed below cannot carry a license notice by themselves, due to Project files listed below cannot carry a license notice by themselves, due to
file format restrictions. file format restrictions.
```text assets/texinfo.png
assets/texinfo.png
```
They should be treated as if they each contains the following text: This file is released into the public domain using [CC0].
```text
Copyright (C) 2020,2021 CismonX <admin@cismon.net>
Copying and distribution of this file, with or without modification, are
permitted in any medium without royalty, provided the copyright notice and
this notice are preserved. This file is offered as-is, without any warranty.
```
## Files from other projects ## Files from other projects
Source code from the projects listed below are **not** part of vscode-texinfo. Source code from the projects listed below are *not* part of vscode-texinfo.
However, when building the project, they are downloaded, compiled, and packaged However, when building the project, they are downloaded, compiled, and packaged
into a single binary file alongside with vscode-texinfo. into a single binary file alongside with vscode-texinfo.
| Project | Copyright Holder | License | | Project | Copyright Holder | License |
| - | - | - | | - | - | - |
| [Texinfo syntax highlighting](https://github.com/Alhadis/language-texinfo) | John Gardner | [ISC](https://github.com/Alhadis/language-texinfo/blob/master/LICENSE.md) | | [Texinfo syntax highlighting] | John Gardner | [ISC](https://github.com/Alhadis/language-texinfo/blob/master/LICENSE.md) |
The following projects are required during runtime of vscode-texinfo, but as The following projects are required during runtime of vscode-texinfo, but as
separate programs. separate programs.
| Project | Copyright Holder | License | | Project | Copyright Holder | License |
| - | - | - | | - | - | - |
| [Visual Studio Code](https://github.com/microsoft/vscode) | Microsoft Corporation | [MIT](https://github.com/microsoft/vscode/blob/main/LICENSE.txt) | | [Visual Studio Code] | Microsoft Corporation | [MIT](https://github.com/microsoft/vscode/blob/main/LICENSE.txt) |
| [GNU Texinfo](https://www.gnu.org/software/texinfo) | Free Software Foundation | [GPL-3.0](https://git.savannah.gnu.org/cgit/texinfo.git/tree/COPYING)-or-later | | [GNU Texinfo] | Free Software Foundation | [GPL-3.0](https://git.savannah.gnu.org/cgit/texinfo.git/tree/COPYING)-or-later |
<!-- Reference Links -->
[CC0]: https://creativecommons.org/public-domain/cc0/
[Texinfo syntax highlighting]: https://github.com/Alhadis/language-texinfo
[Visual Studio Code]: https://github.com/microsoft/vscode
[GNU Texinfo]: https://www.gnu.org/software/texinfo

View File

@ -1,21 +1,20 @@
<!-- <!--
Copyright (C) 2020,2021 CismonX <admin@cismon.net> Copyright (C) 2020,2021,2024 CismonX <admin@cismon.net>
Copying and distribution of this file, with or without modification, are Copying and distribution of this file, with or without modification, are
permitted in any medium without royalty, provided the copyright notice and permitted in any medium without royalty, provided the copyright notice and
this notice are preserved. This file is offered as-is, without any warranty. this notice are preserved. This file is offered as-is, without any warranty.
--> -->
# vscode-texinfo # vscode-texinfo
[![Build Status](https://shields.io/drone/build/CismonX/vscode-texinfo?server=https%3A%2F%2Fdrone.cismon.net)](https://drone.cismon.net/CismonX/vscode-texinfo) [![License]](LICENSE)
[![License](https://img.shields.io/badge/license-GPL--3.0--or--later-blue.svg)](LICENSE)
## About ## About
vscode-texinfo is an extension of Visual Studio Code which aims at improving user experience for editing Texinfo documents. vscode-texinfo is an extension of [Visual Studio Code] which provides the
following features for editing [Texinfo] documents:
Major features include:
* Syntax Highlighting * Syntax Highlighting
* Code Completion * Code Completion
* HTML Preview * HTML Preview
@ -25,4 +24,12 @@ Major features include:
## Getting Started ## Getting Started
For instructions about how to install, use, and contribute to vscode-texinfo, see the [online user manual](https://nongnu.org/vscode-texinfo/manual). For instructions on how to install, use, and contribute to vscode-texinfo,
see the [user manual].
<!-- Reference Links -->
[Visual Studio Code]: https://github.com/microsoft/vscode
[Texinfo]: https://www.gnu.org/software/texinfo/
[License]: https://img.shields.io/badge/license-GPL--3.0--or--later-blue.svg
[user manual]: https://nongnu.org/vscode-texinfo/manual

View File

@ -9,6 +9,8 @@
@set vscode-docs-url https://code.visualstudio.com/docs @set vscode-docs-url https://code.visualstudio.com/docs
@set vscode-api-url https://code.visualstudio.com/api @set vscode-api-url https://code.visualstudio.com/api
@set gnu-texinfo-docs https://www.gnu.org/software/texinfo/manual/texinfo @set gnu-texinfo-docs https://www.gnu.org/software/texinfo/manual/texinfo
@set sv-home-url https://sv.nongnu.org
@set sv-releases-url https://dl.sv.nongnu.org/releases
@tex @tex
\global\def\linkcolor{0 0 1} \global\def\linkcolor{0 0 1}
@ -20,7 +22,7 @@
This manual is for vscode-texinfo (version @value{VERSION}), an extension of This manual is for vscode-texinfo (version @value{VERSION}), an extension of
Visual Studio Code. Visual Studio Code.
Copyright @copyright{} 2021 CismonX <admin@@cismon.net> Copyright @copyright{} 2021,2024 CismonX <admin@@cismon.net>
@quotation @quotation
This manual is licensed under a This manual is licensed under a
@ -51,7 +53,7 @@ Creative Commons Attribution-ShareAlike 4.0 International License}.
This manual is for vscode-texinfo (version @value{VERSION}), an extension of This manual is for vscode-texinfo (version @value{VERSION}), an extension of
Visual Studio Code. Visual Studio Code.
Copyright @copyright{} 2021 CismonX <admin@@cismon.net> Copyright @copyright{} 2021,2024 CismonX <admin@@cismon.net>
This manual is licensed under a This manual is licensed under a
@url{https://creativecommons.org/licenses/by-sa/4.0/, @url{https://creativecommons.org/licenses/by-sa/4.0/,
@ -72,8 +74,8 @@ Creative Commons Attribution-ShareAlike 4.0 International License}.
@chapter Overview @chapter Overview
Texinfo is a typesetting language designed for writing software manuals. It's Texinfo is a typesetting language designed for writing software manuals. It's
the official documention format for GNU projects, but not as popular in modern the official documentation format for GNU projects, but not as popular in
non-GNU free software projects. modern non-GNU free software projects.
One of the main reasons is the lack of editor support. While Emacs does offer a One of the main reasons is the lack of editor support. While Emacs does offer a
``texinfo-mode'', however, Emacs is not widely used among average software ``texinfo-mode'', however, Emacs is not widely used among average software
@ -81,10 +83,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
@ -99,11 +101,11 @@ version 3 of the License}, or (at your option) any later version.
@node Contributing @node Contributing
@section Contributing @section Contributing
This project is hosted on @url{https://sv.gnu.org/p/vscode-texinfo, Savannah}. This project is hosted on @url{@value{sv-home-url}/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 post 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.
@ -120,7 +122,7 @@ binary release, which is proprietary software. You can also build it from
@quotation Note @quotation Note
Theoretically vscode-texinfo can work with any version of Visual Studio Code Theoretically vscode-texinfo can work with any version of Visual Studio Code
since 1.40, but not all versions are tested. It's recommended to install a since 1.82, but not all versions are tested. It's recommended to install a
latest release. latest release.
@end quotation @end quotation
@ -139,35 +141,21 @@ and you can find and install this extension (Extension ID:
@section Manual Installation @section Manual Installation
You can manually download the @code{.vsix} file, from either Savannah's You can manually download the @code{.vsix} file, from either Savannah's
@url{https://dl.sv.gnu.org/releases/vscode-texinfo/, download area}, @url{@value{sv-releases-url}/vscode-texinfo/, download area},
@url{https://open-vsx.org/extension/CismonX/texinfo, Open VSX Registry}, or the @url{https://open-vsx.org/extension/CismonX/texinfo, Open VSX Registry}, or the
proprietary Visual Studio Marketplace. 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:
@ifhtml @set vsix-name texinfo-@value{VERSION}.vsix
@example @example
wget -O cismonx.gpg.asc "https://sv.gnu.org/people/viewgpg.php?user_id=214244" wget -O- '@value{sv-home-url}/people/viewgpg.php?user_id=214244' \
gpg --import cismonx.gpg.asc | gpg --import
wget -O- @value{sv-releases-url}/vscode-texinfo/@value{vsix-name}.sig.asc \
wget https://dl.sv.gnu.org/releases/vscode-texinfo/texinfo-0.2.0.vsix.sig.asc | gpg --verify - @value{vsix-name}
gpg --verify texinfo-0.2.0.vsix.sig.asc texinfo-0.2.0.vsix
@end example @end example
@end ifhtml
@ifnothtml
@example
wget -O cismonx.gpg.asc \
"https://sv.gnu.org/people/viewgpg.php?user_id=214244"
gpg --import cismonx.gpg.asc
wget "https://dl.sv.gnu.org/releases/vscode-texinfo/
texinfo-0.2.0.vsix.sig.asc"
gpg --verify texinfo-0.2.0.vsix.sig.asc texinfo-0.2.0.vsix
@end example
@end ifnothtml
Finally, install the @code{.vsix} file to Visual Studio Code via command Finally, install the @code{.vsix} file to Visual Studio Code via command
palette: @code{Extensions: Install from VSIX...}. palette: @code{Extensions: Install from VSIX...}.
@ -176,7 +164,7 @@ palette: @code{Extensions: Install from VSIX...}.
@node Build from Source @node Build from Source
@section Build from Source @section Build from Source
You can generate the @code{.vsix} file from the source code of vscode-texinfo. The @code{.vsix} file can be built from the source code of vscode-texinfo.
First, clone the source code repository: First, clone the source code repository:
@ -223,8 +211,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 +253,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 +274,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 +303,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 +318,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 +335,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
@ -367,13 +356,13 @@ extension. To manually trigger a refresh, click the status bar item.
@section HTML Preview @section HTML Preview
You can generate the HTML preview of a Texinfo document in Visual Studio Code, You can generate the HTML preview of a Texinfo document in Visual Studio Code,
to see how the document looks like when displayed online. to see how the document looks like when displayed in a web browser.
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 +391,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
@ -423,7 +413,7 @@ allow @@-commands}, which is not handled in vscode-texinfo due to performance
considerations. For these nodes, this feature does not work. considerations. For these nodes, this feature does not work.
@end quotation @end quotation
To disable this feature and hide the code lenses, switch off the configuation To disable this feature and hide the code lenses, switch off the configuration
item @code{texinfo.enableCodeLens}. item @code{texinfo.enableCodeLens}.
See the Visual Studio Code User Guide for more information about See the Visual Studio Code User Guide for more information about
@ -441,7 +431,7 @@ HTML previews.
can produce before quitting. (@code{--error-limit=NUM}) can produce before quitting. (@code{--error-limit=NUM})
@item @code{texinfo.preview.includePaths}: Array of extra paths to search for @item @code{texinfo.preview.includePaths}: Array of extra paths to search for
@code{@@include} files. (@code{-I PATH}) @code{@@include} files. (@code{-I PATH})
@item @code{texinfo.preview.maxSize}: Max allowed size for the genereated HTML @item @code{texinfo.preview.maxSize}: Max allowed size for the generated HTML
file before it's displayed in the preview. Files larger than this limit will file before it's displayed in the preview. Files larger than this limit will
trigger an error. trigger an error.
@item @code{texinfo.preview.noHeaders}: When enabled, headers and menus are no @item @code{texinfo.preview.noHeaders}: When enabled, headers and menus are no
@ -453,6 +443,8 @@ are not validated. (@code{--no-validate})
@item @code{texinfo.preview.variables}: Array of variables to define (as with @item @code{texinfo.preview.variables}: Array of variables to define (as with
@code{@@set}). If a variable has a value, use the ASCII space character to @code{@@set}). If a variable has a value, use the ASCII space character to
separate key and value. separate key and value.
@item @code{texinfo.preview.customizationVariables}: Array of customization
variables. (@code{-c KEY=VALUE})
@end itemize @end itemize

5729
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"_copyrightNotice": [ "_copyrightNotice": [
"Copyright (C) 2020,2021 CismonX <admin@cismon.net>", "Copyright (C) 2020,2021,2022,2024 CismonX <admin@cismon.net>",
"Copying and distribution of this file, with or without modification,", "Copying and distribution of this file, with or without modification,",
"are permitted in any medium without royalty, provided the copyright notice and this notice are preserved.", "are permitted in any medium without royalty, provided the copyright notice and this notice are preserved.",
"This file is offered as-is, without any warranty." "This file is offered as-is, without any warranty."
@ -9,38 +9,37 @@
"displayName": "Texinfo Language Support", "displayName": "Texinfo Language Support",
"description": "Texinfo language support for Visual Studio Code", "description": "Texinfo language support for Visual Studio Code",
"publisher": "cismonx", "publisher": "cismonx",
"version": "0.2.1", "version": "0.3.0",
"author": { "author": {
"name": "CismonX", "name": "CismonX",
"email": "admin@cismon.net", "email": "admin@cismon.net",
"url": "https://cismon.net" "url": "https://cismon.net"
}, },
"license": "GPL-3.0-or-later", "license": "GPL-3.0-or-later",
"homepage": "https://sv.gnu.org/p/vscode-texinfo", "homepage": "https://savannah.nongnu.org/p/vscode-texinfo",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://git.sv.gnu.org/cgit/vscode-texinfo.git" "url": "https://git.savannah.nongnu.org/cgit/vscode-texinfo.git"
}, },
"icon": "assets/texinfo.png", "icon": "assets/texinfo.png",
"devDependencies": { "devDependencies": {
"@types/node": "^14.14.43", "@types/node": "^18.11.9",
"@types/terser-webpack-plugin": "^5.0.3", "@types/vscode": "~1.82.0",
"@types/vscode": "~1.40.0", "@types/webpack": "^5.28.5",
"@types/webpack": "^5.28.0", "@typescript-eslint/eslint-plugin": "^7.5.0",
"@typescript-eslint/eslint-plugin": "^4.22.1", "@typescript-eslint/parser": "^7.5.0",
"@typescript-eslint/parser": "^4.22.1", "@vscode/vsce": "^2.24.0",
"cson": "^7.20.0", "cson": "^8.4.0",
"eslint": "^7.25.0", "eslint": "^8.57.0",
"json": "^11.0.0", "json": "^11.0.0",
"json5": "^2.2.0", "json5": "^2.2.3",
"language-texinfo": "^1.0.0", "language-texinfo": "^1.1.0",
"minify-xml": "^2.5.0", "minify-xml": "^4.4.1",
"ts-loader": "^9.1.1", "ts-loader": "^9.5.1",
"ts-node": "^9.1.1", "ts-node": "^10.9.2",
"typescript": "^4.2.4", "typescript": "^5.4.3",
"vsce": "^1.87.0", "webpack": "^5.91.0",
"webpack": "^5.35.1", "webpack-cli": "^5.1.4"
"webpack-cli": "^4.6.0"
}, },
"scripts": { "scripts": {
"vscode:prepublish": "webpack --mode production", "vscode:prepublish": "webpack --mode production",
@ -72,7 +71,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",
@ -80,7 +79,7 @@
} }
}, },
"engines": { "engines": {
"vscode": "^1.40.0" "vscode": "^1.82.0"
}, },
"categories": [ "categories": [
"Programming Languages", "Programming Languages",
@ -88,9 +87,6 @@
"Other" "Other"
], ],
"main": "./out/extension.js", "main": "./out/extension.js",
"activationEvents": [
"onLanguage:texinfo"
],
"contributes": { "contributes": {
"commands": [ "commands": [
{ {
@ -167,7 +163,7 @@
"texinfo.makeinfo": { "texinfo.makeinfo": {
"type": "string", "type": "string",
"default": "makeinfo", "default": "makeinfo",
"description": "Path to the makeinfo (or texi2any) command." "description": "Path to the makeinfo (or texi2any) program."
}, },
"texinfo.preview.customCSS": { "texinfo.preview.customCSS": {
"type": "string", "type": "string",
@ -205,12 +201,17 @@
"texinfo.preview.noValidation": { "texinfo.preview.noValidation": {
"type": "boolean", "type": "boolean",
"default": false, "default": false,
"description": "Supress node cross-reference validation." "description": "Suppress node cross-reference validation."
}, },
"texinfo.preview.variables": { "texinfo.preview.variables": {
"type": "array", "type": "array",
"default": [], "default": [],
"description": "Define variables (as with @set)." "description": "Define variables (as with @set)."
},
"texinfo.preview.customizationVariables": {
"type": "array",
"default": [],
"description": "Set customization variables (format: KEY=VAL)"
} }
} }
}, },

View File

@ -1,38 +1,45 @@
#!/usr/bin/env bash #!/bin/sh
# #
# Copyright (C) 2021 CismonX <admin@cismon.net> # Copyright (C) 2021,2024 CismonX <admin@cismon.net>
# #
# Copying and distribution of this file, with or without modification, are # Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty, provided the copyright notice and # permitted in any medium without royalty, provided the copyright notice and
# this notice are preserved. This file is offered as-is, without any warranty. # this notice are preserved. This file is offered as-is, without any warranty.
# #
VSIX_FILE_NAME=texinfo-$(json -f package.json version).vsix set -e
PACKAGE_JSON_CLEANUP_JS=$(cat ./scripts/package-json-cleanup.js)
vsce package --baseContentUrl=$(json -f package.json repository.url)/tree version=$(json -f package.json version)
vsix_file_name=texinfo-$version.vsix
vsce package --baseContentUrl="$(json -f package.json repository.url)/tree"
unzip -d "$vsix_file_name.d" "$vsix_file_name"
cd "$vsix_file_name.d"
minify_xml() {
minify-xml --no-shorten-namespaces --no-remove-unused-namespaces \
--no-remove-unused-default-namespace -i "$1"
}
minify_xml '[Content_Types].xml'
minify_xml extension.vsixmanifest
unzip -d $VSIX_FILE_NAME{.d,}
cd $VSIX_FILE_NAME.d
MINIFY_XML_OPTIONS='--no-shorten-namespaces --no-remove-unused-namespaces --no-remove-unused-default-namespace'
minify-xml $MINIFY_XML_OPTIONS --output \[Content_Types\].xml{,}
minify-xml $MINIFY_XML_OPTIONS --output extension.vsixmanifest{,}
cd extension cd extension
# Minify JSON files. # Minify JSON files.
json -j0 -I -e "$PACKAGE_JSON_CLEANUP_JS" -f package.json json -j0 -I -e "$(cat ../../scripts/package-json-cleanup.js)" -f package.json
perl -pi -e 'chomp if eof' package.json perl -pi -e 'chomp if eof' package.json
json5 -o language-configuration.json{,} json5 language-configuration.json > _ && mv _ language-configuration.json
# Remove comments from Markdown files. # Remove comments from Markdown files.
tail -n +9 README.md > _ && mv _ README.md tail -n +9 README.md > _ && mv _ README.md
tail -n +9 CHANGELOG.md > _ && mv _ CHANGELOG.md tail -n +9 CHANGELOG.md > _ && mv _ CHANGELOG.md
cd ext cd ext
# Minify Perl scripts. # Minify Perl scripts.
if [ -x "$(command -v perltidy)" ]; then # You can install perltidy with `cpan Perl::Tidy`
if command -v perltidy; then
perltidy --mangle -dac -b html-preview.pm perltidy --mangle -dac -b html-preview.pm
rm html-preview.pm.bak rm html-preview.pm.bak
fi fi
cd ../../..
cd ../../..
# Re-package .vsix file. # Re-package .vsix file.
node ./scripts/make-vsix.js $VSIX_FILE_NAME node ./scripts/make-vsix.js "$vsix_file_name"
rm -r $VSIX_FILE_NAME.d rm -r "$vsix_file_name.d"

View File

@ -1,17 +1,21 @@
#!/usr/bin/env sh #!/bin/sh
# #
# Copyright (C) 2020,2021 CismonX <admin@cismon.net> # Copyright (C) 2020,2021,2024 CismonX <admin@cismon.net>
# #
# Copying and distribution of this file, with or without modification, are # Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty, provided the copyright notice and # permitted in any medium without royalty, provided the copyright notice and
# this notice are preserved. This file is offered as-is, without any warranty. # this notice are preserved. This file is offered as-is, without any warranty.
# #
SRC_PATH=./node_modules/language-texinfo set -e
DEST_PATH=./out/grammars
mkdir -p $DEST_PATH
# Convert TextMate grammar from CSON to JSON, as VSCode cannot recognize CSON ones.
cson2json $SRC_PATH/grammars/texinfo.cson | json5 > $DEST_PATH/texinfo.json
VERSION=$(json -f package.json version) src_path=./node_modules/language-texinfo
echo "@set VERSION $VERSION" > ./doc/version.texi dest_path=./out/grammars
mkdir -p $dest_path
# Convert TextMate grammar to JSON, since VSCode cannot recognize CSON ones.
cson2json $src_path/grammars/texinfo.cson | json5 > $dest_path/texinfo.json
version=$(json -f package.json version)
echo "@set VERSION $version" > ./doc/version.texi

View File

@ -25,10 +25,11 @@ 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
{
/** /**
* Get context of a Texinfo document. Create one if not exists. * Get context of a Texinfo document. Create one if not exists.
* *
@ -36,32 +37,47 @@ export default class ContextMapping implements vscode.Disposable {
* @returns * @returns
*/ */
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
this.map.set(document, documentContext); = new DocumentContext(this._globalContext, document);
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;
} }
/** /**
@ -70,24 +86,26 @@ export default class ContextMapping implements vscode.Disposable {
* @param document * @param document
* @param nodeName * @param nodeName
*/ */
private gotoPreview(document: vscode.TextDocument, nodeName: string) { private _gotoPreview(document: vscode.TextDocument, nodeName: string) {
this.getDocumentContext(document).initPreview().goto(nodeName); this.getDocumentContext(document).initPreview().goto(nodeName);
} }
private onDocumentClose(document: vscode.TextDocument) { private _onDocumentClose(document: vscode.TextDocument) {
this.map.get(document)?.getPreview()?.close(); this._map.get(document)?.getPreview()?.close();
this.map.delete(document); this._map.delete(document);
} }
private onDocumentSave(document: vscode.TextDocument) { private _onDocumentSave(document: vscode.TextDocument) {
const documentContext = this.tryGetDocumentContext(document); const documentContext = this._tryGetDocumentContext(document);
if (documentContext === undefined) return; if (documentContext === undefined) {
return;
}
documentContext.foldingRange.clear(); documentContext.foldingRange.clear();
documentContext.getPreview()?.updateWebview(); documentContext.getPreview()?.updateWebview();
} }
private onDocumentUpdate(event: vscode.TextDocumentChangeEvent) { private _onDocumentUpdate(event: vscode.TextDocumentChangeEvent) {
const documentContext = this.tryGetDocumentContext(event.document); const documentContext = this._tryGetDocumentContext(event.document);
if (documentContext?.foldingRange.update(event.contentChanges)) { if (documentContext?.foldingRange.update(event.contentChanges)) {
documentContext.documentSymbol.clear(); documentContext.documentSymbol.clear();
} }
@ -98,11 +116,14 @@ export default class ContextMapping implements vscode.Disposable {
* *
* @param editor The editor where the document is being held. * @param editor The editor where the document is being held.
*/ */
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

@ -28,25 +28,28 @@ import PreviewContext from './preview';
/** /**
* Holds all contexts for a Texinfo document. * Holds all contexts for a Texinfo document.
*/ */
export default class DocumentContext { export default class DocumentContext
{
readonly foldingRange = new FoldingRangeContext(this); readonly foldingRange = new FoldingRangeContext(this);
readonly documentSymbol = new DocumentSymbolContext(this); readonly documentSymbol = new DocumentSymbolContext(this);
initPreview() { initPreview() {
return this.preview ??= new PreviewContext(this); return this._preview ??= new PreviewContext(this);
} }
getPreview() { getPreview() {
return this.preview; return this._preview;
} }
closePreview() { closePreview() {
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

@ -27,43 +27,52 @@ import { FoldingRange, Optional } from '../utils/types';
/** /**
* Context for symbols in a Texinfo document. * Context for symbols in a Texinfo document.
*/ */
export default class DocumentSymbolContext { export default class DocumentSymbolContext
{
get documentSymbols() { get documentSymbols() {
return this._documentSymbols ??= this.calculcateDocumentSymbols(); return this._documentSymbols ??= this._calculateDocumentSymbols();
} }
clear() { clear() {
this._documentSymbols = undefined; this._documentSymbols = undefined;
} }
constructor(private readonly documentContext: DocumentContext) {} constructor(private readonly _documentContext: DocumentContext) {}
private _documentSymbols?: vscode.DocumentSymbol[]; private _documentSymbols?: vscode.DocumentSymbol[];
private readonly document = this.documentContext.document; private readonly _document = this._documentContext.document;
/** /**
* Calculate document symbols based on folding ranges. * Calculate document symbols based on folding ranges.
*/ */
private calculcateDocumentSymbols() { private _calculateDocumentSymbols() {
const ranges = Array<Optional<FoldingRange>>(this.document.lineCount); const ranges = Array<Optional<FoldingRange>>(this._document.lineCount);
this.documentContext.foldingRange.foldingRanges this._documentContext.foldingRange.foldingRanges
.filter(range => range.kind === undefined) .filter(range => range.kind === undefined)
.forEach(range => ranges[range.start] = range); .forEach(range => ranges[range.start] = range);
return foldingRangeToSymbols(ranges, 0, ranges.length); return foldingRangeToSymbols(ranges, 0, ranges.length);
} }
} }
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) {
const range = lineNumToRange(idx, node.end); continue;
const selectionRange = lineNumToRange(idx); }
const symbol = new vscode.DocumentSymbol('@' + node.name, node.detail, vscode.SymbolKind.String, const symbol = new vscode.DocumentSymbol(
range, selectionRange); '@' + node.name,
node.detail,
vscode.SymbolKind.String,
lineNumToRange(idx, node.end), // full range
lineNumToRange(idx), // selection range
);
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,24 +27,24 @@ 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
{
/** /**
* Get VSCode folding ranges from the context. * Get VSCode folding ranges from the context.
*/ */
get foldingRanges() { get foldingRanges() {
return this._foldingRanges ?? this.calculateFoldingRanges(); return this._foldingRanges ?? this._calculateFoldingRanges();
} }
/** /**
* Get node values of document as VSCode code lenses. * Get node values of document as VSCode code lenses.
*/ */
get nodeValues() { get nodeValues() {
this._foldingRanges ?? this.calculateFoldingRanges(); this._foldingRanges ?? this._calculateFoldingRanges();
return this.nodes; return this._nodes;
} }
/** /**
@ -53,14 +53,18 @@ export default class FoldingRangeContext {
* @param events Events describing the changes in the document. * @param events Events describing the changes in the document.
*/ */
update(events: readonly vscode.TextDocumentContentChangeEvent[]) { update(events: readonly vscode.TextDocumentContentChangeEvent[]) {
this.contentMayChange = true; this._contentMayChange = true;
if (this._foldingRanges === undefined) return false; if (this._foldingRanges === undefined) {
const eol = this.document.eol === vscode.EndOfLine.LF ? '\n' : '\r\n'; return false;
}
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;
} }
} }
@ -68,40 +72,48 @@ export default class FoldingRangeContext {
} }
clear() { clear() {
if (this.contentMayChange) { if (this._contentMayChange) {
this._foldingRanges = undefined; this._foldingRanges = undefined;
} }
} }
constructor(private readonly documentContext: DocumentContext) {} constructor(private readonly _documentContext: DocumentContext) {}
private readonly document = this.documentContext.document; private readonly _document = this._documentContext.document;
/** /**
* 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[];
private nodes = <vscode.CodeLens[]>[]; private _nodes = <vscode.CodeLens[]>[];
private commentRange?: Range; private _commentRange?: Range;
private headerStart?: number; private _headerStart?: number;
private closingChapter?: number; private _closingChapter?: number;
private closingSection?: number; private _closingSection?: number;
private closingSubsection?: number; private _closingSubsection?: number;
private contentMayChange = true; private _contentMayChange = true;
private addRange(start: number, end: number, extraArgs: { private _addRange(start: number, end: number, extraArgs: {
name?: string, name?: string,
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);
} }
/** /**
@ -110,30 +122,36 @@ export default class FoldingRangeContext {
* @param start Starting line number. * @param start Starting line number.
* @param end Ending line number. * @param end Ending line number.
*/ */
private calculateFoldingRanges() { private _calculateFoldingRanges() {
this.contentMayChange = false; this._contentMayChange = false;
this._foldingRanges = []; this._foldingRanges = [];
this.clearTemporaries(); this._clearTemporaries();
let closingBlocks = <NamedLine[]>[]; let closingBlocks = <NamedLine[]>[];
let lastLine = this.document.lineCount - 1; let lastLine = this._document.lineCount - 1;
let verbatim = false; let verbatim = false;
for (let idx = lastLine; idx >= 0; --idx) { for (let idx = lastLine; idx >= 0; --idx) {
const line = this.document.lineAt(idx).text.trimLeft(); const line = this._document.lineAt(idx).text.trimLeft();
if (!line.startsWith('@')) continue; if (!line.startsWith('@')) {
continue;
}
if (!verbatim) { if (!verbatim) {
if (line === '@bye') { if (line === '@bye') {
lastLine = idx; lastLine = idx;
// Abort anything after `@bye`. // Abort anything after `@bye`.
this._foldingRanges = []; this._foldingRanges = [];
closingBlocks = []; closingBlocks = [];
this.clearTemporaries(); this._clearTemporaries();
continue;
}
if (this._processComment(line, idx)) {
continue; continue;
} }
if (this.processComment(line, idx)) continue;
} }
// Process block. // Process block.
if (line.startsWith('@end ')) { if (line.startsWith('@end ')) {
if (verbatim) continue; if (verbatim) {
continue;
}
const name = line.substring(5).trimRight(); const name = line.substring(5).trimRight();
if (name === 'verbatim') { if (name === 'verbatim') {
verbatim = true; verbatim = true;
@ -141,93 +159,123 @@ export default class FoldingRangeContext {
closingBlocks.push({ name: name, line: idx }); closingBlocks.push({ name: name, line: idx });
continue; continue;
} }
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) {
if (line.substring(1, closingBlock.name.length + 2).trim() === closingBlock.name) { continue;
this.addRange(idx, closingBlock.line, { name: closingBlock.name }); }
// If `verbatim == true` goes here, this line must be the `@verbatim` line. const name = closingBlock.name;
if (line.substring(1, name.length + 2).trim() === name) {
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;
} }
private clearTemporaries() { private _clearTemporaries() {
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) {
for (let idx = lineNum; idx > lineNum - limit; --idx) { for (let idx = lineNum; idx > lineNum - limit; --idx) {
const line = this.document.lineAt(idx).text; const line = this._document.lineAt(idx).text;
if (line.startsWith('@node ')) return idx - 1; if (line.startsWith('@node ')) {
if (line === '') return idx; return idx - 1;
}
if (line === '') {
return idx;
}
} }
return lineNum; return lineNum;
} }
private processComment(lineText: string, lineNum: number) { private _processComment(lineText: string, lineNum: number) {
if (!lineText.startsWith('@c')) return false; if (!lineText.startsWith('@c')) {
if (!lineText.startsWith(' ', 2) && !lineText.startsWith('omment ', 2)) { return false;
}
if (lineText.charAt(2) != ' ' && !lineText.startsWith('omment ', 2)) {
return false; return false;
} }
// Check for opening/closing header. // Check for opening/closing header.
if (lineText.startsWith('%**', lineText[2] === ' ' ? 3 : 9)) { if (lineText.startsWith('%**', lineText[2] === ' ' ? 3 : 9)) {
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,
this.headerStart = undefined; { kind: vscode.FoldingRangeKind.Region });
this._headerStart = undefined;
} }
return true; return true;
} }
if (this.commentRange === undefined) { if (this._commentRange === undefined) {
this.commentRange = { start: lineNum, end: lineNum }; this._commentRange = { start: lineNum, end: lineNum };
} 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,
this.commentRange = undefined; { kind: vscode.FoldingRangeKind.Comment });
this._commentRange = undefined;
} }
return true; return true;
} }
private processNode(lineText: string, lineNum: number, lastLineNum: number) { private _processNode(
const result = lineText.match(FoldingRangeContext.nodeFormat); lineText: string,
if (result === null) return false; lineNum: number,
lastLineNum: number,
) {
const result = lineText.match(FoldingRangeContext._nodeFormat);
if (result === null) {
return false;
}
// Node identifier. // Node identifier.
if (result[1] !== undefined) { if (result[1] !== undefined) {
this.nodes.push(new vscode.CodeLens(lineNumToRange(lineNum), { this._nodes.push(new vscode.CodeLens(lineNumToRange(lineNum), {
title: '$(go-to-file) Goto node in preview', title: '$(go-to-file) Goto node in preview',
command: 'texinfo.preview.goto', command: 'texinfo.preview.goto',
arguments: [this.document, result[5]], arguments: [this._document, result[5]],
})); }));
return true; return true;
} }
// 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,
this.closingSubsection = this.getLastTextLine(lineNum - 1); { name: result[2], detail: result[5] });
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

@ -28,85 +28,108 @@ import { getNodeHtmlRef, prompt } from '../utils/misc';
/** /**
* Stores information of a Texinfo document preview. * Stores information of a Texinfo document preview.
*/ */
export default class PreviewContext { export default class PreviewContext
{
close() { close() {
this.disposables.forEach(event => event.dispose()); this._disposables.forEach(event => event.dispose());
this.panel.dispose(); this._panel.dispose();
this.documentContext.closePreview(); this._documentContext.closePreview();
// Only show diagnostic information when the preview is active. // Only show diagnostic information when the preview is active.
this.diagnosis.delete(this.document); this._diagnosis.delete(this._document);
} }
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() {
this.panel.reveal(); this._panel.reveal();
} }
async updateWebview() { async updateWebview() {
if (this.updating) { if (this._updating) {
this.pendingUpdate = true; this._pendingUpdate = true;
return; return;
} }
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`
setTimeout(() => this.updating && this.updateTitle(), 500); // takes too long.
const initFile = this.globalContext.extensionPath + '/ext/html-preview.pm'; setTimeout(() => this._updating && this._updateTitle(), 500);
const converter = new Converter(this.document.fileName, initFile, this.globalContext.options, this.logger); const converter = new Converter(
const { data, error } = await converter.toHTML(path => this.panel.webview.asWebviewUri(path), this.script); this._document.fileName,
this._globalContext.path + '/ext/html-preview.pm',
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);
} else { } else {
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}.`,
.then(result => result && this.logger.show()); 'Show log', true)
.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();
this.pendingUpdate && this.updateWebview(); this._pendingUpdate && this.updateWebview();
} }
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,
this.disposables.push(this.panel.onDidDispose(() => this.close())); retainContextWhenHidden: true,
this.updateTitle(); 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._updateTitle();
this.updateWebview(); this.updateWebview();
} }
private readonly document = this.documentContext.document; private readonly _document = this._documentContext.document;
private readonly globalContext = this.documentContext.globalContext; private readonly _globalContext = this._documentContext.globalContext;
private readonly diagnosis = this.globalContext.diagnosis; private readonly _diagnosis = this._globalContext.diagnosis;
private readonly logger = this.globalContext.logger; private readonly _logger = this._globalContext.logger;
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.
*/ */
private pendingUpdate = false; private _pendingUpdate = false;
/** /**
* Whether the preview is updating. * Whether the preview is updating.
*/ */
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;
}
return "window.addEventListener('message', event => {" + return "window.addEventListener('message', event => {" +
"const message = event.data;" + "const message = event.data;" +
"switch (message.command) {" + "switch (message.command) {" +
@ -119,9 +142,9 @@ export default class PreviewContext {
"})"; "})";
} }
private updateTitle() { private _updateTitle() {
const updating = this.updating ? '(Updating) ' : ''; const updating = this._updating ? '(Updating) ' : '';
const fileName = path.basename(this.document.fileName); const fileName = path.basename(this._document.fileName);
this.panel.title = `${updating}Preview ${fileName}`; this._panel.title = `${updating}Preview ${fileName}`;
} }
} }

View File

@ -19,22 +19,23 @@
* vscode-texinfo. If not, see <https://www.gnu.org/licenses/>. * vscode-texinfo. If not, see <https://www.gnu.org/licenses/>.
*/ */
import * as path from 'path';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { lineNumToRange } from './utils/misc'; import { escapeStringForRegExp, lineNumToRange } from './utils/misc';
import { isDefined } from './utils/types'; import { isDefined } from './utils/types';
/** /**
* Manage diagnostic information of Texinfo documents. * Manage diagnostic information of Texinfo documents.
*/ */
export default class Diagnosis implements vscode.Disposable { export default class Diagnosis implements vscode.Disposable
{
/** /**
* Remove a document's diagnostic entry from the collection. * Remove a document's diagnostic entry from the collection.
* *
* @param document * @param document
*/ */
delete(document: vscode.TextDocument) { delete(document: vscode.TextDocument) {
this.diagnostics.delete(document.uri); this._diagnostics.delete(document.uri);
} }
/** /**
@ -44,19 +45,29 @@ export default class Diagnosis implements vscode.Disposable {
* @param logText * @param logText
*/ */
update(document: vscode.TextDocument, logText: string) { update(document: vscode.TextDocument, logText: string) {
const fileName = document.uri.path; const fileName = path.basename(document.uri.path);
const regex = new RegExp(`${escapeStringForRegExp(fileName)}:\\d+:`);
const diagnostics = logText.split('\n') const diagnostics = logText.split('\n')
.filter(line => line.startsWith(fileName)) .map(line => line.length > 0 ? line.match(regex) : null)
.map(line => logLineToDiagnostic(line.substring(fileName.length + 1))) .map(result => {
const index = result?.index;
const line = result?.input;
if (index === undefined || line === undefined) {
return undefined;
}
const logText = line.substring(index + fileName.length + 1);
return logToDiagnostic(logText);
})
.filter(isDefined); .filter(isDefined);
this.diagnostics.set(document.uri, diagnostics); this._diagnostics.set(document.uri, diagnostics);
} }
dispose() { dispose() {
this.diagnostics.dispose(); this._diagnostics.dispose();
} }
private readonly diagnostics = vscode.languages.createDiagnosticCollection('texinfo'); private readonly _diagnostics
= vscode.languages.createDiagnosticCollection('texinfo');
} }
/** /**
@ -65,11 +76,15 @@ 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 to 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

@ -33,33 +33,54 @@ import FoldingRangeProvider from './providers/folding_range';
/** /**
* Manage extension-level global-scope contexts. * Manage extension-level global-scope contexts.
*/ */
export default class GlobalContext { export default class GlobalContext
{
readonly contextMapping = new ContextMapping(this); readonly contextMapping = new ContextMapping(this);
readonly diagnosis = new Diagnosis; readonly diagnosis = new Diagnosis;
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;
} }
subscribe(...items: vscode.Disposable[]) { subscribe(...items: vscode.Disposable[]) {
this.context.subscriptions.push(...items); this._context.subscriptions.push(...items);
} }
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,
vscode.languages.registerCompletionItemProvider('texinfo', new CompletionItemProvider(this), '@'), this.diagnosis,
vscode.languages.registerDocumentSymbolProvider('texinfo', new DocumentSymbolProvider(this)), this.indicator,
vscode.languages.registerFoldingRangeProvider('texinfo', new FoldingRangeProvider(this)), this.logger,
vscode.workspace.onDidChangeConfiguration(() => this._options = undefined), vscode.languages.registerCodeLensProvider(
'texinfo',
new CodeLensProvider(this),
),
vscode.languages.registerCompletionItemProvider(
'texinfo',
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

@ -1,7 +1,7 @@
/** /**
* indicator.ts * indicator.ts
* *
* Copyright (C) 2020,2021 CismonX <admin@cismon.net> * Copyright (C) 2020,2021,2024 CismonX <admin@cismon.net>
* *
* This file is part of vscode-texinfo. * This file is part of vscode-texinfo.
* *
@ -26,34 +26,41 @@ import { exec } from './utils/misc';
/** /**
* Shows whether GNU Texinfo is properly installed and configured. * Shows whether GNU Texinfo is properly installed and configured.
*/ */
export default class Indicator implements vscode.Disposable { export default class Indicator implements vscode.Disposable
{
get canDisplayPreview() { get canDisplayPreview() {
return this._canDisplayPreview; return this._canDisplayPreview;
} }
dispose() { dispose() {
this.statusBarItem.dispose(); this._statusBarItem.dispose();
} }
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(
vscode.window.onDidChangeActiveTextEditor(this.refresh.bind(this)), 'texinfo.indicator.click',
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.
*/ */
private async click() { private async _click() {
await this.updateStatus(); await this._updateStatus();
this.refresh(vscode.window.activeTextEditor); this._refresh(vscode.window.activeTextEditor);
} }
/** /**
@ -61,39 +68,46 @@ export default class Indicator implements vscode.Disposable {
* *
* @param editor * @param editor
*/ */
private refresh(editor?: vscode.TextEditor) { private _refresh(editor?: vscode.TextEditor) {
if (editor?.document.languageId === 'texinfo') { if (editor?.document.languageId === 'texinfo') {
this.statusBarItem.show(); this._statusBarItem.show();
} else { } else {
this.statusBarItem.hide(); this._statusBarItem.hide();
} }
} }
/** /**
* 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 = ''; const version = result?.[1] ?? '';
if (result && result[1]) { let tooltip = '', icon: string;
version = result[1]; if (version) {
if (!isNaN(+version) && +version < 6.7) { icon = '$(check)';
icon = '$(warning)'; const verArr = version.split('.');
tooltip = `GNU Texinfo (${options.makeinfo}) is outdated (${version} < 6.7).`; if (version.length >= 2) {
} else { const verMajor = parseInt(verArr[0], 10);
// Unrecognizable version. Assume it is okay. const verMinor = parseInt(verArr[1], 10);
icon = '$(check)'; if (verMajor < 7 || verMinor < 1) {
icon = '$(warning)';
tooltip = `GNU Texinfo (${options.makeinfo}) ` +
`is outdated (${version} < 7.1).`;
}
} }
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';
this.statusBarItem.text = `${icon} GNU Texinfo ${version}`; this._statusBarItem.text = `${icon} GNU Texinfo ${version}`;
this.statusBarItem.tooltip = tooltip; this._statusBarItem.tooltip = tooltip;
} }
} }

View File

@ -24,20 +24,24 @@ import * as vscode from 'vscode';
/** /**
* Logger which prints message to VSCode output channel. * Logger which prints message to VSCode output channel.
*/ */
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(
this.outputChannel.appendLine(`[ ${dateTime} ]\n${message}`); undefined,
{ hour12: false }
);
this._outputChannel.appendLine(`[ ${dateTime} ]\n${message}`);
} }
show() { show() {
this.outputChannel.show(true); this._outputChannel.show(true);
} }
dispose() { dispose() {
this.outputChannel.dispose(); this._outputChannel.dispose();
} }
private readonly outputChannel = vscode.window.createOutputChannel('Texinfo'); private readonly _outputChannel
= vscode.window.createOutputChannel('Texinfo');
} }

View File

@ -26,75 +26,80 @@ import * as vscode from 'vscode';
* *
* See the `contributes.configuration` entry in package.json for details. * See the `contributes.configuration` entry in package.json for details.
*/ */
export default class Options { export default class Options
{
get enableSnippets() { get enableSnippets() {
return this.getBoolean('completion.enableSnippets'); return this._getBoolean('completion.enableSnippets');
} }
get hideSnippetCommands() { get hideSnippetCommands() {
return this.getBoolean('completion.hideSnippetCommands'); return this._getBoolean('completion.hideSnippetCommands');
} }
get noWarnings() { get noWarnings() {
return this.getBoolean('diagnosis.noWarnings'); return this._getBoolean('diagnosis.noWarnings');
} }
get enableCodeLens() { get enableCodeLens() {
return this.getBoolean('enableCodeLens'); return this._getBoolean('enableCodeLens');
} }
get makeinfo() { get makeinfo() {
return this.getString('makeinfo'); return this._getString('makeinfo');
} }
get customCSS() { get customCSS() {
return this.getString('preview.customCSS'); return this._getString('preview.customCSS');
} }
get errorLimit() { get errorLimit() {
return this.getNumber('preview.errorLimit'); return this._getNumber('preview.errorLimit');
} }
get includePaths() { get includePaths() {
return this.getArray('preview.includePaths'); return this._getArray('preview.includePaths');
} }
get maxSize() { get maxSize() {
return this.getNumber('preview.maxSize') * 1024 * 1024; return this._getNumber('preview.maxSize') * 1024 * 1024;
} }
get noHeaders() { get noHeaders() {
return this.getBoolean('preview.noHeaders'); return this._getBoolean('preview.noHeaders');
} }
get noNumberSections() { get noNumberSections() {
return this.getBoolean('preview.noNumberSections'); return this._getBoolean('preview.noNumberSections');
} }
get noValidation() { get noValidation() {
return this.getBoolean('preview.noValidation'); return this._getBoolean('preview.noValidation');
} }
get variables() { get variables() {
return this.getArray('preview.variables'); return this._getArray('preview.variables');
} }
private readonly configuration = vscode.workspace.getConfiguration('texinfo'); get customizationVariables() {
return this._getArray('preview.customizationVariables');
private getArray(section: string): readonly string[] {
return this.configuration.get(section, []);
} }
private getBoolean(section: string) { private readonly _configuration
return this.configuration.get(section, false); = vscode.workspace.getConfiguration('texinfo');
private _getArray(section: string): readonly string[] {
return this._configuration.get(section, []);
} }
private getNumber(section: string) { private _getBoolean(section: string) {
return this.configuration.get(section, 0); return this._configuration.get(section, false);
} }
private getString(section: string) { private _getNumber(section: string) {
return this.configuration.get(section, ''); return this._configuration.get(section, 0);
}
private _getString(section: string) {
return this._configuration.get(section, '');
} }
} }

View File

@ -25,13 +25,18 @@ import GlobalContext from '../global_context';
/** /**
* Provide code lenses for Texinfo document. * Provide code lenses for Texinfo document.
*/ */
export default class CodeLensProvider implements vscode.CodeLensProvider { 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) {
if (!this.globalContext.indicator.canDisplayPreview) return undefined; return undefined;
return this.globalContext.contextMapping.getDocumentContext(document).foldingRange.nodeValues; }
if (!this._globalContext.indicator.canDisplayPreview) {
return undefined;
}
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,11 +25,13 @@ 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,11 +25,13 @@ 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

@ -23,52 +23,76 @@ import * as path from 'path';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import Logger from '../logger'; import Logger from '../logger';
import Options from '../options'; import Options from '../options';
import { exec } from './misc'; import { exec, normalizePath } from './misc';
import { Operator } from './types'; import { Operator } from './types';
/** /**
* Converter which converts file from Texinfo to other formats. * Converter which converts file from Texinfo to other formats.
*/ */
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._pathName) + '/');
const options = ['-o-', '--no-split', '--html', `--error-limit=${this.options.errorLimit}`, const newPath = normalizePath(imgTransformer(pathUri).toString());
`--init-file=${this.initFile}`, '-D', `__vscode_texinfo_image_uri_base ${newPath}`]; const options = ['-o-', '--no-split', '--html',
this.options.noHeaders && options.push('--no-headers'); `--error-limit=${this._options.errorLimit}`,
this.options.noNumberSections && options.push('--no-number-sections'); `--init-file=${this._initFile}`,
this.options.noValidation && options.push('--no-validate'); '-D', `__vscode_texinfo_image_uri_base ${newPath}`,
this.options.noWarnings && options.push('--no-warn'); ];
insertScript !== undefined && options.push('-c', `EXTRA_HEAD <script>${insertScript}</script>`); this._options.noHeaders && options.push('--no-headers');
this.addIncludePaths(this.options.includePaths, options); this._options.noNumberSections && options.push('--no-number-sections');
this.defineVariables(this.options.variables, options); this._options.noValidation && options.push('--no-validate');
this.includeCustomCSS(this.options.customCSS, options); this._options.noWarnings && options.push('--no-warn');
return await exec(this.options.makeinfo, options.concat(this.path), this.options.maxSize); if (insertScript !== undefined) {
options.push('-c', `EXTRA_HEAD <script>${insertScript}</script>`);
}
this._addIncludePaths(this._options.includePaths, options);
this._defineVariables(this._options.variables, options);
this._setCustomizationVariables(
this._options.customizationVariables, options);
this._includeCustomCSS(this._options.customCSS, options);
return await exec(
this._options.makeinfo,
options.concat(normalizePath(this._pathName)),
this._options.maxSize,
);
} }
constructor( constructor(
private readonly path: string, private readonly _pathName: string,
private readonly initFile: string, private readonly _initFile: string,
private readonly options: Options, private readonly _options: Options,
private readonly logger: Logger, private readonly _logger: Logger,
) {} ) {}
private addIncludePaths(paths: readonly string[], options: string[]) { private _addIncludePaths(paths: readonly string[], options: string[]) {
if (paths.length === 0) {
return;
}
const separator = process.platform === 'win32' ? ';' : ':'; const separator = process.platform === 'win32' ? ';' : ':';
options.push('-I', paths.join(separator)); options.push('-I', paths.join(separator));
} }
private defineVariables(variables: readonly string[], options: string[]) { private _defineVariables(variables: readonly string[], options: string[]) {
variables.forEach(varName => options.push('-D', varName)); variables.forEach(varName => options.push('-D', varName));
} }
private includeCustomCSS(cssFileURI: string, options: string[]) { private _setCustomizationVariables(
if (!cssFileURI) return; variables: readonly string[],
options: string[],
) {
variables.forEach(varName => options.push('-c', varName));
}
private _includeCustomCSS(cssFileURI: string, options: string[]) {
if (!cssFileURI) {
return;
}
try { try {
const uri = vscode.Uri.parse(cssFileURI, true); const uri = vscode.Uri.parse(cssFileURI, true);
switch (uri.scheme) { switch (uri.scheme) {
case 'file': case 'file':
options.push(`--css-include=${uri.path}`); options.push(`--css-include=${normalizePath(uri.fsPath)}`);
break; break;
case 'http': case 'http':
case 'https': case 'https':
@ -78,7 +102,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

@ -20,9 +20,20 @@
*/ */
import * as child_process from 'child_process'; import * as child_process from 'child_process';
import * as path from 'path';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { ExecResult } from './types'; import { ExecResult } from './types';
/**
* Escape string to match verbatim in regular expression.
*
* @param str The string to be escaped.
* @returns The escaped string.
*/
export function escapeStringForRegExp(str: string) {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
/** /**
* Execute command and fetch output. * Execute command and fetch output.
* *
@ -32,9 +43,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.UTF-8' },
maxBuffer: maxBuffer,
},
(error, stdout, stderr) => resolve(error
? { error: stderr ? stderr : error.message }
: { data: stdout, error: stderr }
)
)
);
} }
/** /**
@ -46,7 +66,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 +89,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 +99,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
* GNU 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 +123,22 @@ 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);
}
export function normalizePath(pathName: string) {
pathName = path.normalize(pathName);
if (process.platform === 'win32') {
// On Windows, when passing the path of input file to makeinfo,
// using backslashes in path name breaks some other command line
// options (notably, -I).
// Not sure if this is a bug of makeinfo, or perl, or neither.
//
// TODO: We should look into this issue sometime later.
return pathName.replace(/\\/g, '/');
} else {
return pathName;
}
} }

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;

View File

@ -7,8 +7,7 @@
*/ */
{ {
"compilerOptions": { "compilerOptions": {
"module": "CommonJS", "target": "ES2021",
"target": "ES2019",
"outDir": "out", "outDir": "out",
"esModuleInterop": true, "esModuleInterop": true,
"strictNullChecks": true, "strictNullChecks": true,

View File

@ -19,13 +19,15 @@ const config: webpack.Configuration = {
output: { output: {
path: path.resolve(__dirname, 'out'), path: path.resolve(__dirname, 'out'),
filename: 'extension.js', filename: 'extension.js',
libraryTarget: 'commonjs2', library: {
type: "commonjs2",
},
devtoolModuleFilenameTemplate: '../[resource-path]', devtoolModuleFilenameTemplate: '../[resource-path]',
}, },
devtool: isProduction ? false : 'source-map', devtool: isProduction ? false : 'source-map',
optimization: { optimization: {
concatenateModules: true, concatenateModules: true,
minimize: true, minimize: isProduction,
minimizer: [ minimizer: [
new TerserPlugin({ new TerserPlugin({
extractComments: false, extractComments: false,
@ -39,6 +41,9 @@ const config: webpack.Configuration = {
}, },
mangle: { mangle: {
module: true, module: true,
properties: {
regex: /^_/,
},
}, },
}, },
}), }),