initial commit

This commit is contained in:
CismonX 2020-10-04 02:04:18 +08:00
commit ac1872501c
Signed by: cismonx
GPG Key ID: 3094873E29A482FB
17 changed files with 1655 additions and 0 deletions

19
.eslintrc.json Normal file
View File

@ -0,0 +1,19 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "module"
},
"plugins": [
"@typescript-eslint"
],
"rules": {
"@typescript-eslint/naming-convention": "warn",
"@typescript-eslint/semi": "warn",
"curly": "warn",
"eqeqeq": "warn",
"no-throw-literal": "warn",
"semi": "off"
}
}

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
.vscode/
node_modules
out
.vscode-test/
*.vsix

11
.travis.yml Normal file
View File

@ -0,0 +1,11 @@
os:
- linux
dist: bionic
language: node_js
node_js:
- node
- lts/*
script:
- npm run vscode:prepublish

10
.vscodeignore Normal file
View File

@ -0,0 +1,10 @@
.vscode/**
.vscode-test/**
out/test/**
src/**
.gitignore
.yarnrc
**/tsconfig.json
**/.eslintrc.json
**/*.map
**/*.ts

1
CHANGELOG.md Normal file
View File

@ -0,0 +1 @@
# Change Log

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 CismonX <admin@cismon.net>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

15
README.md Normal file
View File

@ -0,0 +1,15 @@
# vscode-texinfo
Texinfo language support for Visual Studio Code.
## Features
**Warning**: This extension is in the early stage of development. **DO NOT USE**.
## Requirements
To enable the preview feature, the `makeinfo` command-line tool, which is a part of [GNU Texinfo](https://www.gnu.org/software/texinfo/), should be present on your system.
## Extension Settings
See VSCode settings for details.

View File

@ -0,0 +1,14 @@
{
"comments": {
"lineComment": "@c"
},
"brackets": [
["{", "}"]
],
"autoClosingPairs": [
["{", "}"]
],
"surroundingPairs": [
["{", "}"]
]
}

1090
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

129
package.json Normal file
View File

@ -0,0 +1,129 @@
{
"name": "texinfo",
"displayName": "Texinfo Language Support",
"description": "Syntax highlighting, autocompletion and preview support for Texinfo.",
"version": "0.1.0",
"author": {
"name": "CismonX",
"email": "admin@cismon.net",
"url": "https://cismon.net"
},
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/CismonX/vscode-texinfo"
},
"engines": {
"vscode": "^1.49.0"
},
"categories": [
"Linters",
"Other",
"Programming Languages",
"Snippets"
],
"main": "./out/extension.js",
"activationEvents": [
"onLanguage:texinfo"
],
"contributes": {
"commands": [
{
"command": "texinfo.showPreview",
"title": "Show preview (Texinfo)",
"icon": "$(open-preview)"
}
],
"menus": {
"editor/title": [
{
"command": "texinfo.showPreview",
"when": "editorLangId == texinfo",
"group": "navigation"
}
]
},
"configuration": {
"title": "Texinfo",
"properties": {
"texinfo.makeinfo": {
"type": "string",
"default": "makeinfo",
"markdownDescription": "Path to the `makeinfo` command."
},
"texinfo.preview.noHeaders": {
"type": "boolean",
"default": false,
"markdownDescription": "Suppress node separators in preview. See `makeinfo --help` for details."
},
"texinfo.preview.maxSize": {
"type": "integer",
"default": "2",
"markdownDescription": "Max allowed size (in MiB) for the preview document."
},
"texinfo.preview.errorLimit": {
"type": "integer",
"default": 100,
"markdownDescription": "Max tolerated number of errors when trying to display preview."
},
"texinfo.preview.force": {
"type": "boolean",
"default": false,
"markdownDescription": "Preserve preview even if errors."
},
"texinfo.preview.noValidate": {
"type": "boolean",
"default": false,
"markdownDescription": "Supress node cross-reference validation."
},
"texinfo.preview.noWarn": {
"type": "boolean",
"default": false,
"markdownDescription": "Suppress warnings."
}
}
},
"languages": [
{
"id": "texinfo",
"aliases": [
"Texinfo"
],
"extensions": [
".texi",
".texinfo",
".txi"
],
"configuration": "./language-configuration.json"
}
],
"grammars": [
{
"language": "texinfo",
"scopeName": "text.texinfo",
"path": "./out/texinfo.tmGrammar.json",
"configuration": "./language-configuration.json"
}
]
},
"scripts": {
"vscode:prepublish": "npm run lint && npm run build",
"compile": "tsc -p ./",
"prepare": "sh ./scripts/prepare.sh",
"build": "npm run prepare && npm run compile",
"lint": "eslint src --ext ts",
"watch": "tsc -watch -p ./"
},
"devDependencies": {
"@types/node": "^14.11.2",
"@types/vscode": "^1.49.0",
"@typescript-eslint/eslint-plugin": "^3.8.0",
"@typescript-eslint/parser": "^3.8.0",
"eslint": "^7.10.0",
"typescript": "^4.0.3"
},
"dependencies": {
"cson": "^7.20.0",
"language-texinfo": "^1.0.0"
}
}

6
scripts/prepare.sh Executable file
View File

@ -0,0 +1,6 @@
#!/usr/bin/env sh
# Convert TextMate grammar from CSON to JSON (VSCode cannot recognize CSON ones).
TMGRAMMAR_CSON=./node_modules/language-texinfo/grammars/texinfo.cson
TMGRAMMAR_JSON=./out/texinfo.tmGrammar.json
cson2json $TMGRAMMAR_CSON > $TMGRAMMAR_JSON

53
src/converter.ts Normal file
View File

@ -0,0 +1,53 @@
/**
* converter.ts
*
* @author CismonX <admin@cismon.net>
* @license MIT
*/
import { Options } from './options';
import { exec } from './utils';
/**
* Texinfo to HTML converter.
*/
export class Converter {
/**
* Convert a texinfo document to HTML.
*
* @param path Path to the Texinfo document to be converted.
*/
static async convert(path: string) {
const converter = new Converter(path);
return await converter.convert();
}
/**
* The options to be passed to the `makeinfo` command.
*/
private readonly options = ['-o', '-', '--no-split', '--html'];
private constructor(path: string) {
if (Options.noHeaders) {
this.options.push('--no-headers');
}
if (Options.force) {
this.options.push('--force');
}
if (Options.noValidate) {
this.options.push('--no-validate');
}
if (Options.noWarn) {
this.options.push('--no-warn');
}
this.options.push(`--error-limit=${Options.errorLimit}`);
this.options.push(path);
}
private async convert() {
const makeinfo = Options.makeinfo;
const maxBuffer = Options.maxSize * 1024 * 1024;
return await exec(makeinfo, this.options, maxBuffer);
}
}

22
src/extension.ts Normal file
View File

@ -0,0 +1,22 @@
/**
* extension.ts - Texinfo extension entry
*
* @author CismonX <admin@cismon.net>
* @license MIT
*/
import * as vscode from 'vscode';
import { Preview } from './preview';
import { Options } from './options';
export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(
vscode.workspace.onDidSaveTextDocument(Preview.update),
vscode.workspace.onDidCloseTextDocument(Preview.close),
vscode.commands.registerTextEditorCommand('texinfo.showPreview', Preview.show));
}
export function deactivate() {
Preview.destroyAll();
Options.clear();
}

75
src/options.ts Normal file
View File

@ -0,0 +1,75 @@
/**
* options.ts
*
* @author CismonX <admin@cismon.net>
* @license MIT
*/
import * as vscode from 'vscode';
let options: Options | undefined;
/**
* Get extension options.
*
* See `contributes.configuration` of package.json for details.
*/
export class Options {
private static get instance() {
if (options === undefined) {
options = new Options('texinfo');
}
return options;
}
static clear() {
options = undefined;
}
static get makeinfo() {
return Options.instance.getString('makeinfo');
}
static get noHeaders() {
return Options.instance.getBoolean('preview.noHeaders');
}
static get maxSize() {
return Options.instance.getNumber('preview.maxSize');
}
static get errorLimit() {
return Options.instance.getNumber('preview.errorLimit');
}
static get force() {
return Options.instance.getBoolean('preview.force');
}
static get noValidate() {
return Options.instance.getBoolean('preview.noValidate');
}
static get noWarn() {
return Options.instance.getBoolean('preview.noWarn');
}
private readonly configuration: vscode.WorkspaceConfiguration;
private constructor(section: string) {
this.configuration = vscode.workspace.getConfiguration(section);
}
private getString(section: string) {
return this.configuration.get<string>(section) ?? '';
}
private getBoolean(section: string) {
return this.configuration.get<boolean>(section) ?? false;
}
private getNumber(section: string) {
return this.configuration.get<number>(section) ?? 0;
}
}

122
src/preview.ts Normal file
View File

@ -0,0 +1,122 @@
/**
* preview.ts
*
* @author CismonX <admin@cismon.net>
* @license MIT
*/
import * as vscode from 'vscode';
import * as path from 'path';
import { Converter } from './converter';
import { prompt } from './utils';
/**
* Texinfo document preview.
*/
export class Preview {
private static readonly map = new Map<vscode.TextDocument, Preview>();
/**
* Create (if not yet created) and show preview for a Texinfo document.
*
* @param editor The editor where the document is being held.
*/
static async show(editor: vscode.TextEditor) {
const document = editor.document;
if (document.isUntitled) {
if (!await prompt('Save this document to display preview.', 'Save')) {
return;
}
if (!await document.save()) {
return;
}
}
(Preview.map.get(document) ?? new Preview(document)).panel.reveal();
}
/**
* If the document has a corresponding Texinfo preview, update the preview.
*
* @param document
*/
static update(document: vscode.TextDocument) {
Preview.getByDocument(document)?.updateWebview();
}
/**
* If the document has a corresponding Texinfo preview, close the preview.
*
* @param document
*/
static close(document: vscode.TextDocument) {
Preview.getByDocument(document)?.destroy();
}
static destroyAll() {
Preview.map.forEach((preview) => preview.destroy());
}
/**
* Get associated preview instance of the given document.
*
* @param document
*/
private static getByDocument(document: vscode.TextDocument) {
return document.languageId !== 'texinfo' ? undefined : Preview.map.get(document);
}
private readonly panel: vscode.WebviewPanel;
private readonly disposables = new Array<vscode.Disposable>();
/**
* Whether the preview is updating.
*/
private updating = false;
/**
* Whether a preview update request is pending.
*/
private pendingUpdate = false;
private constructor(private readonly document: vscode.TextDocument) {
this.panel = vscode.window.createWebviewPanel('texinfo.preview', '', vscode.ViewColumn.Beside);
this.disposables.push(this.panel.onDidDispose(() => this.destroy()));
Preview.map.set(this.document, this);
this.updateWebview();
}
private get title() {
const updating = this.updating ? '(Updating) ' : '';
const fileName = path.basename(this.document.fileName);
return `${updating}Preview ${fileName}`;
}
private destroy() {
this.disposables.forEach((event) => event.dispose());
this.panel.dispose();
Preview.map.delete(this.document);
}
private async updateWebview() {
if (this.updating) {
this.pendingUpdate = true;
return;
}
this.updating = true;
this.pendingUpdate = false;
this.panel.title = this.title;
const htmlCode = await Converter.convert(this.document.fileName);
if (htmlCode === undefined) {
vscode.window.showErrorMessage(`Failed to show preview for file ${this.document.fileName}.`);
} else {
this.panel.webview.html = htmlCode;
}
this.updating = false;
this.panel.title = this.title;
if (this.pendingUpdate) {
this.updateWebview();
}
}
}

44
src/utils.ts Normal file
View File

@ -0,0 +1,44 @@
/**
* utils.ts - Helper functions
*
* @author CismonX <admin@cismon.net>
* @license MIT
*/
import * as vscode from 'vscode';
import * as child_process from 'child_process';
/**
* Open a prompt with two buttons, "Confirm" and "Cancel", and wait for user action.
*
* @param message The message to be displayed on the prompt.
* @param confirm Text to be displayed on the "Confirm" button.
* @yields Whether the user clicked the "Confirm" button.
*/
export async function prompt(message: string, confirm: string) {
return confirm === await vscode.window.showInformationMessage(message, confirm, 'Cancel');
}
/**
* Execute command and get output.
*
* @param path Path to the executable file.
* @param args Arguments to be passed to the command.
* @param maxBuffer Max output buffer size.
* @yields The output data, or `undefined` if execution fails.
*/
export function exec(path: string, args: string[], maxBuffer: number) {
return new Promise<string | undefined>((resolve) => {
child_process.execFile(path, args, { maxBuffer: maxBuffer }, (error, stdout, stderr) => {
if (stderr) {
console.log(stderr);
}
if (error) {
console.error(error);
resolve(undefined);
} else {
resolve(stdout);
}
});
});
}

18
tsconfig.json Normal file
View File

@ -0,0 +1,18 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"outDir": "out",
"lib": [
"es6"
],
"sourceMap": true,
"rootDir": "src",
"strict": true,
"noImplicitReturns": true
},
"exclude": [
"node_modules",
".vscode-test"
]
}