├── lib └── .keep ├── .mocharc.json ├── img ├── logo-ltex.png ├── banner-ltex.png └── logo-ltex.svg ├── .markdownlint.json ├── .gitignore ├── tsconfig.json ├── vscode-ltex.code-workspace ├── .vscodeignore ├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature-request.md │ └── bug-report.md ├── dependabot.yml ├── pull_request_template.md ├── workflows │ ├── label-commenter.yml │ ├── nightly.yml │ └── ci.yml └── label-commenter-config.yml ├── .vscode ├── settings.json ├── tasks.json └── launch.json ├── .npmignore ├── biome.json ├── src ├── Coc.ts ├── test │ ├── ProgressStack.test.ts │ ├── TestTools.ts │ ├── index.ts │ ├── Logger.test.ts │ ├── Settings.test.ts │ ├── extension.test.ts │ ├── ExtensionInitializer.ts │ ├── Commands.test.ts │ └── TestRunner.ts ├── ProgressStack.ts ├── LoggingOutputChannel.ts ├── Logger.ts ├── I18n.ts ├── WorkspaceConfigurationRequestHandler.ts ├── StatusBarItemManager.ts ├── StatusPrinter.ts ├── extension.ts └── BugReporter.ts ├── walkthrough ├── en │ ├── readDocumentation.md │ ├── checkLatexDocuments.md │ ├── customizeSettings.md │ ├── getInvolved.md │ ├── applyQuickFixes.md │ └── checkMarkdownDocuments.md └── de │ ├── readDocumentation.md │ ├── checkLatexDocuments.md │ ├── customizeSettings.md │ ├── getInvolved.md │ ├── applyQuickFixes.md │ └── checkMarkdownDocuments.md ├── .all-contributorsrc ├── tools ├── LanguageToolLanguageLister.java ├── copyChangelogToGitHubReleases.py ├── common.py ├── updateFromGhPages.py ├── generateCodeName.py ├── createOfflinePackages.py ├── updateLtexLsVersionAndHashDigests.py ├── validateJsonFiles.py ├── patchForTarget.py └── convertChangelog.py ├── ACKNOWLEDGMENTS.md ├── webpack.config.js ├── MAINTAINING.md ├── .eslintrc.js ├── i18n ├── messages.nls.de.json └── messages.nls.json ├── SECURITY.md ├── CODE_OF_CONDUCT.md └── schemas └── package.schema.json.LICENSE /lib/.keep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.mocharc.json: -------------------------------------------------------------------------------- 1 | { 2 | "unhandled-rejections": "strict" 3 | } 4 | -------------------------------------------------------------------------------- /img/logo-ltex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neo-ltex/vscode-ltex/HEAD/img/logo-ltex.png -------------------------------------------------------------------------------- /img/banner-ltex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neo-ltex/vscode-ltex/HEAD/img/banner-ltex.png -------------------------------------------------------------------------------- /.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "MD013": false, 3 | "MD033": { 4 | "allowed_elements": [ 5 | "sub", 6 | "sup" 7 | ] 8 | }, 9 | "MD038": false 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.pyc 3 | 4 | /CHANGELOG.md 5 | /*.vsix 6 | 7 | /.vscode-test/ 8 | /dist/ 9 | /lib/ 10 | /node_modules/ 11 | /out/ 12 | /tmp-*/ 13 | 14 | /.vscode/ltex.*.txt 15 | 16 | !/lib/.keep 17 | /.venv 18 | *.tsbuildinfo 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "esModuleInterop": true, 4 | "lib": [ 5 | "es2016" 6 | ], 7 | "module": "commonjs", 8 | "moduleResolution": "node", 9 | "outDir": "out", 10 | "resolveJsonModule": true, 11 | "rootDir": "src", 12 | "skipLibCheck": true, 13 | "sourceMap": true, 14 | "strict": true, 15 | "target": "es6" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /vscode-ltex.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | }, 6 | { 7 | "path": "../ltex-ls" 8 | }, 9 | { 10 | "path": "../lsp-cli" 11 | }, 12 | { 13 | "path": "../.github" 14 | }, 15 | ], 16 | "settings": { 17 | "githubPullRequests.remotes": [ 18 | "origin" 19 | ], 20 | "terminal.integrated.env.linux": { 21 | "DONT_PROMPT_WSL_INSTALL": "1" 22 | }, 23 | "typescript.enablePromptUseWorkspaceTsdk": true 24 | } 25 | } -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .eslintrc.js 2 | .gitattributes 3 | .gitignore 4 | .gitmodules 5 | .markdownlint.json 6 | .mocharc.json 7 | .npmignore 8 | .vscodeignore 9 | .webpack.config.js 10 | changelog.xml 11 | tsconfig.json 12 | *.vsix 13 | 14 | .github/ 15 | .mypy_cache/ 16 | .vscode/ 17 | .vscode-test/ 18 | img/ 19 | node_modules/ 20 | out/ 21 | schemas/ 22 | src/ 23 | test/ 24 | tmp-*/ 25 | tools/ 26 | **/*.map 27 | 28 | !.github/ISSUE_TEMPLATE/bug-report.md 29 | !img/logo-ltex.png 30 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | end_of_line = lf 9 | insert_final_newline = true 10 | indent_size = 2 11 | indent_style = tab 12 | trim_trailing_whitespace = true 13 | 14 | [*.{md,mdx}] 15 | indent_style = space 16 | max_line_length = off 17 | 18 | [*.{yml,yaml}] 19 | indent_style = space 20 | 21 | [*.json] 22 | indent_style = space 23 | insert_final_newline = false 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | 3 | contact_links: 4 | - name: "Question → Q&A at GitHub Discussions" 5 | url: "https://github.com/valentjn/vscode-ltex/discussions?discussions_q=category%3AQ%26A" 6 | about: "Got a question about LTeX, not a bug report or feature request? Ask here for help!" 7 | - name: "Website/Documentation of LTeX" 8 | url: "https://valentjn.github.io/ltex/" 9 | about: "Browse through the official documentation of LTeX." 10 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "biomejs", 4 | "clibuilder", 5 | "clipboardy", 6 | "lstlisting", 7 | "ltex", 8 | "nvim", 9 | "repobuddy", 10 | "rsweave", 11 | "vsix" 12 | ], 13 | "githubPullRequests.remotes": [ 14 | "origin" 15 | ], 16 | "javascript.format.semicolons": "insert", 17 | "terminal.integrated.env.linux": { 18 | "DONT_PROMPT_WSL_INSTALL": "1" 19 | }, 20 | "typescript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces": false, 21 | "typescript.format.semicolons": "insert" 22 | } -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.map 3 | *.pyc 4 | *.vsix 5 | 6 | .mypy_cache/ 7 | 8 | /.eslintrc.js 9 | /.gitattributes 10 | /.gitignore 11 | /.gitmodules 12 | /.markdownlint.json 13 | /.mocharc.json 14 | /.npmignore 15 | /.vscodeignore 16 | /.webpack.config.js 17 | /changelog.xml 18 | /tsconfig.json 19 | 20 | /.github/**/* 21 | /.vscode/ 22 | /.vscode-test/ 23 | /img/**/* 24 | /lib/**/* 25 | /node_modules/ 26 | /out/ 27 | /schemas/ 28 | /src/ 29 | /test/ 30 | /tmp-*/ 31 | /tools/ 32 | 33 | !/.github/ISSUE_TEMPLATE/bug-report.md 34 | !/img/logo-ltex.png 35 | !/lib/.keep 36 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.5.2/schema.json", 3 | "extends": [ 4 | "./node_modules/@repobuddy/biome/recommended.json" 5 | ], 6 | "files": { 7 | "ignore": [ 8 | "dist", 9 | "out", 10 | ".vscode-test" 11 | ], 12 | "include": [ 13 | "src", 14 | "**/*.json" 15 | ] 16 | }, 17 | "formatter": { 18 | "lineEnding": "lf" 19 | }, 20 | "linter": { 21 | "enabled": true, 22 | "rules": { 23 | "suspicious": { 24 | "noExplicitAny": "off" 25 | } 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2019-2021 Julian Valentin, LTeX Development Community 2 | # 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 | 7 | version: 2 8 | updates: 9 | - package-ecosystem: "npm" 10 | directory: "/" 11 | schedule: 12 | interval: "weekly" 13 | day: "sunday" 14 | open-pull-requests-limit: 10 15 | assignees: 16 | - "valentjn" 17 | labels: 18 | - "pr-dependabot 🤖" 19 | -------------------------------------------------------------------------------- /src/Coc.ts: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2019-2021 Julian Valentin, LTeX Development Community 2 | * 3 | * This Source Code Form is subject to the terms of the Mozilla Public 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this 5 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 | */ 7 | 8 | // #if TARGET == 'coc.nvim' 9 | // // seems to be missing in coc.nvim typings 10 | // declare module 'coc.nvim' { 11 | // enum ConfigurationTarget { 12 | // Global, 13 | // User, 14 | // Workspace, 15 | // // not supported by coc.nvim, needs to be caught before calling coc.nvim functions 16 | // WorkspaceFolder, 17 | // } 18 | // } 19 | // #endif 20 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | Explain what problem you are trying to fix, and what your changes do in order to fix it. 4 | 5 | Link related issues or pull requests. 6 | 7 | ## Checklist 8 | 9 | * [ ] Have you read the [guidelines on contributing code to LTEX](https://valentjn.github.io/ltex/vscode-ltex/contributing.html#how-to-contribute-code)? 10 | * [ ] Have you filled in all missing information in this pull request template? 11 | * [ ] Have you written new tests for your changes, if applicable? 12 | * [ ] Do your changes pass all [code checks](https://valentjn.github.io/ltex/vscode-ltex/contributing.html#code-checks) such as linting and tests? 13 | -------------------------------------------------------------------------------- /walkthrough/en/readDocumentation.md: -------------------------------------------------------------------------------- 1 | # Read Documentation 2 | 3 | Be sure to check the documentation if you want to learn more about LTEX's advanced features. These include 4 | 5 | - external setting files to save dictionaries in workspace folders, 6 | - magic comments to change the language in the middle of a file, 7 | - support for LATEX's babel package, 8 | - and many more. 9 | 10 | The documentation also includes a list of frequently asked questions (FAQ) and information about settings, commands, and recent changes of LTEX. 11 | 12 | Click on the *Open Documentation* button to open the documentation of LTEX in a new browser window. 13 | -------------------------------------------------------------------------------- /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "README.md" 4 | ], 5 | "imageSize": 100, 6 | "commit": false, 7 | "commitType": "docs", 8 | "commitConvention": "angular", 9 | "contributors": [ 10 | { 11 | "login": "valentjn", 12 | "name": "Julian Valentin", 13 | "avatar_url": "https://avatars.githubusercontent.com/u/19839841?v=4", 14 | "profile": "https://valentjn.github.io", 15 | "contributions": [ 16 | "code" 17 | ] 18 | } 19 | ], 20 | "contributorsPerLine": 7, 21 | "skipCi": true, 22 | "repoType": "github", 23 | "repoHost": "https://github.com", 24 | "projectName": "vscode-ltex", 25 | "projectOwner": "neo-ltex" 26 | } 27 | -------------------------------------------------------------------------------- /tools/LanguageToolLanguageLister.java: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2019-2021 Julian Valentin, LTeX Development Community 2 | * 3 | * This Source Code Form is subject to the terms of the Mozilla Public 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this 5 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 | */ 7 | 8 | import org.languagetool.Language; 9 | import org.languagetool.Languages; 10 | 11 | public class LanguageToolLanguageLister { 12 | public static void main(String[] args) { 13 | for (Language language : Languages.get()) { 14 | System.out.println(language.getShortCodeWithCountryAndVariant() + ";" + language.getName()); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/test/ProgressStack.test.ts: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2019-2021 Julian Valentin, LTeX Development Community 2 | * 3 | * This Source Code Form is subject to the terms of the Mozilla Public 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this 5 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 | */ 7 | 8 | // #if TARGET == 'vscode' 9 | import * as Assert from 'assert'; 10 | 11 | import ProgressStack from '../ProgressStack'; 12 | 13 | describe('Test ProgressStack', () => { 14 | it('Test finishTask()', () => { 15 | const progressStack: ProgressStack = new ProgressStack('Test', {report(): void {}}); 16 | Assert.throws(progressStack.finishTask); 17 | }); 18 | }); 19 | // #endif 20 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "Watch extension", 6 | "type": "shell", 7 | "command": "npm", 8 | "args": [ 9 | "run", 10 | "webpack-dev" 11 | ], 12 | "presentation": { 13 | "reveal": "silent" 14 | }, 15 | "isBackground": true, 16 | "problemMatcher": "$tsc-watch" 17 | }, 18 | { 19 | "label": "Build extension", 20 | "type": "shell", 21 | "command": "npm", 22 | "args": [ 23 | "run", 24 | "webpack" 25 | ], 26 | "presentation": { 27 | "reveal": "silent" 28 | }, 29 | "isBackground": false, 30 | "problemMatcher": "$tsc-watch", 31 | "group": { 32 | "kind": "build", 33 | "isDefault": true 34 | } 35 | } 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /walkthrough/de/readDocumentation.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Dokumentation lesen 4 | 5 | Lesen Sie die Dokumentation, wenn Sie mehr über LTEXs fortgeschrittene Funktionen erfahren möchten. Unter diesen sind 6 | 7 | - externe Einstellungsdateien, um Wörterbücher in Arbeitsbereichs-Ordnern zu speichern, 8 | - magische Kommentare, um die Sprache mitten in einer Datei zu ändern, 9 | - Unterstützung für LATEXs babel-Paket, 10 | - und viele mehr. 11 | 12 | Die Dokumentation beinhaltet außerdem eine Liste von häufig gestellten Fragen (FAQ) und Informationen über Einstellungen, Befehle und kürzlich erfolgten Änderungen von LTEX. 13 | 14 | Klicken Sie auf den Knopf *Dokumentation öffnen,* um die Dokumentation von LTEX in einem neuen Browserfenster zu öffnen. 15 | -------------------------------------------------------------------------------- /src/test/TestTools.ts: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2019-2021 Julian Valentin, LTeX Development Community 2 | * 3 | * This Source Code Form is subject to the terms of the Mozilla Public 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this 5 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 | */ 7 | 8 | // #if TARGET == 'vscode' 9 | import * as Code from 'vscode'; 10 | 11 | export default class TestTools { 12 | public static async createNewFile(codeLanguage: string, contents?: string): 13 | Promise { 14 | return await Code.workspace.openTextDocument({language: codeLanguage, content: contents}); 15 | } 16 | 17 | public static async sleep(ms: number): Promise { 18 | return new Promise((resolve: () => void) => setTimeout(resolve, ms)); 19 | } 20 | } 21 | // #endif 22 | -------------------------------------------------------------------------------- /walkthrough/en/checkLatexDocuments.md: -------------------------------------------------------------------------------- 1 | # Check LATEX Documents 2 | 3 | To check documents in a code language like LATEX that is not supported by VS Code out-of-the-box, you have to install an extension that provides VS Code support for that language. 4 | 5 | For example, to check LATEX documents, install the *LaTeX Workshop* extension by clicking on the *Install LaTeX Workshop Extension* button. 6 | 7 | After you have installed *LaTeX Workshop,* click on the button *Open LaTeX Example* to open the following example document in a new editor. LTEX will underline two errors after a short period of time. 8 | 9 | ```latex 10 | \section{\LaTeX{} Example} 11 | 12 | This is a sentence \emph{without any errors.} 13 | This is a sentence \emph{with a speling error.} 14 | Finally, this is a sentence \emph{with an grammar error.} 15 | ``` 16 | -------------------------------------------------------------------------------- /walkthrough/en/customizeSettings.md: -------------------------------------------------------------------------------- 1 | # Customize Settings 2 | 3 | LTEX features lots of settings, which can be used to customize the checking experience. 4 | 5 | Click on the *Open LTeX settings* button to open the settings of LTEX. You can do that manually by running the command *Preferences: Open Settings (UI)* in the Command Palette (`Ctrl+Shift+P`) and selecting *Extensions* › *LTeX* on the left side. 6 | 7 | Some settings are: 8 | 9 | - [`ltex.language`](https://valentjn.github.io/ltex/settings.html#ltexlanguage) sets the language in which the documents are checked. The default language is English. 10 | - [`ltex.latex.commands`](https://valentjn.github.io/ltex/settings.html#ltexlatexcommands) allows ignoring custom LATEX commands. 11 | - There are many more settings. [Check the extensive documentation for detailed descriptions.](https://valentjn.github.io/ltex/settings.html) 12 | -------------------------------------------------------------------------------- /walkthrough/en/getInvolved.md: -------------------------------------------------------------------------------- 1 | # Get Involved 2 | 3 | If you like LTEX, you can show your appreciation by starring the GitHub repository or leaving a positive review on the VS Marketplace or on Open VSX. 4 | 5 | Click on *Open GitHub Page* to open the page of the GitHub repository in a new browser window. Click on *Leave Review on VS Marketplace* or *Leave Review on Open VSX* to open the VS Marketplace page or the Open VSX page. 6 | 7 | On GitHub, you get involved in LTEX in various ways: 8 | 9 | - You can ask questions about LTEX on the Discussions page. 10 | - You can report bugs to help make LTEX better. 11 | - You can request features to make LTEX more powerful. 12 | 13 | If you want to report bugs, request features, or otherwise contribute to LTEX, [check the documentation on contributing to LTEX first](https://valentjn.github.io/ltex/vscode-ltex/contributing.html). 14 | -------------------------------------------------------------------------------- /walkthrough/en/applyQuickFixes.md: -------------------------------------------------------------------------------- 1 | # Apply Quick Fixes 2 | 3 | When moving your mouse over an error, LTEX shows a message with details. 4 | 5 | For example, when you hover over `speling` in this Markdown example… 6 | 7 | ```markdown 8 | This is a sentence *with a speling error in it.* 9 | ``` 10 | 11 | … LTEX will show the following message: 12 | 13 | ```plaintext 14 | 'speling': Possible spelling mistake found. – MORFOLOGIK_RULE_EN_US 15 | ``` 16 | 17 | You can fix the error by selecting *Quick Fix...* below the message. For the example above, the following quick fixes will be shown: 18 | 19 | - Use 'spelling' 20 | - Use 'spewing' 21 | - Use 'spieling' 22 | - Add 'speling' to dictionary 23 | - Hide false positive 24 | - Disable rule 25 | 26 | Tip: If the text editor cursor is at an error, you can press `Ctrl+.` (Control and period keys) to show the menu of available quick fixes without having to move the mouse. 27 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | { 3 | "version": "0.1.0", 4 | "configurations": [ 5 | { 6 | "name": "Launch extension", 7 | "type": "extensionHost", 8 | "request": "launch", 9 | "runtimeExecutable": "${execPath}", 10 | "args": ["--extensionDevelopmentPath=${workspaceRoot}" ], 11 | "stopOnEntry": false, 12 | "sourceMaps": true, 13 | "outFiles": [ "${workspaceRoot}/dist/**/*.js" ], 14 | "preLaunchTask": "Build extension" 15 | }, 16 | { 17 | "name": "Launch tests", 18 | "type": "extensionHost", 19 | "request": "launch", 20 | "runtimeExecutable": "${execPath}", 21 | "args": ["--extensionDevelopmentPath=${workspaceRoot}", "--extensionTestsPath=${workspaceRoot}/out/test" ], 22 | "stopOnEntry": false, 23 | "sourceMaps": true, 24 | "outFiles": [ "${workspaceRoot}/out/test/**/*.js" ], 25 | "preLaunchTask": "Build extension" 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /.github/workflows/label-commenter.yml: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2019-2021 Julian Valentin, LTeX Development Community 2 | # 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 | 7 | name: "Label Commenter" 8 | 9 | on: 10 | issues: 11 | types: 12 | - "labeled" 13 | - "unlabeled" 14 | pull_request_target: 15 | types: 16 | - "labeled" 17 | - "unlabeled" 18 | 19 | jobs: 20 | comment: 21 | name: "Label Commenter Job" 22 | runs-on: "ubuntu-20.04" 23 | steps: 24 | - uses: "actions/checkout@v2" 25 | with: 26 | ref: "develop" 27 | 28 | - name: "Run Label Commenter" 29 | uses: "peaceiris/actions-label-commenter@v1.10.0" 30 | with: 31 | github_token: "${{ secrets.GITHUB_TOKEN }}" 32 | config_file: ".github/label-commenter-config.yml" 33 | -------------------------------------------------------------------------------- /ACKNOWLEDGMENTS.md: -------------------------------------------------------------------------------- 1 | 8 | 9 | # Acknowledgments 10 | 11 | ## LanguageTool Extension for Visual Studio Code 12 | 13 | As a fork of the abandoned [LanguageTool for Visual Studio Code extension](https://github.com/adamvoss/vscode-languagetool), this extension would not have been possible without the work of Adam Voss. 14 | 15 | ## ltex-ls 16 | 17 | Distributions of LTEX include [ltex-ls](https://github.com/valentjn/ltex-ls) under the terms of the [Mozilla Public License, version 2.0](https://github.com/valentjn/ltex-ls/blob/release/LICENSE.md). For information on dependencies of ltex-ls, please see [ltex-ls/ACKNOWLEDGMENTS.md](https://github.com/valentjn/ltex-ls/blob/release/ACKNOWLEDGMENTS.md). 18 | -------------------------------------------------------------------------------- /walkthrough/de/checkLatexDocuments.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # LATEX-Dokumente prüfen 4 | 5 | Um Dokumente in einer Sprache wie LATEX, die standardmäßig nicht von VS Code unterstützt wird, zu überprüfen, müssen Sie eine Erweiterung installieren, die Unterstützung für diese Sprache in VS Code bereitstellt. 6 | 7 | Um zum Beispiel LATEX-Dokumente zu prüfen, installieren Sie die Erweiterung *LaTeX Workshop,* indem Sie auf den Knopf *LaTeX Workshop installieren* drücken. 8 | 9 | Nachdem Sie *LaTeX Workshop* installiert haben, klicken Sie auf den Knopf *LaTeX-Beispiel öffnen,* um das folgende Beispieldokument in einem neuen Editor zu öffnen. Nach einer kurzen Zeit wird LTEX zwei Fehler unterstreichen. 10 | 11 | ```latex 12 | \section{\LaTeX{} Example} 13 | 14 | This is a sentence \emph{without any errors.} 15 | This is a sentence \emph{with a speling error.} 16 | Finally, this is a sentence \emph{with an grammar error.} 17 | ``` 18 | -------------------------------------------------------------------------------- /walkthrough/en/checkMarkdownDocuments.md: -------------------------------------------------------------------------------- 1 | # Check Markdown Documents 2 | 3 | Checking Markdown documents is easy with LTEX. Just open them, and LTEX will underline spelling and grammar errors, if there are any. 4 | 5 | If you open a new untitled file, change the language mode to `markdown` by running the command *Change Language Mode* in the Command Palette (`Ctrl+Shift+P`). 6 | 7 | When started for the first time, LTEX downloads necessary components to the extension folder. [Read the documentation for offline installation methods.](https://valentjn.github.io/ltex/vscode-ltex/installation-usage-vscode-ltex.html#offline-installation) 8 | 9 | Click on the button *Open Markdown Example* to open the following example document in a new editor. LTEX will underline two errors after a short period of time. 10 | 11 | ```markdown 12 | # Markdown Example 13 | 14 | This is a sentence *without any errors.* 15 | This is a sentence *with a speling error.* 16 | Finally, this is a sentence *with an grammar error.* 17 | ``` 18 | -------------------------------------------------------------------------------- /walkthrough/de/customizeSettings.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Einstellungen anpassen 4 | 5 | LTEX bietet eine Vielzahl von Einstellungen, mit denen das Prüfverhalten angepasst werden kann. 6 | 7 | Klicken Sie auf den Knopf *LTeX-Einstellungen öffnen,* um die Einstellungen von LTEX zu öffnen. Sie können dies auch per Hand erledigen, indem Sie den Befehl *Einstellungen: Einstellungen öffnen (Benutzeroberfläche)* in der Befehlspalette (`Strg+Shift+P`) ausführen und auf der linken Seite auf *Erweiterungen* › *LTeX* klicken. 8 | 9 | Einige Einstellungen sind: 10 | 11 | - [`ltex.language`](https://valentjn.github.io/ltex/settings-de.html#ltexlanguage) bestimmt die Sprache, in der die Dokumente geprüft werden. Die Standardsprache ist Englisch. 12 | - [`ltex.latex.commands`](https://valentjn.github.io/ltex/settings-de.html#ltexlatexcommands) ermöglicht es, eigene LATEX-Befehle zu ignorieren. 13 | - Es gibt viele weitere Einstellungen. [Lesen Sie die ausführliche Dokumentation für weitere Informationen.](https://valentjn.github.io/ltex/settings-de.html) 14 | -------------------------------------------------------------------------------- /walkthrough/de/getInvolved.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Mitmachen 4 | 5 | Wenn Sie LTEX mögen, können Sie Ihre Wertschätzung zeigen, indem Sie dem GitHub-Repository einen Stern geben oder eine positive Bewertung auf VS Marketplace oder Open VSX hinterlassen. 6 | 7 | Klicken Sie auf den Knopf *GitHub-Seite öffnen,* um die Seite des GitHub-Repositorys in einem neuen Browserfenster zu öffnen. Klicken Sie auf den Knopf *Auf VS Marketplace bewerten* oder *Auf Open VSX bewerten,* um die Seite von VS Marketplace oder Open VSX zu öffnen. 8 | 9 | Auf GitHub können Sie bei LTEX auf verschiedene Art und Weise mitmachen: 10 | 11 | - Sie können Fragen über LTEX auf der Discussions-Seite stellen. 12 | - Sie können Fehler melden, um zu helfen, LTEX zu verbessern. 13 | - Sie können Funktionen vorschlagen, um LTEX leistungsfähiger zu machen. 14 | 15 | Wenn Sie Fehler melden, Funktionen vorschlagen oder anderweitig zu LTEX beitragen möchten, [lesen Sie zunächst die Dokumentation über Beiträge zu LTEX](https://valentjn.github.io/ltex/vscode-ltex/contributing.html). 16 | -------------------------------------------------------------------------------- /walkthrough/de/applyQuickFixes.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Schnelle Problembehebungen anwenden 4 | 5 | Wenn Sie mit der Maus über einen Fehler fahren, zeigt LTEX eine Meldung mit Details an. 6 | 7 | Wenn Sie zum Beispiel über `speling` in diesem Markdown-Beispiel fahren … 8 | 9 | ```markdown 10 | This is a sentence *with a speling error in it.* 11 | ``` 12 | 13 | … dann zeigt LTEX folgende Meldung an: 14 | 15 | ```plaintext 16 | 'speling': Possible spelling mistake found. – MORFOLOGIK_RULE_EN_US 17 | ``` 18 | 19 | Sie können den Fehler beheben, indem Sie auf *Schnelle Problembehebung ...* unterhalb der Meldung klicken. Im obigen Beispiel werden dann die folgenden schnellen Problembehebungen angezeigt: 20 | 21 | - 'spelling' verwenden 22 | - 'spewing' verwenden 23 | - 'spieling' verwenden 24 | - 'speling' zum Wörterbuch hinzufügen 25 | - Falschen Fehler verbergen 26 | - Regel deaktivieren 27 | 28 | Tipp: Wenn der Texteditor-Cursor bei einem Fehler ist, drücken Sie `Strg+.` (Steuerung- und Punkt-Tasten), um das Menü mit verfügbaren schnellen Problembehebungen anzuzeigen, ohne die Maus zu bewegen. 29 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | 3 | 'use strict'; 4 | 5 | const Path = require('path'); 6 | 7 | /**@type {import('webpack').Configuration}*/ 8 | const config = { 9 | target: 'node', 10 | mode: 'development', 11 | entry: './src/extension.ts', 12 | output: { 13 | path: Path.resolve(__dirname, 'dist'), 14 | filename: 'extension.js', 15 | libraryTarget: 'commonjs2', 16 | devtoolModuleFilenameTemplate: '../[resource-path]', 17 | }, 18 | devtool: 'source-map', 19 | externals: { 20 | 'coc.nvim': 'commonjs coc.nvim', 21 | vscode: 'commonjs vscode', 22 | }, 23 | resolve: { 24 | extensions: [ 25 | '.ts', 26 | '.js', 27 | ], 28 | }, 29 | module: { 30 | rules: [ 31 | { 32 | test: /\.ts$/, 33 | exclude: /node_modules/, 34 | use: [ 35 | { 36 | loader: 'ts-loader' 37 | }, 38 | ], 39 | }, 40 | ], 41 | }, 42 | optimization: { 43 | minimizer: [ 44 | () => ({ 45 | terserOptions: { 46 | extractComments: false, 47 | }, 48 | }), 49 | ], 50 | }, 51 | }; 52 | 53 | module.exports = config; 54 | -------------------------------------------------------------------------------- /walkthrough/de/checkMarkdownDocuments.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Markdown-Dokumente prüfen 4 | 5 | Es ist einfach, Markdown-Dokumente mit LTEX zu prüfen. Öffnen Sie sie einfach und LTEX wird alle Schreib- und Grammatikfehler unterstreichen, falls es welche gibt. 6 | 7 | Wenn Sie eine neue unbenannte Datei öffnen, ändern Sie den Sprachmodus zu `markdown`, indem Sie den Befehl *Sprachmodus ändern* in der Befehlspalette (`Strg+Shift+P`) verwenden. 8 | 9 | Beim ersten Start lädt LTEX notwendige Komponenten herunter und speichert sie im Erweiterungsordner. [Lesen Sie die Dokumentation für Offline-Installationsmethoden.](https://valentjn.github.io/ltex/vscode-ltex/installation-usage-vscode-ltex.html#offline-installation) 10 | 11 | Klicken Sie auf den Knopf *Markdown-Beispiel öffnen,* um das folgende Beispieldokument in einem neuen Editor zu öffnen. Nach einer kurzen Zeit wird LTEX zwei Fehler unterstreichen. 12 | 13 | ```markdown 14 | # Markdown Example 15 | 16 | This is a sentence *without any errors.* 17 | This is a sentence *with a speling error.* 18 | Finally, this is a sentence *with an grammar error.* 19 | ``` 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Feature Request" 3 | about: "Suggest a desirable, nice-to-have feature. See https://valentjn.github.io/ltex/vscode-ltex/contributing.html#how-to-request-features to learn how to request features." 4 | title: "" 5 | labels: "1-feature-request ✨" 6 | assignees: "" 7 | --- 8 | 9 | Note: Per the contribution guidelines, deleting parts of the template or not filling in vital information may result in the issue to be immediately closed as invalid. 10 | 11 | **Is your feature request related to a problem? Please describe.** 12 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 13 | Using actual real-world examples that explain why you and many other users would benefit from the feature increases the request's chances of being implemented. 14 | 15 | **Describe the solution you'd like** 16 | A clear and concise description of what you want to happen. 17 | 18 | **Describe alternatives you've considered** 19 | A clear and concise description of any alternative solutions or features you've considered. 20 | 21 | **Additional context** 22 | Add any other context or screenshots about the feature request here. 23 | -------------------------------------------------------------------------------- /src/test/index.ts: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2019-2021 Julian Valentin, LTeX Development Community 2 | * 3 | * This Source Code Form is subject to the terms of the Mozilla Public 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this 5 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 | */ 7 | 8 | import glob from 'glob'; 9 | import Mocha from 'mocha'; 10 | import * as Path from 'path'; 11 | 12 | export function run(): Promise { 13 | const mocha: Mocha = new Mocha({ 14 | ui: 'bdd', 15 | timeout: 300000, 16 | color: true, 17 | }); 18 | 19 | const testsRoot: string = Path.resolve(__dirname, '..'); 20 | 21 | return new Promise((resolve: () => void, reject: (reason?: any) => void) => { 22 | glob('**/**.test.js', {cwd: testsRoot}, (e: Error | null, files: string[]) => { 23 | if (e != null) { 24 | return reject(e); 25 | } 26 | 27 | files.forEach((x: string) => mocha.addFile(Path.resolve(testsRoot, x))); 28 | 29 | try { 30 | mocha.run((failures: number): void => { 31 | if (failures > 0) { 32 | reject(new Error(`${failures} tests failed.`)); 33 | } else { 34 | resolve(); 35 | } 36 | }); 37 | } catch (e: unknown) { 38 | console.error(e); 39 | reject(e); 40 | } 41 | }); 42 | }); 43 | } 44 | -------------------------------------------------------------------------------- /src/test/Logger.test.ts: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2019-2021 Julian Valentin, LTeX Development Community 2 | * 3 | * This Source Code Form is subject to the terms of the Mozilla Public 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this 5 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 | */ 7 | 8 | // #if TARGET == 'vscode' 9 | import * as Assert from 'assert'; 10 | 11 | import Logger from '../Logger'; 12 | import LoggingOutputChannel from '../LoggingOutputChannel'; 13 | 14 | describe('Test Logger', () => { 15 | before(() => { 16 | const clientOutputChannel: LoggingOutputChannel = 17 | new LoggingOutputChannel('LTeX Language Client'); 18 | const serverOutputChannel: LoggingOutputChannel = 19 | new LoggingOutputChannel('LTeX Language Server'); 20 | Logger.clientOutputChannel = clientOutputChannel; 21 | Logger.serverOutputChannel = serverOutputChannel; 22 | Assert.strictEqual(Logger.clientOutputChannel, clientOutputChannel); 23 | Assert.strictEqual(Logger.serverOutputChannel, serverOutputChannel); 24 | }); 25 | 26 | it('Test log()', () => { 27 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 28 | // @ts-ignore 29 | Logger.log(null); 30 | }); 31 | 32 | it('Test warn()', () => { 33 | Logger.clientOutputChannel.clear(); 34 | Logger.warn('Test1'); 35 | Logger.warn('Test2', new Error('Error1')); 36 | const log: string = Logger.clientOutputChannel.content; 37 | Assert.ok(log.includes('Warning')); 38 | Assert.ok(log.includes('Test1')); 39 | Assert.ok(log.includes('Test2')); 40 | Assert.ok(log.includes('Error1')); 41 | }); 42 | 43 | it('Test error()', () => { 44 | Logger.clientOutputChannel.clear(); 45 | Logger.error('Test1'); 46 | Logger.error('Test2', new Error('Error1')); 47 | const log: string = Logger.clientOutputChannel.content; 48 | Assert.ok(log.includes('Error')); 49 | Assert.ok(log.includes('Test1')); 50 | Assert.ok(log.includes('Test2')); 51 | Assert.ok(log.includes('Error1')); 52 | }); 53 | 54 | it('Test showClientOutputChannel()', () => { 55 | Logger.showClientOutputChannel(); 56 | }); 57 | }); 58 | // #endif 59 | -------------------------------------------------------------------------------- /src/test/Settings.test.ts: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2019-2021 Julian Valentin, LTeX Development Community 2 | * 3 | * This Source Code Form is subject to the terms of the Mozilla Public 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this 5 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 | */ 7 | 8 | // #if TARGET == 'vscode' 9 | import * as Assert from 'assert'; 10 | import * as Code from 'vscode'; 11 | 12 | import ExtensionInitializer from './ExtensionInitializer'; 13 | import TestTools from './TestTools'; 14 | 15 | describe('Test settings', () => { 16 | before(async () => { 17 | await ExtensionInitializer.initialize(); 18 | }); 19 | 20 | beforeEach(async () => { 21 | await ExtensionInitializer.resetState(); 22 | }); 23 | 24 | it('Test deletion of diagnostics when closing files', async () => { 25 | const document: Code.TextDocument = await TestTools.createNewFile('latex', 26 | 'This is an \\textbf{test}.'); 27 | await TestTools.sleep(5000); 28 | 29 | Assert.strictEqual(Code.languages.getDiagnostics(document.uri).length, 1); 30 | 31 | await Code.commands.executeCommand('workbench.action.closeActiveEditor'); 32 | await TestTools.sleep(1000); 33 | 34 | Assert.strictEqual(Code.languages.getDiagnostics(document.uri).length, 0); 35 | }); 36 | 37 | it('Test ltex.clearDiagnosticsWhenClosingFile', async () => { 38 | try { 39 | Code.workspace.getConfiguration('ltex').update('clearDiagnosticsWhenClosingFile', false, 40 | Code.ConfigurationTarget.Global); 41 | const document: Code.TextDocument = await TestTools.createNewFile('latex', 42 | 'This is an \\textbf{test}.'); 43 | await TestTools.sleep(5000); 44 | 45 | Assert.strictEqual(Code.languages.getDiagnostics(document.uri).length, 1); 46 | 47 | await Code.commands.executeCommand('workbench.action.closeActiveEditor'); 48 | await TestTools.sleep(1000); 49 | 50 | Assert.strictEqual(Code.languages.getDiagnostics(document.uri).length, 1); 51 | } finally { 52 | Code.workspace.getConfiguration('ltex').update( 53 | 'clearDiagnosticsWhenClosingFile', undefined, Code.ConfigurationTarget.Global); 54 | } 55 | }); 56 | }); 57 | // #endif 58 | -------------------------------------------------------------------------------- /.github/label-commenter-config.yml: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2019-2021 Julian Valentin, LTeX Development Community 2 | # 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 | 7 | labels: 8 | - name: "3-fixed" 9 | labeled: 10 | issue: 11 | body: "This issue is now fixed on `develop`. The fix will be included in the next release of LTEX.\n\nIf you don't want to wait, you can [try out the nightly pre-release](https://valentjn.github.io/ltex/vscode-ltex/contributing.html#how-to-test-pre-releases) tomorrow. Nightly pre-releases are published every morning at around 4am UTC." 12 | action: "close" 13 | 14 | - name: "3-invalid" 15 | labeled: 16 | issue: 17 | body: "Hi! Per the [maintainer guidelines](https://github.com/valentjn/ltex-ls/blob/develop/MAINTAINING.md#guidelines-about-issues), this issue is automatically closed because it is invalid. The most common reason is that it does not follow the issue template, or it misses vital information from the template. Please see the documentation to learn how to properly report [bugs](https://valentjn.github.io/ltex/vscode-ltex/contributing.html#how-to-report-bugs) or [feature requests](https://valentjn.github.io/ltex/vscode-ltex/contributing.html#how-to-request-features). Edit the issue description to follow the template or to include the missing information. Thank you!" 18 | action: "close" 19 | unlabeled: 20 | issue: 21 | body: "This issue is not invalid anymore. Reopening." 22 | action: "open" 23 | 24 | - name: "3-stale" 25 | labeled: 26 | issue: 27 | body: "Hi! Per the [maintainer guidelines](https://github.com/valentjn/ltex-ls/blob/develop/MAINTAINING.md#guidelines-about-issues), this issue is automatically closed because it has been stale for too long. The most common reason for an issue to become stale is missing information from its submitter (see one of the previous comments). If the missing information is provided, the issue will be reopened again. Thank you!" 28 | action: "close" 29 | unlabeled: 30 | issue: 31 | body: "This issue is not stale anymore. Reopening." 32 | action: "open" 33 | -------------------------------------------------------------------------------- /MAINTAINING.md: -------------------------------------------------------------------------------- 1 | 8 | 9 | # Maintainer Guidelines 10 | 11 | ## Guidelines About Issues 12 | 13 | - **Stale issues** are closed seven days after they became stale. An issue becomes stale if: 14 | - The issue is missing information. 15 | - The issue is declared stale by a maintainer. 16 | - **Discussions** may happen on any issue regardless of its state. Discussions don't prolong the lifetime of stale issues, and they must be about the original issue, otherwise a new issue must be opened. 17 | - Issues may only be **reopened** if the reason for closing it doesn't exist anymore. 18 | - *Examples:* Missing information is provided, bug occurs again due to a regression, etc. 19 | - Issue comments that do not contribute to the discussion (e.g., “I'm affected, too” on confirmed bug reports) may be hidden. Instead, reactions (e.g., thumbs-up) should be used to upvote issues. 20 | - Issues that don't follow the **template** may be closed immediately. 21 | - Issues may be **locked** if they violate the [code of conduct](https://valentjn.github.io/ltex/code-of-conduct.html). 22 | 23 | ## Guidelines About Versioning 24 | 25 | - **Semantic versioning** is used for vscode-ltex and ltex-ls. The two versions are independent of each other. 26 | - For bug fixes, the patch version is increased. 27 | - For new features, the minor version is increased. 28 | - For breaking changes, the major version is increased. 29 | - *Explanation:* Breaking changes are changes that may require action from users (e.g., most changes of existing LTEX settings). 30 | 31 | ## Guidelines About Fundamental Changes 32 | 33 | - **Fundamental changes** are announced as an issue, in the documentation, and/or as message boxes in VS Code three months before their implementation. 34 | - *Explanation:* Fundamental changes are possibly breaking changes that change the foundation of LTEX (e.g., upgrade from Java 8 to Java 11). 35 | - **Documentation of fundamental changes** and associated deprecated settings may be deleted three months after their implementation. 36 | -------------------------------------------------------------------------------- /tools/copyChangelogToGitHubReleases.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # Copyright (C) 2019-2021 Julian Valentin, LTeX Development Community 4 | # 5 | # This Source Code Form is subject to the terms of the Mozilla Public 6 | # License, v. 2.0. If a copy of the MPL was not distributed with this 7 | # file, You can obtain one at https://mozilla.org/MPL/2.0/. 8 | 9 | import json 10 | import pathlib 11 | import sys 12 | from typing import cast, List 13 | import urllib.parse 14 | import xml.etree.ElementTree as et 15 | 16 | import semver 17 | 18 | sys.path.append(str(pathlib.Path(__file__).parent)) 19 | import common 20 | import convertChangelog 21 | 22 | 23 | 24 | def getVersions(changelogFilePath: pathlib.Path) -> List[str]: 25 | document = et.parse(changelogFilePath).getroot() 26 | releases = document.findall("./{http://maven.apache.org/changes/1.0.0}body" 27 | "/{http://maven.apache.org/changes/1.0.0}release") 28 | return [x.attrib["version"] for x in releases if (x.attrib["date"] != "upcoming") and 29 | (semver.VersionInfo.parse(x.attrib["version"]).major >= 4)] 30 | 31 | 32 | 33 | def getReleaseIdOfGitHubRelease(version: str) -> int: 34 | apiUrl = (f"https://api.github.com/repos/{urllib.parse.quote_plus(common.organization)}/" 35 | f"{urllib.parse.quote_plus(common.repository)}/releases/tags/" 36 | f"{urllib.parse.quote_plus(version)}") 37 | response = common.requestFromGitHub(apiUrl) 38 | return cast(int, response["id"]) 39 | 40 | 41 | 42 | def updateDescriptionOfGitHubRelease(releaseId: int, description: str) -> None: 43 | apiUrl = (f"https://api.github.com/repos/{urllib.parse.quote_plus(common.organization)}/" 44 | f"{urllib.parse.quote_plus(common.repository)}/releases/{releaseId}") 45 | common.requestFromGitHub(apiUrl, method="PATCH", data=json.dumps({"body": description})) 46 | 47 | 48 | 49 | def main() -> None: 50 | changelogFilePath = pathlib.Path(pathlib.Path(__file__).parent.parent.joinpath("changelog.xml")) 51 | versions = getVersions(changelogFilePath) 52 | 53 | for version in versions: 54 | print(f"Processing version '{version}'...") 55 | description = convertChangelog.convertChangelogFromXmlToMarkdown(changelogFilePath, version) 56 | print(description) 57 | releaseId = getReleaseIdOfGitHubRelease(version) 58 | updateDescriptionOfGitHubRelease(releaseId, description) 59 | 60 | 61 | 62 | if __name__ == "__main__": 63 | main() 64 | -------------------------------------------------------------------------------- /src/test/extension.test.ts: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2019-2021 Julian Valentin, LTeX Development Community 2 | * 3 | * This Source Code Form is subject to the terms of the Mozilla Public 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this 5 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 | */ 7 | 8 | // #if TARGET == 'vscode' 9 | import * as Assert from 'assert'; 10 | import * as Code from 'vscode'; 11 | 12 | import ExtensionInitializer from './ExtensionInitializer'; 13 | import TestTools from './TestTools'; 14 | 15 | describe('Test extension (end-to-end)', () => { 16 | async function assertCheckingResult(document: Code.TextDocument): Promise { 17 | await TestTools.sleep(5000); 18 | const diagnostics: Code.Diagnostic[] = Code.languages.getDiagnostics(document.uri); 19 | Assert.strictEqual(diagnostics.length, 1); 20 | Assert.strictEqual(diagnostics[0].source, 'LTeX'); 21 | } 22 | 23 | before(async () => { 24 | await ExtensionInitializer.initialize(); 25 | }); 26 | 27 | beforeEach(async () => { 28 | await ExtensionInitializer.resetState(); 29 | }); 30 | 31 | it('Test checking of Markdown files', async () => { 32 | const document: Code.TextDocument = await TestTools.createNewFile('markdown', 33 | 'This is an *test*.'); 34 | return assertCheckingResult(document); 35 | }); 36 | 37 | it('Test checking of LaTeX files', async () => { 38 | const document: Code.TextDocument = await TestTools.createNewFile('latex', 39 | 'This is an \\textbf{test}.'); 40 | return assertCheckingResult(document); 41 | }); 42 | 43 | it('Test arrays in ltex.enabled', async () => { 44 | try { 45 | Code.workspace.getConfiguration('ltex').update('enabled', ['markdown'], 46 | Code.ConfigurationTarget.Global); 47 | 48 | const markdownDocument: Code.TextDocument = await TestTools.createNewFile('markdown', 49 | 'This is an *test*.'); 50 | await assertCheckingResult(markdownDocument); 51 | 52 | const latexDocument: Code.TextDocument = await TestTools.createNewFile('latex', 53 | 'This is an *test*.'); 54 | await TestTools.sleep(5000); 55 | const diagnostics: Code.Diagnostic[] = Code.languages.getDiagnostics(latexDocument.uri); 56 | Assert.strictEqual(diagnostics.length, 0); 57 | } finally { 58 | Code.workspace.getConfiguration('ltex').update('enabled', undefined, 59 | Code.ConfigurationTarget.Global); 60 | } 61 | }); 62 | }); 63 | // #endif 64 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "es6": true, 5 | "node": true, 6 | }, 7 | "extends": [ 8 | "eslint:recommended", 9 | "plugin:@typescript-eslint/eslint-recommended", 10 | "plugin:@typescript-eslint/recommended", 11 | "google", 12 | ], 13 | "globals": { 14 | "Atomics": "readonly", 15 | "SharedArrayBuffer": "readonly", 16 | }, 17 | "parser": "@typescript-eslint/parser", 18 | "parserOptions": { 19 | "ecmaVersion": 11, 20 | "project": "./tsconfig.json", 21 | "sourceType": "module", 22 | }, 23 | "plugins": [ 24 | "@typescript-eslint", 25 | "only-warn", 26 | ], 27 | "rules": { 28 | "@typescript-eslint/explicit-module-boundary-types": [ 29 | "off", 30 | ], 31 | "@typescript-eslint/no-empty-function": [ 32 | "off", 33 | ], 34 | "@typescript-eslint/no-explicit-any": [ 35 | "off", 36 | ], 37 | "@typescript-eslint/no-inferrable-types": [ 38 | "off", 39 | ], 40 | "@typescript-eslint/no-non-null-assertion": [ 41 | "off", 42 | ], 43 | "@typescript-eslint/no-unused-vars": [ 44 | "warn", 45 | { 46 | "argsIgnorePattern": "^_", 47 | }, 48 | ], 49 | "@typescript-eslint/typedef": [ 50 | "warn", 51 | { 52 | "arrayDestructuring": true, 53 | "arrowParameter": true, 54 | "memberVariableDeclaration": true, 55 | "objectDestructuring": true, 56 | "parameter": true, 57 | "propertyDeclaration": true, 58 | "variableDeclaration": true, 59 | "variableDeclarationIgnoreFunction": true, 60 | }, 61 | ], 62 | "indent": [ 63 | "off", 64 | ], 65 | "linebreak-style": [ 66 | "warn", 67 | (require("os").EOL === "\r\n" ? "windows" : "unix"), 68 | ], 69 | "max-len": [ 70 | "warn", 71 | 100, 72 | ], 73 | "no-constant-condition": [ 74 | "warn", 75 | { 76 | "checkLoops": false, 77 | }, 78 | ], 79 | "no-unused-vars": [ 80 | "off", 81 | ], 82 | "operator-linebreak": [ 83 | "warn", 84 | "before", 85 | { 86 | "overrides": { 87 | "=": "after", 88 | "+=": "after", 89 | "-=": "after", 90 | }, 91 | }, 92 | ], 93 | "padded-blocks": [ 94 | "off", 95 | ], 96 | "quotes": [ 97 | "warn", 98 | "single", 99 | ], 100 | "require-jsdoc": [ 101 | "off", 102 | ], 103 | "semi": [ 104 | "warn", 105 | "always", 106 | ], 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /tools/common.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # Copyright (C) 2019-2021 Julian Valentin, LTeX Development Community 4 | # 5 | # This Source Code Form is subject to the terms of the Mozilla Public 6 | # License, v. 2.0. If a copy of the MPL was not distributed with this 7 | # file, You can obtain one at https://mozilla.org/MPL/2.0/. 8 | 9 | import json 10 | import os 11 | import pathlib 12 | import re 13 | import subprocess 14 | import sys 15 | import traceback 16 | from typing import Any, Optional, Tuple 17 | import urllib.parse 18 | import urllib.request 19 | 20 | 21 | 22 | repoDirPath = pathlib.Path(__file__).parent.parent 23 | 24 | 25 | 26 | def getToBeDownloadedVersions() -> Tuple[str, str]: 27 | with open(repoDirPath.joinpath("src", "DependencyManager.ts"), "r") as f: 28 | dependencyManagerTypescript = f.read() 29 | 30 | matches = re.findall(r"_toBeDownloadedLtexLsTag: string =\n *'(.*?)';", 31 | dependencyManagerTypescript) 32 | assert len(matches) == 1 33 | toBeDownloadedLtexLsTag = matches[0] 34 | 35 | matches = re.findall(r"_toBeDownloadedLtexLsVersion: string =\n *'(.*?)';", 36 | dependencyManagerTypescript) 37 | assert len(matches) == 1 38 | toBeDownloadedLtexLsVersion = matches[0] 39 | 40 | return toBeDownloadedLtexLsTag, toBeDownloadedLtexLsVersion 41 | 42 | toBeDownloadedLtexLsTag, toBeDownloadedLtexLsVersion = getToBeDownloadedVersions() 43 | 44 | 45 | 46 | def getGitHubOrganizationRepository() -> Tuple[str, str]: 47 | output = subprocess.run(["git", "remote", "get-url", "origin"], 48 | cwd=pathlib.Path(__file__).parent.parent, stdout=subprocess.PIPE).stdout.decode() 49 | regexMatch = re.search(r"github.com[:/](.*?)/(.*?)(?:\.git)?$", output) 50 | assert regexMatch is not None, output 51 | organization, repository = regexMatch.group(1), regexMatch.group(2) 52 | return organization, repository 53 | 54 | organization, repository = getGitHubOrganizationRepository() 55 | 56 | 57 | 58 | def requestFromGitHub(url: str, decodeAsJson: bool = True, 59 | method: Optional[str] = None, data: Optional[str] = None) -> Any: 60 | headers = {} 61 | 62 | if "LTEX_GITHUB_OAUTH_TOKEN" in os.environ: 63 | headers["Authorization"] = "token {}".format(os.environ["LTEX_GITHUB_OAUTH_TOKEN"]) 64 | 65 | apiRequest = urllib.request.Request(url, headers=headers, 66 | data=(None if data is None else data.encode()), method=method) 67 | 68 | try: 69 | with urllib.request.urlopen(apiRequest) as f: response = f.read() 70 | except urllib.error.HTTPError as e: 71 | traceback.print_exc() 72 | print("Response body: \"{!r}\"".format(e.read())) 73 | sys.exit(1) 74 | 75 | if decodeAsJson: response = json.loads(response) 76 | return response 77 | -------------------------------------------------------------------------------- /i18n/messages.nls.de.json: -------------------------------------------------------------------------------- 1 | { 2 | "checkingAllDocumentsInWorkspace": "Prüfe alle Dokumente im Arbeitsbereich ...", 3 | "checkingDocumentN": "Prüfe Dokument {0}/{1} ... ({2})", 4 | "copyReportAndCreateIssue": "Bericht kopieren und Problem erstellen", 5 | "couldNotCheckDocument": "Prüfung des Dokuments '{0}' fehlgeschlagen: '{1}'", 6 | "couldNotCheckDocumentsAsNoDocumentsWereFound": "Prüfung der Dokumente fehlgeschlagen, da keine *.md- oder *.tex-Dokumente gefunden wurden. Nur Dokumente, die in einem der geöffneten Arbeitsbereichs-Ordner gespeichert sind, können überprüft werden. [Mehr Informationen ...](https://valentjn.github.io/ltex/vscode-ltex/commands.html#ltex-check-all-documents-in-workspace)", 7 | "couldNotCheckDocumentsAsNoFoldersWereOpened": "Prüfung der Dokumente fehlgeschlagen, da keine Ordner im Arbeitsbereich geöffnet sind. Bitte öffnen Sie zuerst einen Ordner im Arbeitsbereich. [Mehr Informationen ...](https://valentjn.github.io/ltex/vscode-ltex/commands.html#ltex-check-all-documents-in-workspace)", 8 | "couldNotInstallLtexLs": "Installation von ltex-ls fehlgeschlagen, siehe das Protokoll 'LTeX Language Client' für mehr Informationen.", 9 | "couldNotRunLtexLs": "Ausführung von ltex-ls mit Java fehlgeschlagen, siehe das Protokoll 'LTeX Language Client' für mehr Informationen.", 10 | "couldNotStartLanguageClient": "Start des Language Clients fehlgeschlagen!", 11 | "downloading": "Lade {0} herunter ...", 12 | "downloadingAndExtractingLtexLs" : "Lade ltex-ls herunter und entpacke es ...", 13 | "extracting": "Entpacke {0} ...", 14 | "findingAllDocumentsInWorkspace": "Finde alle Dokumente im Arbeitsbereich ...", 15 | "ltexIsChecking": "LTeX prüft ...", 16 | "ltexLanguageServer": "LTeX Language Server", 17 | "ltexNotInitialized": "LTeX wurde aufgrund eines vorhergehenden Fehlers nicht initialisiert.", 18 | "ltexReady": "LTeX bereit", 19 | "ltexTraceServerSetToVerbose": "ltex.trace.server wurde in der Benutzer-Konfiguration auf \"verbose\" gesetzt.", 20 | "noEditorOpenToCheckDocument": "Kein Editor geöffnet, um ein Dokument prüfen zu können.", 21 | "offlineInstructions": "Offline-Installations-Anleitung", 22 | "setLtexTraceServerToVerbose": "ltex.trace.server auf \"verbose\" setzen", 23 | "startingLtex": "Starte LTeX ...", 24 | "startingLtexLs": "Starte ltex-ls ...", 25 | "thanksForReportingBug": "Vielen Dank dafür, dass Sie helfen, LTeX zu verbessern. Lesen Sie zunächst die [Anleitung darüber, wie man Bugs meldet](https://valentjn.github.io/ltex/vscode-ltex/contributing.html#how-to-report-bugs). Wenn Sie das Problem auf GitHub erstellen, fügen Sie den Bug-Bericht aus der Zwischenablage in die Beschreibung des Problems ein.", 26 | "tryAgain": "Nochmals versuchen", 27 | "verifying": "Verifiziere {0} ...", 28 | "youMightWantToTryOfflineInstallation": "Versuchen Sie, die Offline-Installationsmethode auszuführen." 29 | } 30 | -------------------------------------------------------------------------------- /src/ProgressStack.ts: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2019-2021 Julian Valentin, LTeX Development Community 2 | * 3 | * This Source Code Form is subject to the terms of the Mozilla Public 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this 5 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 | */ 7 | 8 | // #if TARGET == 'vscode' 9 | import * as Code from 'vscode'; 10 | // #elseif TARGET == 'coc.nvim' 11 | // import * as Code from 'coc.nvim'; 12 | // #endif 13 | 14 | import {i18n} from './I18n'; 15 | 16 | export default class ProgressStack { 17 | private _taskProgressStack: number[]; 18 | private _taskSizeStack: number[]; 19 | private _taskNameStack: string[]; 20 | private _progressInterface: Code.Progress<{increment?: number; message?: string}>; 21 | private _progressOnLastUpdate: number; 22 | 23 | public constructor(name: string, 24 | progressInterface: Code.Progress<{increment?: number; message?: string}>) { 25 | this._taskProgressStack = [0]; 26 | this._taskSizeStack = [1]; 27 | this._taskNameStack = [name]; 28 | this._progressInterface = progressInterface; 29 | this._progressOnLastUpdate = 0; 30 | } 31 | 32 | public startTask(size: number, name: string): void { 33 | this._taskProgressStack.push(0); 34 | this._taskSizeStack.push(size); 35 | this._taskNameStack.push(name); 36 | this.showProgress(); 37 | } 38 | 39 | public updateTask(progress: number, name?: string): void { 40 | const n: number = this._taskProgressStack.length; 41 | this._taskProgressStack[n - 1] = progress; 42 | if (name != null) this._taskNameStack[n - 1] = name; 43 | this.showProgress(); 44 | } 45 | 46 | public finishTask(): void { 47 | const n: number = this._taskProgressStack.length - 1; 48 | if (n == 0) throw Error(i18n('couldNotFinishTask')); 49 | this._taskProgressStack.pop(); 50 | this._taskProgressStack[n - 1] += this._taskSizeStack.pop() as number; 51 | this._taskNameStack.pop(); 52 | this.showProgress(); 53 | } 54 | 55 | public getTaskName(): string { 56 | const n: number = this._taskProgressStack.length; 57 | return this._taskNameStack[n - 1]; 58 | } 59 | 60 | public showProgress(): void { 61 | const n: number = this._taskProgressStack.length; 62 | let currentProgress: number = 0; 63 | let currentSize: number = 1; 64 | 65 | for (let i: number = 0; i < n; i++) { 66 | currentSize *= this._taskSizeStack[i]; 67 | currentProgress += currentSize * this._taskProgressStack[i]; 68 | } 69 | 70 | const progressIncrement: number = 100 * currentProgress - this._progressOnLastUpdate; 71 | const currentName: string = this._taskNameStack[n - 1]; 72 | this._progressInterface.report({increment: progressIncrement, message: currentName}); 73 | this._progressOnLastUpdate = 100 * currentProgress; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Bug Report" 3 | about: "Something isn't working as expected? Create a bug report. See https://valentjn.github.io/ltex/vscode-ltex/contributing.html#how-to-report-bugs to learn how to report bugs." 4 | title: "" 5 | labels: "1-bug 🐛, 2-unconfirmed" 6 | assignees: "" 7 | --- 8 | 9 | Note: It is highly recommended to follow the instructions at https://valentjn.github.io/ltex/vscode-ltex/contributing.html#how-to-report-bugs and use the `LTeX: Report bug in LTeX` command from within Visual Studio Code. Per the contribution guidelines, deleting parts of the template or not filling in vital information may result in the issue to be immediately closed as invalid. 10 | 11 | **Describe the bug** 12 | A clear and concise description of what the bug is. 13 | 14 | **Steps to reproduce** 15 | Steps to reproduce the behavior: 16 | 17 | 1. Go to "..." 18 | 2. Click on "..." 19 | 3. Scroll down to "..." 20 | 4. See error 21 | 22 | **Expected behavior** 23 | A clear and concise description of what you expected to happen. 24 | 25 | **Sample document** 26 | If the bug occurs for a specific document (e.g. LaTeX), please paste it here. If your document is very long or confidential, please create and attach a smaller example for which the bug still occurs so that we can reproduce it. 27 | 28 |
29 | 30 | ``` 31 | REPLACE_THIS_WITH_SAMPLE_DOCUMENT 32 | ``` 33 | 34 |
35 | 36 | **LTeX configuration** 37 | Please paste all configuration settings starting with `ltex.` from your `settings.json`. You can help us by temporarily removing some irrelevant settings from your `settings.json` and see if the bug still occurs. 38 | 39 |
40 | 41 | ``` 42 | REPLACE_THIS_WITH_LTEX_CONFIGURATION 43 | ``` 44 | 45 |
46 | 47 | **"LTeX Language Server" log file** 48 | First, reproduce the bug. Then, go to `View` → `Output` and select `LTeX Language Server` in the drop-down list. Paste this log here: 49 | 50 |
51 | 52 | ``` 53 | REPLACE_THIS_WITH_LTEX_LANGUAGE_SERVER_LOG 54 | ``` 55 | 56 |
57 | 58 | **"LTeX Language Client" log file** 59 | First, set the `ltex.trace.server` setting in your `settings.json` to `"verbose"`. Then, reload the VS Code window and reproduce the bug. Go to `View` → `Output` and select `LTeX Language Client` in the drop-down list. Paste this log here (note: it will contain your checked document): 60 | 61 |
62 | 63 | ``` 64 | REPLACE_THIS_WITH_LTEX_LANGUAGE_CLIENT_LOG 65 | ``` 66 | 67 |
68 | 69 | **Version information** 70 | List here the version information of the relevant software. 71 | 72 | - Operating system: Linux (which distribution/version), macOS xx.xx, or Windows xx 73 | - VS Code: 1.xx.x 74 | - vscode-ltex: x.xx 75 | - ltex-ls: x.xx (only if not using ltex-ls automatically downloaded by LTeX) 76 | - Java: x.xx (usually obtained with `java -version`, only if not using Java automatically downloaded by LTeX) 77 | 78 | **Additional context/information** 79 | You can add any other context or information about the problem here. 80 | -------------------------------------------------------------------------------- /src/LoggingOutputChannel.ts: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2019-2021 Julian Valentin, LTeX Development Community 2 | * 3 | * This Source Code Form is subject to the terms of the Mozilla Public 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this 5 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 | */ 7 | 8 | // #if TARGET == 'vscode' 9 | import * as Code from 'vscode'; 10 | import EventEmitter = Code.EventEmitter; 11 | // #elseif TARGET == 'coc.nvim' 12 | // import * as Code from 'coc.nvim'; 13 | // import EventEmitter = Code.Emitter; 14 | // #endif 15 | 16 | type Entry = { 17 | time: number; 18 | text: string; 19 | }; 20 | 21 | export default class LoggingOutputChannel implements Code.OutputChannel { 22 | public readonly name: string; 23 | private _outputChannel: Code.OutputChannel; 24 | private _entries: Entry[]; 25 | private _onAppendEventEmitter: EventEmitter; 26 | 27 | private static readonly pruneDuration: number = 86400; 28 | 29 | public constructor(name: string) { 30 | this.name = name; 31 | this._outputChannel = Code.window.createOutputChannel(name); 32 | this._entries = []; 33 | this._onAppendEventEmitter = new EventEmitter(); 34 | } 35 | 36 | private pruneOldEntries(): void { 37 | const now: number = Date.now(); 38 | 39 | while (this._entries.length > 0) { 40 | if (now - this._entries[0].time >= 1000 * LoggingOutputChannel.pruneDuration) { 41 | this._entries.shift(); 42 | } else { 43 | break; 44 | } 45 | } 46 | } 47 | 48 | private appendEntry(text: string): void { 49 | this._entries.push({time: Date.now(), text: text}); 50 | } 51 | 52 | public append(value: string): void { 53 | this._outputChannel.append(value); 54 | this.pruneOldEntries(); 55 | this.appendEntry(value); 56 | this._onAppendEventEmitter.fire(value); 57 | } 58 | 59 | public appendLine(value: string): void { 60 | this._outputChannel.appendLine(value); 61 | this.pruneOldEntries(); 62 | this.appendEntry(value + '\n'); 63 | this._onAppendEventEmitter.fire(value + '\n'); 64 | } 65 | 66 | public get content(): string { 67 | let contents: string = ''; 68 | 69 | this._entries.forEach((entry: Entry) => { 70 | contents += entry.text; 71 | }); 72 | 73 | return contents; 74 | } 75 | 76 | public onAppend(listener: (text: string) => void): void { 77 | this._onAppendEventEmitter.event(listener); 78 | } 79 | 80 | public clear(): void { 81 | this._outputChannel.clear(); 82 | this._entries = []; 83 | } 84 | 85 | // #if TARGET == 'vscode' 86 | public show(preserveFocus?: boolean | undefined): void; 87 | public show(column?: Code.ViewColumn | undefined, preserveFocus?: boolean | undefined): void; 88 | public show(_column?: any, preserveFocus?: any): void { 89 | // #elseif TARGET == 'coc.nvim' 90 | // public show(preserveFocus?: boolean): void { 91 | // #endif 92 | this._outputChannel.show(preserveFocus); 93 | } 94 | 95 | public hide(): void { 96 | this._outputChannel.hide(); 97 | } 98 | 99 | public dispose(): void { 100 | this._onAppendEventEmitter.dispose(); 101 | this._outputChannel.dispose(); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /.github/workflows/nightly.yml: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2019-2021 Julian Valentin, LTeX Development Community 2 | # 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 | 7 | name: "Nightly" 8 | on: 9 | # schedule: 10 | # - cron: "30 3 * * *" 11 | workflow_dispatch: 12 | 13 | jobs: 14 | deploy: 15 | name: "Nightly - Deploy Job" 16 | runs-on: "ubuntu-20.04" 17 | 18 | steps: 19 | - name: "Checkout Repository" 20 | uses: "actions/checkout@v2" 21 | 22 | - name: "Set up Node.js" 23 | uses: "actions/setup-node@v1" 24 | with: 25 | node-version: "16.11.0" 26 | 27 | - name: "Install Node.js Dependencies" 28 | run: "npm install && npm install -g vsce@1 ovsx@0.2.1" 29 | 30 | - name: "Set up Python" 31 | uses: "actions/setup-python@v2" 32 | with: 33 | python-version: "3.9.0" 34 | 35 | - name: "Install Python Dependencies" 36 | run: "python -u -m pip install --upgrade pip && pip install semver==2.13.0" 37 | 38 | - name: "Set VSCODE_LTEX_VERSION" 39 | run: "echo \"VSCODE_LTEX_VERSION=$(python -u -c \"import datetime; import json; version = json.load(open('package.json', 'r'))['version']; print('{}.nightly.{}'.format((version[:-8] if version.endswith('.develop') else version), datetime.datetime.today().strftime('%Y-%m-%d')), end='')\")\" >> $GITHUB_ENV" 40 | 41 | - name: "Check VSCODE_LTEX_VERSION" 42 | run: "if [[ -z \"$VSCODE_LTEX_VERSION\" ]]; then echo 'Error: VSCODE_LTEX_VERSION not set!'; (exit 1); fi; echo \"VSCODE_LTEX_VERSION set to '$VSCODE_LTEX_VERSION'\"" 43 | 44 | - name: "Bump Version" 45 | run: "python -u -c \"import json; file = open('package.json', 'r+'); json_ = json.loads(file.read()); json_['version'] = '${{ env.VSCODE_LTEX_VERSION }}'; file.seek(0); file.truncate(); file.write(json.dumps(json_, indent=2) + '\\n')\"" 46 | 47 | - name: "Update Version of LTeX LS" 48 | env: 49 | LTEX_GITHUB_OAUTH_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 50 | run: "python -u tools/updateLtexLsVersionAndHashDigests.py --tag nightly" 51 | 52 | - name: "Build Package with vsce" 53 | run: "vsce package" 54 | 55 | - name: "Build Offline Packages" 56 | env: 57 | LTEX_GITHUB_OAUTH_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 58 | run: "python -u tools/createOfflinePackages.py" 59 | 60 | - name: "Delete Old Nightly Releases" 61 | uses: "dev-drprasad/delete-older-releases@v0.2.0" 62 | with: 63 | keep_latest: 0 64 | delete_tag_pattern: "nightly" 65 | env: 66 | GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 67 | 68 | - name: "Update Nightly Tag" 69 | run: "git tag -f nightly && git push -f origin nightly" 70 | 71 | - name: "Create GitHub Release" 72 | uses: "softprops/action-gh-release@v0.1.8" 73 | env: 74 | GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 75 | with: 76 | tag_name: "nightly" 77 | name: "${{ env.VSCODE_LTEX_VERSION }}" 78 | prerelease: true 79 | body: "This is a nightly build. Use at your own risk." 80 | files: "ltex-${{ env.VSCODE_LTEX_VERSION }}-offline-linux-x64.vsix\nltex-${{ env.VSCODE_LTEX_VERSION }}-offline-mac-x64.vsix\nltex-${{ env.VSCODE_LTEX_VERSION }}-offline-windows-x64.vsix" 81 | -------------------------------------------------------------------------------- /src/Logger.ts: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2019-2021 Julian Valentin, LTeX Development Community 2 | * 3 | * This Source Code Form is subject to the terms of the Mozilla Public 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this 5 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 | */ 7 | 8 | // #if TARGET == 'vscode' 9 | import * as Code from 'vscode'; 10 | import * as CodeLanguageClient from 'vscode-languageclient/node'; 11 | // #elseif TARGET == 'coc.nvim' 12 | // import * as Code from 'coc.nvim'; 13 | // import CodeLanguageClient = Code; 14 | // #endif 15 | 16 | import LoggingOutputChannel from './LoggingOutputChannel'; 17 | 18 | export default class Logger { 19 | private static _clientOutputChannel: LoggingOutputChannel; 20 | private static _serverOutputChannel: LoggingOutputChannel; 21 | 22 | public static log(message: string, type: string = 'Info'): void { 23 | const timestamp: string = (new Date()).toISOString(); 24 | if (message == null) message = ''; 25 | const lines: string[] = message.split(/\r?\n/); 26 | 27 | lines.forEach((line: string) => { 28 | const logLine: string = `${timestamp} ${type}: ${line}`; 29 | Logger._clientOutputChannel.appendLine(logLine); 30 | }); 31 | } 32 | 33 | public static warn(message: string, e?: Error | unknown): void { 34 | Logger.log(message, 'Warning'); 35 | 36 | try { 37 | Logger.logError(e as Error, 'Warning'); 38 | } catch { 39 | // do nothing 40 | } 41 | } 42 | 43 | public static error(message: string, e?: Error | unknown): void { 44 | Logger.log(message, 'Error'); 45 | 46 | try { 47 | Logger.logError(e as Error, 'Error'); 48 | } catch { 49 | // do nothing 50 | } 51 | } 52 | 53 | public static logError(e: Error, type: string = 'Info'): void { 54 | Logger.log('Error details:', type); 55 | Logger.log(((e.stack != null) ? e.stack : `${e.name}: ${e.message}`), type); 56 | } 57 | 58 | public static logExecutable(executable: CodeLanguageClient.Executable): void { 59 | Logger.log(' Command: ' + JSON.stringify(executable.command)); 60 | Logger.log(' Arguments: ' + JSON.stringify(executable.args)); 61 | 62 | if (executable.options != null) { 63 | Logger.log(' env[\'JAVA_HOME\']: ' 64 | + JSON.stringify(executable.options.env['JAVA_HOME'])); 65 | Logger.log(' env[\'JAVA_OPTS\']: ' 66 | + JSON.stringify(executable.options.env['JAVA_OPTS'])); 67 | } 68 | } 69 | 70 | public static createOutputChannels(context: Code.ExtensionContext): void { 71 | Logger._clientOutputChannel = new LoggingOutputChannel('LTeX Language Client'); 72 | Logger._serverOutputChannel = new LoggingOutputChannel('LTeX Language Server'); 73 | 74 | context.subscriptions.push(Logger._clientOutputChannel); 75 | context.subscriptions.push(Logger._serverOutputChannel); 76 | } 77 | 78 | public static showClientOutputChannel(): void { 79 | Logger._clientOutputChannel.show(); 80 | } 81 | 82 | public static get clientOutputChannel(): LoggingOutputChannel { 83 | return Logger._clientOutputChannel; 84 | } 85 | 86 | public static set clientOutputChannel(clientOutputChannel: LoggingOutputChannel) { 87 | Logger._clientOutputChannel = clientOutputChannel; 88 | } 89 | 90 | public static get serverOutputChannel(): LoggingOutputChannel { 91 | return Logger._serverOutputChannel; 92 | } 93 | 94 | public static set serverOutputChannel(serverOutputChannel: LoggingOutputChannel) { 95 | Logger._serverOutputChannel = serverOutputChannel; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /tools/updateFromGhPages.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # Copyright (C) 2019-2021 Julian Valentin, LTeX Development Community 4 | # 5 | # This Source Code Form is subject to the terms of the Mozilla Public 6 | # License, v. 2.0. If a copy of the MPL was not distributed with this 7 | # file, You can obtain one at https://mozilla.org/MPL/2.0/. 8 | 9 | import os 10 | import pathlib 11 | import re 12 | import sys 13 | from typing import Any 14 | 15 | import yaml 16 | 17 | sys.path.append(str(pathlib.Path(__file__).parent)) 18 | import common 19 | 20 | 21 | 22 | pagesDirPath = pathlib.Path("/mnt/d/repos/ltex") 23 | 24 | 25 | 26 | def getSlug(markdown: str) -> str: 27 | return re.sub(r"[^a-z0-9\-]", "", re.sub(r"[ ]", "-", markdown.lower())) 28 | 29 | def getMarkdownStructure(baseUrl: str, markdownPath: str) -> Any: 30 | with open(markdownPath, "r") as f: markdown = f.read() 31 | 32 | iterator = re.finditer(r"^---$", markdown, flags=re.MULTILINE) 33 | match = next(iterator) 34 | match = next(iterator) 35 | metadata = yaml.load(markdown[:match.start()], Loader=yaml.SafeLoader) 36 | if not metadata.get("toc", True): return [] 37 | markdown = markdown[match.end():] 38 | 39 | matches = re.findall(r"^(#+) (.*)$", markdown, flags=re.MULTILINE) 40 | structure: Any = [] 41 | 42 | for match in matches: 43 | level = len(match[0]) - 2 44 | listToAppend = structure 45 | for i in range(level): listToAppend = listToAppend[-1]["children"] 46 | slug = getSlug(match[1]) 47 | listToAppend.append({"title" : processTitle(match[1]), "url" : "{}#{}".format(baseUrl, slug), 48 | "children" : []}) 49 | 50 | return structure 51 | 52 | def convertToMarkdown(structure: Any, indent: int = 0) -> str: 53 | markdown = "" 54 | 55 | for entry in structure: 56 | markdown += f"{indent * ' '}- [{entry['title']}]({entry['url']})\n" 57 | 58 | if len(entry["children"]) > 0: 59 | markdown += convertToMarkdown(entry["children"], indent + 2) 60 | 61 | return markdown 62 | 63 | def processTitle(title: str) -> str: 64 | title = title.replace("LaTeX", "LATEX").replace("TeX", "TEX") 65 | prevTitle = None 66 | 67 | while title != prevTitle: 68 | prevTitle = title 69 | title = re.sub(r"(`.*?)LTEX", r"\1LTeX", title) 70 | 71 | return title 72 | 73 | 74 | 75 | def updateReadme() -> None: 76 | serverUrl = "https://valentjn.github.io/ltex" 77 | sidebarYamlPath = pagesDirPath.joinpath("_data", "sidebars", "sidebar.yml") 78 | with open(sidebarYamlPath, "r") as f: sidebarYaml = yaml.load(f, Loader=yaml.SafeLoader) 79 | 80 | structure = [] 81 | 82 | for folder in sidebarYaml["entries"][0]["folders"]: 83 | children = [] 84 | 85 | for entry in folder["folderitems"]: 86 | if "url" in entry: 87 | url = serverUrl + entry["url"] 88 | markdownPath = "{}{}.md".format(pagesDirPath.joinpath("pages"), 89 | os.path.splitext(entry["url"])[0]) 90 | grandChildren = getMarkdownStructure(url, markdownPath) 91 | else: 92 | url = entry["external_url"] 93 | grandChildren = [] 94 | 95 | children.append({"title": processTitle(entry["title"]), "url" : url, 96 | "children" : grandChildren}) 97 | 98 | structure.append({"title" : processTitle(folder["title"]), "url" : children[0]["url"], 99 | "children" : children}) 100 | 101 | markdown = convertToMarkdown(structure) 102 | 103 | readmePath = common.repoDirPath.joinpath("README.md") 104 | with open(readmePath, "r") as f: readme = f.read() 105 | 106 | readme = re.sub(r"## Information & Documentation\n\n.*?^( *[^\-\*\n ])", 107 | r"## Information & Documentation\n\n{}\n\1".format(markdown.replace("\\", "\\\\")), readme, 108 | flags=re.MULTILINE | re.DOTALL) 109 | 110 | with open(readmePath, "w") as f: f.write(readme) 111 | 112 | 113 | 114 | def main() -> None: 115 | updateReadme() 116 | 117 | 118 | 119 | if __name__ == "__main__": 120 | main() 121 | -------------------------------------------------------------------------------- /src/I18n.ts: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2019-2021 Julian Valentin, LTeX Development Community 2 | * 3 | * This Source Code Form is subject to the terms of the Mozilla Public 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this 5 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 | */ 7 | 8 | // #if TARGET == 'vscode' 9 | import * as Code from 'vscode'; 10 | // #elseif TARGET == 'coc.nvim' 11 | // import * as Code from 'coc.nvim'; 12 | // #endif 13 | import * as Fs from 'fs'; 14 | import * as Path from 'path'; 15 | 16 | import Logger from './Logger'; 17 | 18 | type MessageObject = { 19 | [key: string]: string; 20 | }; 21 | 22 | interface VSCodeNlsConfig { 23 | locale: string; 24 | availableLanguages: { 25 | [pack: string]: string; 26 | }; 27 | _languagePackSupport?: boolean; 28 | _languagePackId?: string; 29 | _translationsConfigFile?: string; 30 | _cacheRoot?: string; 31 | _corruptedFile: string; 32 | } 33 | 34 | export class I18n { 35 | private static _language: string; 36 | private static _messages: MessageObject; 37 | private static _defaultMessages: MessageObject; 38 | 39 | public static initialize(context: Code.ExtensionContext): void { 40 | this._language = I18n.getLanguage(); 41 | Logger.log(`Setting LTeX UI language to '${this._language}'.`); 42 | Logger.log('Loading i18n messages...'); 43 | this._messages = I18n.loadMessages(context); 44 | Logger.log('Loading default i18n messages...'); 45 | this._defaultMessages = I18n.loadDefaultMessages(context); 46 | } 47 | 48 | public static getLanguage(): string { 49 | let language: string = 'en-US'; 50 | const codeNlsConfigStr: string | undefined = process.env['VSCODE_NLS_CONFIG']; 51 | 52 | if (codeNlsConfigStr != null) { 53 | try { 54 | const codeNlsConfig: VSCodeNlsConfig = JSON.parse(codeNlsConfigStr) as VSCodeNlsConfig; 55 | language = codeNlsConfig.locale; 56 | } catch { 57 | // ignore errors, use default language 58 | } 59 | } 60 | 61 | return language; 62 | } 63 | 64 | public static loadMessages(context: Code.ExtensionContext): MessageObject { 65 | let language: string = this._language.replace(/[^A-Za-z0-9\-_]/g, ''); 66 | 67 | while (true) { 68 | const filePath: string = Path.resolve(context.extensionPath, 'i18n', 69 | `messages.nls.${language}.json`); 70 | 71 | if (Fs.existsSync(filePath)) { 72 | return I18n.loadMessagesFile(filePath); 73 | } else { 74 | const index: number = language.indexOf('-'); 75 | 76 | if (index >= 0) { 77 | language = language.substr(0, index); 78 | } else { 79 | return I18n.loadDefaultMessages(context); 80 | } 81 | } 82 | } 83 | } 84 | 85 | public static loadDefaultMessages(context: Code.ExtensionContext): MessageObject { 86 | return I18n.loadMessagesFile(Path.resolve(context.extensionPath, 'i18n', 'messages.nls.json')); 87 | } 88 | 89 | public static loadMessagesFile(filePath: string): MessageObject { 90 | const fileContents: string = Fs.readFileSync(filePath, {encoding: 'utf-8'}); 91 | return JSON.parse(fileContents); 92 | } 93 | 94 | public static getMessage(key: string): string { 95 | if (Object.prototype.hasOwnProperty.call(this._messages, key)) { 96 | return this._messages[key]; 97 | } else if (Object.prototype.hasOwnProperty.call(this._defaultMessages, key)) { 98 | return this._defaultMessages[key]; 99 | } else { 100 | Logger.warn(`Could not find i18n message key '${key}'.`); 101 | return key; 102 | } 103 | } 104 | 105 | public static i18n(key: string, ...messageArguments: any[]): string { 106 | return I18n.getMessage(key).replace(/\{(\d+)\}/g, (_match: string, groups: string[]) => { 107 | return String(messageArguments[parseInt(groups[0])]); 108 | }); 109 | } 110 | } 111 | 112 | const i18n: (key: string, ...messageArguments: any[]) => string = I18n.i18n; 113 | export {i18n}; 114 | -------------------------------------------------------------------------------- /src/test/ExtensionInitializer.ts: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2019-2021 Julian Valentin, LTeX Development Community 2 | * 3 | * This Source Code Form is subject to the terms of the Mozilla Public 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this 5 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 | */ 7 | 8 | // #if TARGET == 'vscode' 9 | import * as Code from 'vscode'; 10 | import * as CodeLanguageClient from 'vscode-languageclient/node'; 11 | 12 | import * as Ltex from '../extension'; 13 | import TestTools from './TestTools'; 14 | 15 | export default class ExtensionInitializer { 16 | private static _languageClient: CodeLanguageClient.LanguageClient | null = null; 17 | 18 | public static async initialize(): Promise { 19 | if (ExtensionInitializer._languageClient != null) return Promise.resolve(); 20 | 21 | const ltex: Code.Extension | undefined = 22 | Code.extensions.getExtension('neo-ltex.ltex'); 23 | if (ltex == null) return Promise.reject(new Error('Could not find LTeX.')); 24 | 25 | Code.workspace.getConfiguration('ltex').update('trace.server', 'messages', 26 | Code.ConfigurationTarget.Global); 27 | const document: Code.TextDocument = await TestTools.createNewFile( 28 | 'markdown', 'This is an *test*.'); 29 | console.log('Waiting for activation of LTeX...'); 30 | 31 | for (let i: number = 0; i < 120; i++) { 32 | if (ltex.isActive) break; 33 | await TestTools.sleep(500); 34 | } 35 | 36 | const api: Ltex.Api | null = ltex.exports; 37 | 38 | if (api.clientOutputChannel == null) { 39 | return Promise.reject(new Error('Client output channel not initialized.')); 40 | } 41 | 42 | console.log(api.clientOutputChannel.content); 43 | api.clientOutputChannel.onAppend((text: string) => { 44 | console.log(text); 45 | }); 46 | 47 | if (api.serverOutputChannel == null) { 48 | return Promise.reject(new Error('Server output channel not initialized.')); 49 | } 50 | 51 | console.log(api.serverOutputChannel.content); 52 | api.serverOutputChannel.onAppend((text: string) => { 53 | console.log(text); 54 | }); 55 | 56 | console.log('Waiting for language client to be ready...'); 57 | ExtensionInitializer._languageClient = api.languageClient; 58 | 59 | if (ExtensionInitializer._languageClient == null) { 60 | return Promise.reject(new Error('Language client not initialized.')); 61 | } 62 | 63 | await ExtensionInitializer._languageClient.onReady(); 64 | console.log('Language client is ready.'); 65 | 66 | let ltexReady: boolean = false; 67 | 68 | for (let i: number = 0; i < 120; i++) { 69 | const diagnostics: Code.Diagnostic[] = Code.languages.getDiagnostics(document.uri); 70 | 71 | if ((diagnostics.length == 1) && (diagnostics[0].source == 'LTeX')) { 72 | ltexReady = true; 73 | break; 74 | } 75 | 76 | await TestTools.sleep(500); 77 | } 78 | 79 | if (!ltexReady) return Promise.reject(new Error('LTeX not successfully initialized.')); 80 | } 81 | 82 | public static async getLanguageClient(): Promise { 83 | if (ExtensionInitializer._languageClient != null) { 84 | return Promise.resolve(ExtensionInitializer._languageClient); 85 | } else { 86 | return ExtensionInitializer.initialize().then( 87 | () => new Promise((resolve: (value: CodeLanguageClient.LanguageClient) => void, 88 | reject: (reason: Error) => void) => { 89 | if (ExtensionInitializer._languageClient != null) { 90 | resolve(ExtensionInitializer._languageClient); 91 | } else { 92 | reject(new Error('LTeX not successfully initialized.')); 93 | } 94 | }), 95 | ); 96 | } 97 | } 98 | 99 | public static async resetState(): Promise { 100 | console.log('Closing all editors...'); 101 | await Code.commands.executeCommand('workbench.action.closeAllEditors'); 102 | await TestTools.sleep(200); 103 | return Promise.resolve(); 104 | } 105 | } 106 | // #endif 107 | -------------------------------------------------------------------------------- /src/test/Commands.test.ts: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2019-2021 Julian Valentin, LTeX Development Community 2 | * 3 | * This Source Code Form is subject to the terms of the Mozilla Public 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this 5 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 | */ 7 | 8 | // #if TARGET == 'vscode' 9 | import * as Assert from 'assert'; 10 | import * as Code from 'vscode'; 11 | import * as Path from 'path'; 12 | import * as Util from 'util'; 13 | 14 | import ExtensionInitializer from './ExtensionInitializer'; 15 | import TestTools from './TestTools'; 16 | 17 | describe('Test commands', () => { 18 | before(async () => { 19 | await ExtensionInitializer.initialize(); 20 | }); 21 | 22 | beforeEach(async () => { 23 | await ExtensionInitializer.resetState(); 24 | }); 25 | 26 | it('Test ltex.clearDiagnosticsInCurrentDocument and ltex.checkCurrentDocument', async () => { 27 | const document: Code.TextDocument = await TestTools.createNewFile('latex', 28 | 'This is an \\textbf{test}.'); 29 | await TestTools.sleep(10000); 30 | Assert.strictEqual(Code.languages.getDiagnostics(document.uri).length, 1); 31 | await Code.commands.executeCommand('ltex.clearDiagnosticsInCurrentDocument'); 32 | await TestTools.sleep(1000); 33 | Assert.strictEqual(Code.languages.getDiagnostics(document.uri).length, 0); 34 | await Code.commands.executeCommand('ltex.checkCurrentDocument'); 35 | await TestTools.sleep(10000); 36 | Assert.strictEqual(Code.languages.getDiagnostics(document.uri).length, 1); 37 | }); 38 | 39 | it('Test ltex.clearAllDiagnostics and ltex.checkCurrentDocument', async () => { 40 | const document1: Code.TextDocument = await TestTools.createNewFile('latex', 41 | 'This is an \\textbf{test}.'); 42 | const document2: Code.TextDocument = await TestTools.createNewFile('latex', 43 | 'This is an \\textbf{test}.'); 44 | await TestTools.sleep(10000); 45 | 46 | Assert.strictEqual(Code.languages.getDiagnostics(document1.uri).length, 1); 47 | Assert.strictEqual(Code.languages.getDiagnostics(document2.uri).length, 1); 48 | 49 | await Code.commands.executeCommand('ltex.clearDiagnosticsInCurrentDocument'); 50 | await TestTools.sleep(1000); 51 | 52 | Assert.strictEqual(Code.languages.getDiagnostics(document1.uri).length, 0); 53 | Assert.strictEqual(Code.languages.getDiagnostics(document2.uri).length, 1); 54 | 55 | await Code.commands.executeCommand('ltex.checkCurrentDocument'); 56 | await TestTools.sleep(10000); 57 | 58 | Assert.strictEqual(Code.languages.getDiagnostics(document1.uri).length, 1); 59 | Assert.strictEqual(Code.languages.getDiagnostics(document2.uri).length, 1); 60 | }); 61 | 62 | it('Test ltex.checkAllDocumentsInWorkspace', async () => { 63 | if (Code.workspace.workspaceFolders == null) throw new Error('No workspace folder open.'); 64 | const workspaceFolderPath: string = Code.workspace.workspaceFolders[0].uri.fsPath; 65 | const fileUri1: Code.Uri = Code.Uri.file(Path.join(workspaceFolderPath, 'test1.tex')); 66 | const fileUri2: Code.Uri = Code.Uri.file(Path.join(workspaceFolderPath, 'test2.md')); 67 | const fileUri3: Code.Uri = Code.Uri.file(Path.join(workspaceFolderPath, 'test3.txt')); 68 | 69 | const textEncoder: Util.TextEncoder = new Util.TextEncoder(); 70 | await Code.workspace.fs.writeFile(fileUri1, textEncoder.encode('This is an test.\n')); 71 | await Code.workspace.fs.writeFile(fileUri2, textEncoder.encode('This is an test.\n')); 72 | await Code.workspace.fs.writeFile(fileUri3, textEncoder.encode('This is an test.\n')); 73 | await TestTools.sleep(1000); 74 | 75 | Assert.strictEqual(Code.languages.getDiagnostics(fileUri1).length, 0); 76 | Assert.strictEqual(Code.languages.getDiagnostics(fileUri2).length, 0); 77 | Assert.strictEqual(Code.languages.getDiagnostics(fileUri3).length, 0); 78 | 79 | await Code.commands.executeCommand('ltex.checkAllDocumentsInWorkspace'); 80 | await TestTools.sleep(10000); 81 | 82 | Assert.strictEqual(Code.languages.getDiagnostics(fileUri1).length, 1); 83 | Assert.strictEqual(Code.languages.getDiagnostics(fileUri2).length, 1); 84 | Assert.strictEqual(Code.languages.getDiagnostics(fileUri3).length, 0); 85 | }); 86 | }); 87 | // #endif 88 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 8 | 9 | # Security Policy 10 | 11 | For the LTEX project, the security of its users is its top priority. This document contains information on which versions are supported with security updates and how to report potential security vulnerabilities. 12 | 13 | ## Supported Versions 14 | 15 | By default, only the most recent published version of LTEX is supported with security updates. 16 | 17 | If deemed relevant by the maintainers (depending on the severity of the security vulnerability and the need to keep older versions for compatibility), older versions of LTEX might also get security updates in exceptional circumstances. 18 | 19 | ## Definition of Vulnerabilities 20 | 21 | LTEX follows [Microsoft's definition of security vulnerabilities](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)). 22 | 23 | In this document, the term “vulnerability” is synonymous to “security vulnerability.” 24 | 25 | ## Reporting a Vulnerability 26 | 27 | **Please do not report security vulnerabilities through public GitHub issues.** 28 | 29 | Please report security vulnerabilities via the Security Advisory form under the Security tab in the GitHub repository ([instructions](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability)). 30 | 31 | You can expect an initial response within 24 hours. If you do not get a response, please send a follow-up message. 32 | 33 | In your report, please include at least the following information (as much as possible): 34 | 35 | - Type of issue (e.g., buffer overflow, SQL injection, cross-site scripting, etc.) 36 | - Paths of source files related to the issue 37 | - Location of the affected source code (tag/branch/commit) 38 | - Configuration required to reproduce the issue 39 | - Step-by-step instructions to reproduce the issue 40 | - Proof-of-concept or exploit code (if possible) 41 | - Impact of the issue, including how an attacker might exploit the issue 42 | 43 | ## Process of Handling Vulnerabilities 44 | 45 | The steps of the process of handling vulnerabilities is as follows: 46 | 47 | 1. [Initial response](#reporting-a-vulnerability) 48 | 2. Confirmation that the issue is a [security vulnerability](#definition-of-vulnerabilities) 49 | 3. All vulnerable versions have been unpublished/deleted 50 | 4. Fix is being implemented 51 | 5. Fix is implemented 52 | 6. Confirmation that the issue has been fixed in a new non-public pre-release 53 | 7. Fix is pushed and released; [responsible disclosure](#responsible-disclosure-policy) 54 | 8. [Post-mortem analysis](#post-mortem-analysis) 55 | 56 | You will obtain an update as soon as the next step has been completed, but no later than 5 days after the last update. 57 | 58 | ## Responsible Disclosure Policy 59 | 60 | LTEX follows the principle of responsible disclosure. This means that security vulnerabilities are disclosed, but only a specific period of time. 61 | 62 | Vulnerabilities are disclosed as soon as a fix has been pushed and released or after 30 days after the initial report, whichever comes first. No information must be disclosed before the embargo ends, neither by the reporter nor by the maintainers. 63 | 64 | Vulnerabilities are disclosed in the project 65 | 66 | - as a new GitHub security advisory, 67 | - as a new and pinned GitHub issue, and 68 | - in the changelog. 69 | 70 | In the disclosure, the reporter is given proper credit, unless they request to stay anonymous. The reporter is free to post their own analysis on personal pages. 71 | 72 | ## Post-Mortem Analysis 73 | 74 | After the vulnerability has been disclosed, a thorough post-mortem analysis is performed to minimize the risk of future vulnerabilities of similar nature. The analysis is focused on the underlying root causes and newly implemented counter-measures. 75 | 76 | When the post-mortem analysis is complete, the description of the GitHub issue, in which the issue has been disclosed, is amended to include the analysis. 77 | -------------------------------------------------------------------------------- /tools/generateCodeName.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # Copyright (C) 2019-2021 Julian Valentin, LTeX Development Community 4 | # 5 | # This Source Code Form is subject to the terms of the Mozilla Public 6 | # License, v. 2.0. If a copy of the MPL was not distributed with this 7 | # file, You can obtain one at https://mozilla.org/MPL/2.0/. 8 | 9 | import argparse 10 | import pathlib 11 | import random 12 | import re 13 | import sys 14 | from typing import List 15 | import xml.etree.ElementTree as et 16 | 17 | sys.path.append(str(pathlib.Path(__file__).parent)) 18 | import common 19 | 20 | 21 | 22 | suffixes = """ 23 | Acceleration 24 | Accumulation 25 | Acquisition 26 | Agitation 27 | Algorithm 28 | Allocation 29 | Alternative 30 | Amalgamation 31 | Amplification 32 | Annihilation 33 | Application 34 | Approximation 35 | Attenuation 36 | Bifurcation 37 | Calculation 38 | Capacitance 39 | Catalyst 40 | Collapse 41 | Collision 42 | Combustion 43 | Complexity 44 | Conclusion 45 | Configuration 46 | Congruence 47 | Conjecture 48 | Constant 49 | Contamination 50 | Contraction 51 | Conundrum 52 | Convergence 53 | Correlation 54 | Decay 55 | Decoupling 56 | Deprivation 57 | Derivation 58 | Deterioration 59 | Determination 60 | Deviation 61 | Diffusion 62 | Diremption 63 | Disassociation 64 | Disintegration 65 | Displacement 66 | Dissection 67 | Dissipation 68 | Dissolution 69 | Duality 70 | Elasticity 71 | Elevation 72 | Emanation 73 | Entanglement 74 | Equivalency 75 | Erosion 76 | Evaporation 77 | Excitation 78 | Expansion 79 | Expedition 80 | Experiment 81 | Experimentation 82 | Extraction 83 | Factor 84 | Fluctuation 85 | Formulation 86 | Fragmentation 87 | Hypothesis 88 | Illumination 89 | Implementation 90 | Implosion 91 | Incursion 92 | Indeterminacy 93 | Insufficiency 94 | Integration 95 | Interruption 96 | Isotope 97 | Malfunction 98 | Manifestation 99 | Manipulation 100 | Materialization 101 | Methodology 102 | Metric 103 | Miniaturization 104 | Minimization 105 | Momentum 106 | Negation 107 | Nomenclature 108 | Obliteration 109 | Observation 110 | Optimization 111 | Oscillation 112 | Paradigm 113 | Paradox 114 | Peculiarity 115 | Permeability 116 | Permutation 117 | Perturbation 118 | Polarization 119 | Postulate 120 | Potential 121 | Proposal 122 | Proposition 123 | Proximity 124 | Ramification 125 | Reaction 126 | Realignment 127 | Recalibration 128 | Recoil 129 | Recurrence 130 | Reflection 131 | Regeneration 132 | Renormalization 133 | Resonance 134 | Resurgence 135 | Revelation 136 | Reverberation 137 | Saturation 138 | Scattering 139 | Schism 140 | Simulation 141 | Solution 142 | Stimulation 143 | Sublimation 144 | Submergence 145 | Substitution 146 | Summation 147 | Synchronicity 148 | Syndrome 149 | Theorem 150 | Thermalization 151 | Topology 152 | Transcendence 153 | Transformation 154 | Transmission 155 | Transmogrification 156 | Triangulation 157 | Turbulence 158 | Valuation 159 | Verification 160 | Vortex 161 | """.strip().splitlines() 162 | 163 | 164 | 165 | def getUsedSuffixes() -> List[str]: 166 | xmlFilePath = pathlib.Path(common.repoDirPath.joinpath("changelog.xml")) 167 | document = et.parse(xmlFilePath).getroot() 168 | releases = document.findall("./{http://maven.apache.org/changes/1.0.0}body" 169 | "/{http://maven.apache.org/changes/1.0.0}release") 170 | usedSuffixes = [] 171 | 172 | for release in releases: 173 | if ("description" not in release.attrib) or (release.attrib["date"] == "upcoming"): continue 174 | regexMatch = re.search(r"The .* ([A-Za-z]+)", release.attrib["description"]) 175 | assert regexMatch is not None 176 | usedSuffixes.append(regexMatch.group(1)) 177 | 178 | return usedSuffixes 179 | 180 | 181 | 182 | def main() -> None: 183 | parser = argparse.ArgumentParser( 184 | description="Generate a code name for the next release of LTeX") 185 | parser.add_argument("topic", metavar="TOPIC", help="Main topic of the release") 186 | args = parser.parse_args() 187 | 188 | usedSuffixes = getUsedSuffixes() 189 | 190 | numberOfUsages = {x : 0 for x in suffixes} 191 | for x in usedSuffixes: numberOfUsages[x] += 1 192 | suffixes.sort(key=lambda x: numberOfUsages[x]) 193 | minNumberOfUsages = numberOfUsages[suffixes[0]] 194 | 195 | possibleSuffixes = [x for x in suffixes if numberOfUsages[x] == minNumberOfUsages] 196 | suffix = random.choice(possibleSuffixes) 197 | 198 | print(f"The {args.topic} {suffix}") 199 | 200 | 201 | 202 | if __name__ == "__main__": 203 | main() 204 | -------------------------------------------------------------------------------- /tools/createOfflinePackages.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # Copyright (C) 2019-2021 Julian Valentin, LTeX Development Community 4 | # 5 | # This Source Code Form is subject to the terms of the Mozilla Public 6 | # License, v. 2.0. If a copy of the MPL was not distributed with this 7 | # file, You can obtain one at https://mozilla.org/MPL/2.0/. 8 | 9 | import argparse 10 | import json 11 | import pathlib 12 | import platform 13 | import re 14 | import shlex 15 | import subprocess 16 | import sys 17 | import tarfile 18 | from typing import Optional 19 | import urllib.error 20 | import urllib.parse 21 | import urllib.request 22 | import zipfile 23 | 24 | import semver 25 | 26 | sys.path.append(str(pathlib.Path(__file__).parent)) 27 | import common 28 | 29 | 30 | 31 | libDirPath = common.repoDirPath.joinpath("lib") 32 | 33 | 34 | 35 | def cleanLibDir() -> None: 36 | cmd = ["git", "-C", str(common.repoDirPath), "clean", "-f", "-x", str(libDirPath)] 37 | print("Cleaning lib/ by running '{}'...".format(" ".join(shlex.quote(x) for x in cmd))) 38 | subprocess.run(cmd) 39 | 40 | 41 | 42 | def getLtexVersion() -> semver.VersionInfo: 43 | with open(common.repoDirPath.joinpath("package.json"), "r") as f: packageJson = json.load(f) 44 | return semver.VersionInfo.parse(packageJson["version"]) 45 | 46 | 47 | 48 | def downloadLtexLs(platform: str, arch: str) -> None: 49 | ltexLsArchiveType = ("zip" if platform == "windows" else "tar.gz") 50 | ltexLsArchiveName = ( 51 | f"ltex-ls-{common.toBeDownloadedLtexLsVersion}-{platform}-{arch}.{ltexLsArchiveType}") 52 | 53 | ltexLsUrl = ("https://github.com/valentjn/ltex-ls/releases/download/" 54 | f"{common.toBeDownloadedLtexLsTag}/{ltexLsArchiveName}") 55 | ltexLsArchivePath = libDirPath.joinpath(ltexLsArchiveName) 56 | print(f"Downloading ltex-ls {common.toBeDownloadedLtexLsVersion} from '{ltexLsUrl}' to " 57 | f"'{ltexLsArchivePath}'...") 58 | urllib.request.urlretrieve(ltexLsUrl, ltexLsArchivePath) 59 | 60 | extractLtexLs(ltexLsArchivePath) 61 | 62 | print("Removing ltex-ls archive...") 63 | ltexLsArchivePath.unlink() 64 | 65 | def extractLtexLs(ltexLsArchivePath: pathlib.Path) -> None: 66 | print("Extracting ltex-ls archive...") 67 | 68 | if ltexLsArchivePath.suffix == ".zip": 69 | with zipfile.ZipFile(ltexLsArchivePath, "r") as zipFile: zipFile.extractall(path=libDirPath) 70 | else: 71 | with tarfile.open(ltexLsArchivePath, "r:gz") as tarFile: tarFile.extractall(path=libDirPath) 72 | 73 | 74 | 75 | def createPackage(ltexPlatform: Optional[str] = None, ltexArch: Optional[str] = None) -> None: 76 | ltexVersion = getLtexVersion() 77 | 78 | if ltexPlatform is None: 79 | packageName = f"ltex-{ltexVersion}.vsix" 80 | else: 81 | packageName = f"ltex-{ltexVersion}-offline-{ltexPlatform}-{ltexArch}.vsix" 82 | 83 | assert re.match(r"^[\-\.0-9A-Z_a-z]+$", packageName) is not None 84 | cmd = f"npx vsce package -o \"{packageName}\"" 85 | print(f"Creating package by running '{cmd}'...") 86 | subprocess.run(cmd, shell=True) 87 | 88 | 89 | 90 | def main() -> None: 91 | parser = argparse.ArgumentParser(description="Build offline packages of LTeX") 92 | parser.add_argument("--current-system", action="store_true", 93 | help="Build offline package only for current platform/architecture") 94 | parser.add_argument("--ltex-ls-path", type=pathlib.Path, metavar="PATH", 95 | help="Don't download ltex-ls from GitHub, but use archive from this path") 96 | args = parser.parse_args() 97 | 98 | if args.current_system: 99 | ltexPlatform = { 100 | "Linux" : "linux", 101 | "Darwin" : "mac", 102 | "Windows" : "windows", 103 | }[platform.system()] 104 | ltexArch = { 105 | "AMD64" : "x64", 106 | "x86_64" : "x64", 107 | }[platform.machine()] 108 | ltexPlatformArchs = [(ltexPlatform, ltexArch)] 109 | else: 110 | ltexPlatformArchs = [("linux", "x64"), ("mac", "x64"), ("windows", "x64")] 111 | 112 | ltexLsArchivePath = args.ltex_ls_path 113 | cleanLibDir() 114 | 115 | for ltexPlatform, ltexArch in ltexPlatformArchs: 116 | print("") 117 | print(f"Processing platform '{ltexPlatform}' and architecture '{ltexArch}'...") 118 | 119 | if ltexLsArchivePath is None: 120 | downloadLtexLs(ltexPlatform, ltexArch) 121 | else: 122 | extractLtexLs(ltexLsArchivePath) 123 | 124 | createPackage(ltexPlatform, ltexArch) 125 | cleanLibDir() 126 | 127 | 128 | 129 | if __name__ == "__main__": 130 | main() 131 | -------------------------------------------------------------------------------- /tools/updateLtexLsVersionAndHashDigests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # Copyright (C) 2019-2021 Julian Valentin, LTeX Development Community 4 | # 5 | # This Source Code Form is subject to the terms of the Mozilla Public 6 | # License, v. 2.0. If a copy of the MPL was not distributed with this 7 | # file, You can obtain one at https://mozilla.org/MPL/2.0/. 8 | 9 | import argparse 10 | import hashlib 11 | import pathlib 12 | import re 13 | import sys 14 | from typing import Dict, Iterable 15 | import urllib.parse 16 | 17 | import semver 18 | 19 | sys.path.append(str(pathlib.Path(__file__).parent)) 20 | import common 21 | 22 | 23 | 24 | def getLatestLtexLsVersion(versions: Iterable[str], 25 | allowPrerelease: bool = False) -> semver.VersionInfo: 26 | latestVersion = None 27 | 28 | for versionString in versions: 29 | if semver.VersionInfo.isvalid(versionString): 30 | version = semver.VersionInfo.parse(versionString) 31 | 32 | if ((allowPrerelease or (version.prerelease is None)) and 33 | ((latestVersion is None) or (version > latestVersion))): 34 | latestVersion = version 35 | 36 | return latestVersion 37 | 38 | 39 | 40 | def getDownloadUrlsOfGitHubReleases(organizationName: str, repositoryName: str, 41 | tagName: str) -> Dict[str, str]: 42 | apiUrl = (f"https://api.github.com/repos/{urllib.parse.quote_plus(organizationName)}/" 43 | f"{urllib.parse.quote_plus(repositoryName)}/releases/tags/{urllib.parse.quote_plus(tagName)}") 44 | response = common.requestFromGitHub(apiUrl) 45 | return {x["name"] : x["browser_download_url"] for x in response["assets"]} 46 | 47 | 48 | 49 | def main() -> None: 50 | parser = argparse.ArgumentParser(description="Update version and hash digest of LTeX LS") 51 | parser.add_argument("--allow-prerelease", action="store_true", 52 | help="Allow prerelease versions") 53 | parser.add_argument("--tag", 54 | help="Tag to use; if omitted, tag with latest semantic version will be used") 55 | args = parser.parse_args() 56 | 57 | print("Retrieving list of releases of LTeX LS...") 58 | releases = common.requestFromGitHub("https://api.github.com/repos/valentjn/ltex-ls/releases") 59 | 60 | if args.tag is not None: 61 | ltexLsTag = args.tag 62 | 63 | for release in releases: 64 | if release["tag_name"] == ltexLsTag: 65 | ltexLsVersion = release["name"] 66 | break 67 | else: 68 | ltexLsVersion = getLatestLtexLsVersion([x["tag_name"] for x in releases], 69 | allowPrerelease=args.allow_prerelease) 70 | ltexLsVersion = str(ltexLsVersion) 71 | ltexLsTag = ltexLsVersion 72 | 73 | print(f"Retrieving list of assets for LTeX LS {ltexLsVersion} (tag '{ltexLsTag}')...") 74 | downloadUrls = getDownloadUrlsOfGitHubReleases("valentjn", "ltex-ls", ltexLsTag) 75 | assetFileNames = [x for x in downloadUrls if x != f"ltex-ls-{ltexLsVersion}.tar.gz"] 76 | assetFileNames.sort() 77 | hashDigests = [] 78 | 79 | for assetFileName in assetFileNames: 80 | print(f"Downloading '{assetFileName}'...") 81 | ltexLsUrl = ("https://github.com/valentjn/ltex-ls/releases/download/" 82 | f"{urllib.parse.quote_plus(ltexLsTag)}/{assetFileName}") 83 | response = common.requestFromGitHub(ltexLsUrl, decodeAsJson=False) 84 | 85 | print("Computing hash digest...") 86 | hashDigest = hashlib.sha256(response).hexdigest() 87 | print(f"Hash digest is '{hashDigest}'.") 88 | hashDigests.append(hashDigest) 89 | 90 | hashDigestsTypescript = "".join(f" '{x}':\n '{y}',\n" 91 | for x, y in zip(assetFileNames, hashDigests)) 92 | 93 | print("Writing version and hash digests to 'src/DependencyManager.ts'...") 94 | dependencyManagerFilePath = common.repoDirPath.joinpath("src", "DependencyManager.ts") 95 | with open(dependencyManagerFilePath, "r") as f: dependencyManagerTypescript = f.read() 96 | dependencyManagerTypescript = re.sub(r"(_toBeDownloadedLtexLsTag: string =\n *').*?(';\n)", 97 | rf"\g<1>{ltexLsTag}\g<2>", dependencyManagerTypescript) 98 | dependencyManagerTypescript = re.sub(r"(_toBeDownloadedLtexLsVersion: string =\n *').*?(';\n)", 99 | rf"\g<1>{ltexLsVersion}\g<2>", dependencyManagerTypescript) 100 | dependencyManagerTypescript = re.sub( 101 | r"(_toBeDownloadedLtexLsHashDigests: \{\[fileName: string\]: string\} = \{\n).*?( \};\n)", 102 | fr"\g<1>{hashDigestsTypescript}\g<2>", dependencyManagerTypescript, flags=re.DOTALL) 103 | with open(dependencyManagerFilePath, "w") as f: f.write(dependencyManagerTypescript) 104 | 105 | 106 | 107 | if __name__ == "__main__": 108 | main() 109 | -------------------------------------------------------------------------------- /src/WorkspaceConfigurationRequestHandler.ts: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2019-2021 Julian Valentin, LTeX Development Community 2 | * 3 | * This Source Code Form is subject to the terms of the Mozilla Public 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this 5 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 | */ 7 | 8 | // #if TARGET == 'vscode' 9 | import * as Code from 'vscode'; 10 | // #elseif TARGET == 'coc.nvim' 11 | // import * as Code from 'coc.nvim'; 12 | // #endif 13 | 14 | import ExternalFileManager from './ExternalFileManager'; 15 | 16 | type ConfigurationItem = { 17 | scopeUri: string; 18 | section: string; 19 | }; 20 | 21 | type LanguageSpecificSettingValue = {[language: string]: string[]}; 22 | 23 | type ConfigurationResultItem = { 24 | dictionary: LanguageSpecificSettingValue; 25 | disabledRules: LanguageSpecificSettingValue; 26 | enabledRules: LanguageSpecificSettingValue; 27 | hiddenFalsePositives: LanguageSpecificSettingValue; 28 | }; 29 | 30 | export default class WorkspaceConfigurationRequestHandler { 31 | private _externalFileManager: ExternalFileManager; 32 | 33 | public constructor(externalFileManager: ExternalFileManager) { 34 | this._externalFileManager = externalFileManager; 35 | } 36 | 37 | private mergeSettings(uri: Code.Uri, settingName: string): LanguageSpecificSettingValue { 38 | const result: LanguageSpecificSettingValue = {}; 39 | 40 | this._externalFileManager.updateWatchers(uri, settingName); 41 | 42 | const userSettingValue: LanguageSpecificSettingValue = 43 | this._externalFileManager.getSettingValue(uri, settingName, 44 | Code.ConfigurationTarget.Global); 45 | WorkspaceConfigurationRequestHandler.mergeLanguageSpecificSettingValue( 46 | result, userSettingValue); 47 | 48 | const workspaceSettingValue: LanguageSpecificSettingValue = 49 | this._externalFileManager.getSettingValue(uri, settingName, 50 | Code.ConfigurationTarget.Workspace); 51 | WorkspaceConfigurationRequestHandler.mergeLanguageSpecificSettingValue( 52 | result, workspaceSettingValue); 53 | 54 | const workspaceFolderSettingValue: LanguageSpecificSettingValue = 55 | this._externalFileManager.getSettingValue(uri, settingName, 56 | Code.ConfigurationTarget.WorkspaceFolder); 57 | WorkspaceConfigurationRequestHandler.mergeLanguageSpecificSettingValue( 58 | result, workspaceFolderSettingValue); 59 | 60 | for (const language in result) { 61 | if (!Object.prototype.hasOwnProperty.call(result, language)) continue; 62 | result[language] = WorkspaceConfigurationRequestHandler.cleanUpWorkspaceSpecificStringArray( 63 | result[language]); 64 | } 65 | 66 | return result; 67 | } 68 | 69 | private static mergeLanguageSpecificSettingValue(value1: LanguageSpecificSettingValue, 70 | value2: LanguageSpecificSettingValue | undefined): void { 71 | if (value2 == null) return; 72 | 73 | for (const language in value2) { 74 | if (!Object.prototype.hasOwnProperty.call(value2, language)) continue; 75 | if (!Object.prototype.hasOwnProperty.call(value1, language)) value1[language] = []; 76 | 77 | for (const entry of value2[language]) { 78 | value1[language].push(entry); 79 | } 80 | } 81 | } 82 | 83 | public static cleanUpWorkspaceSpecificStringArray(array: string[]): string[] { 84 | const negativeSet: Set = new Set(); 85 | const positiveSet: Set = new Set(); 86 | 87 | for (let entry of array) { 88 | if (entry.startsWith('-')) { 89 | entry = entry.substr(1); 90 | 91 | if (positiveSet.has(entry)) { 92 | positiveSet.delete(entry); 93 | } else { 94 | negativeSet.add(entry); 95 | } 96 | } else { 97 | positiveSet.add(entry); 98 | 99 | if (negativeSet.has(entry)) { 100 | negativeSet.delete(entry); 101 | } else { 102 | positiveSet.add(entry); 103 | } 104 | } 105 | } 106 | 107 | const result: string[] = []; 108 | for (const entry of negativeSet) result.push(`-${entry}`); 109 | for (const entry of positiveSet) result.push(entry); 110 | result.sort((a: string, b: string) => a.localeCompare(b, undefined, {sensitivity: 'base'})); 111 | 112 | return result; 113 | } 114 | 115 | public handle(params: {items: ConfigurationItem[]}): ConfigurationResultItem[] { 116 | const result: ConfigurationResultItem[] = []; 117 | 118 | for (const item of params.items) { 119 | const uri: Code.Uri = Code.Uri.parse(item.scopeUri); 120 | result.push({ 121 | dictionary: this.mergeSettings(uri, 'dictionary'), 122 | disabledRules: this.mergeSettings(uri, 'disabledRules'), 123 | enabledRules: this.mergeSettings(uri, 'enabledRules'), 124 | hiddenFalsePositives: this.mergeSettings(uri, 'hiddenFalsePositives'), 125 | }); 126 | } 127 | 128 | return result; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | valentjn (a) bsplines.org. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | . 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | . Translations are available at 128 | . 129 | -------------------------------------------------------------------------------- /src/StatusBarItemManager.ts: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2019-2021 Julian Valentin, LTeX Development Community 2 | * 3 | * This Source Code Form is subject to the terms of the Mozilla Public 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this 5 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 | */ 7 | 8 | // #if TARGET == 'vscode' 9 | import * as Code from 'vscode'; 10 | import * as CodeLanguageClient from 'vscode-languageclient/node'; 11 | // #elseif TARGET == 'coc.nvim' 12 | // import * as Code from 'coc.nvim'; 13 | // import CodeLanguageClient = Code; 14 | // #endif 15 | 16 | import {i18n} from './I18n'; 17 | import Logger from './Logger'; 18 | 19 | enum Status { 20 | starting, 21 | ready, 22 | checking, 23 | } 24 | 25 | interface ProgressParams { 26 | token: CodeLanguageClient.ProgressToken; 27 | value: T; 28 | } 29 | 30 | export default class StatusBarItemManager { 31 | private _statusBarItem: Code.StatusBarItem; 32 | private _progressMap: { 33 | [token: string]: { 34 | progress: number; 35 | startTime: number; 36 | }; 37 | }; 38 | private _status: Status; 39 | 40 | private _startingStatusText: string; 41 | private _readyStatusText: string; 42 | private _checkingStatusText: string; 43 | 44 | private static readonly readyStatusDuration: number = 1000; 45 | private static readonly checkingStatusDelay: number = 5000; 46 | 47 | public constructor(context: Code.ExtensionContext) { 48 | // #if TARGET == 'vscode' 49 | this._statusBarItem = Code.window.createStatusBarItem(Code.StatusBarAlignment.Left); 50 | // #elseif TARGET == 'coc.nvim' 51 | // this._statusBarItem = Code.window.createStatusBarItem(); 52 | // #endif 53 | this._progressMap = {}; 54 | this._status = Status.starting; 55 | 56 | // #if TARGET == 'vscode' 57 | this._startingStatusText = `$(loading~spin) ${i18n('startingLtex')}`; 58 | this._readyStatusText = `$(check) ${i18n('ltexReady')}`; 59 | this._checkingStatusText = `$(loading~spin) ${i18n('ltexIsChecking')}`; 60 | // #elseif TARGET == 'coc.nvim' 61 | // this._startingStatusText = i18n('startingLtex'); 62 | // this._readyStatusText = i18n('ltexReady'); 63 | // this._checkingStatusText = i18n('ltexIsChecking'); 64 | // #endif 65 | 66 | context.subscriptions.push(this._statusBarItem); 67 | context.subscriptions.push(Code.workspace.onDidChangeConfiguration( 68 | this.onDidChangeConfiguration, this)); 69 | 70 | this.setStatusToStarting(); 71 | } 72 | 73 | private onDidChangeConfiguration(event: Code.ConfigurationChangeEvent): void { 74 | if (event.affectsConfiguration('ltex.statusBarItem')) this.update(); 75 | } 76 | 77 | private update(): void { 78 | if (this._status == Status.starting) { 79 | this._statusBarItem.text = this._startingStatusText; 80 | // #if TARGET == 'coc.nvim' 81 | // this._statusBarItem.isProgress = true; 82 | // #endif 83 | this._statusBarItem.show(); 84 | return; 85 | } 86 | 87 | const tokens: string[] = StatusBarItemManager.getKeys(this._progressMap); 88 | const now: number = Date.now(); 89 | 90 | for (const token of tokens) { 91 | if (now - this._progressMap[token].startTime >= StatusBarItemManager.checkingStatusDelay) { 92 | this._status = Status.checking; 93 | this._statusBarItem.text = this._checkingStatusText; 94 | // #if TARGET == 'coc.nvim' 95 | // this._statusBarItem.isProgress = true; 96 | // #endif 97 | this._statusBarItem.show(); 98 | return; 99 | } 100 | } 101 | 102 | const oldStatus: Status = this._status; 103 | this._status = Status.ready; 104 | this._statusBarItem.text = this._readyStatusText; 105 | 106 | if (oldStatus == Status.checking) { 107 | this.setStatusToReady(); 108 | return; 109 | } 110 | 111 | const visible: boolean | undefined = 112 | Code.workspace.getConfiguration('ltex').get('statusBarItem'); 113 | 114 | if ((visible != null) && visible) { 115 | this._statusBarItem.show(); 116 | } else { 117 | this._statusBarItem.hide(); 118 | } 119 | } 120 | 121 | private static getKeys(obj: any): string[] { 122 | const result: string[] = []; 123 | 124 | for (const key in obj) { 125 | if (Object.prototype.hasOwnProperty.call(obj, key)) result.push(key); 126 | } 127 | 128 | return result; 129 | } 130 | 131 | public setStatusToStarting(): void { 132 | this._status = Status.starting; 133 | this.update(); 134 | } 135 | 136 | public setStatusToReady(): void { 137 | this._status = Status.ready; 138 | this._statusBarItem.text = this._readyStatusText; 139 | // #if TARGET == 'coc.nvim' 140 | // this._statusBarItem.isProgress = true; 141 | // #endif 142 | this._statusBarItem.show(); 143 | setTimeout(this.update.bind(this), StatusBarItemManager.readyStatusDuration); 144 | } 145 | 146 | public handleProgressNotification( 147 | params: ProgressParams): void { 150 | let token: { 151 | uri: string, 152 | operation: string, 153 | uuid: string, 154 | }; 155 | 156 | try { 157 | token = JSON.parse(params.token.toString()); 158 | } catch (e: unknown) { 159 | Logger.warn(i18n('couldNotParseTokenInProgressNotification', params.token, params)); 160 | return; 161 | } 162 | 163 | if (token.operation != 'checkDocument') { 164 | Logger.warn(i18n('unknownOperationInProgressNotification', token.operation, params)); 165 | return; 166 | } 167 | 168 | if (params.value.kind == 'begin') { 169 | this._progressMap[params.token] = {progress: 0, startTime: Date.now()}; 170 | setTimeout(this.update.bind(this), StatusBarItemManager.checkingStatusDelay + 100); 171 | } else if (params.value.kind == 'end') { 172 | if (Object.prototype.hasOwnProperty.call(this._progressMap, params.token)) { 173 | delete this._progressMap[params.token]; 174 | this.update(); 175 | } 176 | } 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/test/TestRunner.ts: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2019-2021 Julian Valentin, LTeX Development Community 2 | * 3 | * This Source Code Form is subject to the terms of the Mozilla Public 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this 5 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 | */ 7 | 8 | // #if TARGET == 'vscode' 9 | import * as ChildProcess from 'child_process'; 10 | import { 11 | downloadAndUnzipVSCode, resolveCliPathFromVSCodeExecutablePath, runTests, 12 | } from '@vscode/test-electron'; 13 | import * as Fs from 'fs'; 14 | import * as Path from 'path'; 15 | import * as Rimraf from 'rimraf'; 16 | 17 | import {version as ltexVersion} from '../../package.json'; 18 | 19 | let ltexDirPath: string; 20 | let vscodeExecutablePath: string; 21 | let cliPath: string; 22 | 23 | const originalPath: string | undefined = process.env['PATH']; 24 | const originalJavaHome: string | undefined = process.env['JAVA_HOME']; 25 | 26 | async function runTestIteration(testIteration: number): Promise { 27 | const useOfflinePackage: boolean = ((testIteration & 1) != 0); 28 | 29 | console.log(''); 30 | console.log(`Running test iteration ${testIteration}...`); 31 | console.log(''); 32 | console.log(`useOfflinePackage = ${useOfflinePackage}`); 33 | console.log(''); 34 | 35 | const tmpDirPrefix: string = Path.join(ltexDirPath, 'tmp-'); 36 | console.log(`Creating temporary directory with prefix '${tmpDirPrefix}'...`); 37 | const tmpDirPath: string = Fs.mkdtempSync(tmpDirPrefix); 38 | console.log(`Created temporary directory '${tmpDirPath}'.`); 39 | 40 | const ltexLibDirPath: string = Path.join(ltexDirPath, 'lib'); 41 | const gitCleanArgs: string[] = ['-C', ltexDirPath, 'clean', '-f', '-x', ltexLibDirPath]; 42 | console.log(`Cleaning '${ltexLibDirPath}'...`); 43 | const childProcess: ChildProcess.SpawnSyncReturns = ChildProcess.spawnSync( 44 | 'git', gitCleanArgs, {encoding: 'utf-8', timeout: 10000}); 45 | 46 | if (childProcess.status != 0) { 47 | throw new Error(`Could not clean '${ltexLibDirPath}'. ` 48 | + `Exit code: ${childProcess.status}. stdout: '${childProcess.stdout}'. ` 49 | + `stderr: '${childProcess.stderr}'.`); 50 | } 51 | 52 | try { 53 | const userDataDirPath: string = Path.join(tmpDirPath, 'user'); 54 | const extensionsDirPath: string = Path.join(tmpDirPath, 'extensions'); 55 | const workspaceDirPath: string = Path.join(tmpDirPath, 'workspace'); 56 | Fs.mkdirSync(workspaceDirPath); 57 | 58 | const cliArgs: string[] = [ 59 | '--user-data-dir', userDataDirPath, 60 | '--extensions-dir', extensionsDirPath, 61 | '--install-extension', 'james-yu.latex-workshop', 62 | ]; 63 | 64 | if (useOfflinePackage) { 65 | let platform: string = 'linux'; 66 | if (process.platform == 'darwin') platform = 'mac'; 67 | else if (process.platform == 'win32') platform = 'windows'; 68 | cliArgs.push('--install-extension', Path.join(ltexDirPath, 69 | `ltex-${ltexVersion}-offline-${platform}-x64.vsix`)); 70 | } 71 | 72 | console.log('Calling Code CLI for extension installation...'); 73 | const childProcess: ChildProcess.SpawnSyncReturns = ChildProcess.spawnSync( 74 | cliPath, cliArgs, {encoding: 'utf-8', stdio: 'inherit'}); 75 | 76 | if (childProcess.status != 0) throw new Error('Could not install extensions.'); 77 | 78 | if (originalPath != null) { 79 | process.env['PATH'] = originalPath; 80 | } else if (process.env['PATH'] != null) { 81 | delete process.env['PATH']; 82 | } 83 | 84 | if (originalJavaHome != null) { 85 | process.env['JAVA_HOME'] = originalJavaHome; 86 | } else if (process.env['JAVA_HOME'] != null) { 87 | delete process.env['JAVA_HOME']; 88 | } 89 | 90 | if (useOfflinePackage) { 91 | console.log(`Removing '${ltexLibDirPath}'...`); 92 | Rimraf.sync(ltexLibDirPath); 93 | const ltexOfflineLibDirPath: string = 94 | Path.join(extensionsDirPath, `neo-ltex.ltex-${ltexVersion}`, 'lib'); 95 | console.log(`Moving '${ltexOfflineLibDirPath}' to '${ltexLibDirPath}'...`); 96 | Fs.renameSync(ltexOfflineLibDirPath, ltexLibDirPath); 97 | } 98 | 99 | console.log('Running tests...'); 100 | const exitCode: number = await runTests({ 101 | vscodeExecutablePath: vscodeExecutablePath, 102 | launchArgs: [ 103 | '--user-data-dir', userDataDirPath, 104 | '--extensions-dir', extensionsDirPath, 105 | workspaceDirPath, 106 | ], 107 | extensionDevelopmentPath: ltexDirPath, 108 | extensionTestsPath: Path.join(__dirname, './index'), 109 | }); 110 | 111 | if (exitCode != 0) throw new Error(`Test returned exit code ${exitCode}.`); 112 | } finally { 113 | if (tmpDirPath != null) { 114 | try { 115 | Rimraf.sync(tmpDirPath); 116 | } catch { 117 | console.log(`Could not delete temporary directory '${tmpDirPath}', leaving on disk.`); 118 | } 119 | } 120 | } 121 | 122 | return Promise.resolve(); 123 | } 124 | 125 | async function main(): Promise { 126 | let fastMode: boolean = false; 127 | let onlyTestIteration: number | null = null; 128 | 129 | for (let i: number = 0; i < process.argv.length; i++) { 130 | const arg: string = process.argv[i]; 131 | 132 | if (arg == '--fast') { 133 | fastMode = true; 134 | } else if ((arg == '--iteration') && (i < process.argv.length - 1)) { 135 | onlyTestIteration = parseInt(process.argv[i + 1]); 136 | i++; 137 | } 138 | } 139 | 140 | ltexDirPath = Path.resolve(__dirname, '..', '..'); 141 | const codeVersion: string = '1.57.1'; 142 | let codePlatform: string | undefined; 143 | 144 | console.log('Downloading and installing VS Code...'); 145 | vscodeExecutablePath = await downloadAndUnzipVSCode(codeVersion, codePlatform); 146 | 147 | console.log('Resolving CLI path to VS Code...'); 148 | cliPath = resolveCliPathFromVSCodeExecutablePath(vscodeExecutablePath); 149 | 150 | for (let testIteration: number = 0; testIteration < 2; testIteration++) { 151 | if (fastMode && (testIteration != 1)) continue; 152 | if ((onlyTestIteration != null) && (testIteration != onlyTestIteration)) continue; 153 | await runTestIteration(testIteration); 154 | } 155 | 156 | return Promise.resolve(); 157 | } 158 | 159 | main(); 160 | // #endif 161 | -------------------------------------------------------------------------------- /img/logo-ltex.svg: -------------------------------------------------------------------------------- 1 | 2 | 20 | 45 | 47 | 49 | 50 | 52 | image/svg+xml 53 | 55 | 56 | 57 | 58 | 59 | 67 | 72 | 74 | 99 | 101 | 106 | 111 | 116 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /src/StatusPrinter.ts: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2019-2021 Julian Valentin, LTeX Development Community 2 | * 3 | * This Source Code Form is subject to the terms of the Mozilla Public 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this 5 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 | */ 7 | 8 | // #if TARGET == 'vscode' 9 | import * as Code from 'vscode'; 10 | import * as CodeLanguageClient from 'vscode-languageclient/node'; 11 | // #elseif TARGET == 'coc.nvim' 12 | // import * as Code from 'coc.nvim'; 13 | // import CodeLanguageClient = Code; 14 | // #endif 15 | 16 | import DependencyManager from './DependencyManager'; 17 | import ExternalFileManager from './ExternalFileManager'; 18 | import {i18n} from './I18n'; 19 | import Logger from './Logger'; 20 | 21 | interface ServerCommandResult { 22 | success: boolean; 23 | errorMessage?: string; 24 | } 25 | 26 | interface GetServerStatusCommandResult extends ServerCommandResult { 27 | processId: number | null; 28 | wallClockDuration: number | null; 29 | cpuDuration: number | null; 30 | cpuUsage: number | null; 31 | usedMemory: number | null; 32 | totalMemory: number | null; 33 | isChecking: boolean | null; 34 | documentUriBeingChecked: string | null; 35 | } 36 | 37 | export default class StatusPrinter { 38 | private _context: Code.ExtensionContext; 39 | private _dependencyManager: DependencyManager; 40 | private _externalFileManager: ExternalFileManager; 41 | private _languageClient: CodeLanguageClient.LanguageClient | null; 42 | 43 | private static readonly _ltexLsStatusTimeout: number = 500; 44 | 45 | public constructor(context: Code.ExtensionContext, dependencyManager: DependencyManager, 46 | externalFileManager: ExternalFileManager) { 47 | this._context = context; 48 | this._dependencyManager = dependencyManager; 49 | this._externalFileManager = externalFileManager; 50 | this._languageClient = null; 51 | } 52 | 53 | public set languageClient(languageClient: CodeLanguageClient.LanguageClient) { 54 | this._languageClient = languageClient; 55 | } 56 | 57 | public async print(): Promise { 58 | let ltexLsStatus: GetServerStatusCommandResult = { 59 | success: false, 60 | processId: null, 61 | wallClockDuration: null, 62 | cpuDuration: null, 63 | cpuUsage: null, 64 | usedMemory: null, 65 | totalMemory: null, 66 | isChecking: null, 67 | documentUriBeingChecked: null, 68 | }; 69 | 70 | if (this._languageClient != null) { 71 | try { 72 | ltexLsStatus = await this._languageClient.sendRequest('workspace/executeCommand', 73 | {command: '_ltex.getServerStatus', arguments: []}); 74 | } catch { 75 | // ignore errors 76 | } 77 | } 78 | 79 | Logger.log(i18n('ltexStatus')); 80 | Logger.log(i18n('separationLine')); 81 | 82 | Logger.log(i18n('vscodeLtexInfo')); 83 | Logger.log(i18n('extensionDirPath', StatusPrinter.formatString(this._context.extensionPath))); 84 | Logger.log(i18n('userSettingsDirPath', 85 | StatusPrinter.formatString(this._externalFileManager.getUserSettingsDirPath()))); 86 | Logger.log(i18n('workspaceSettingsDirPath', 87 | StatusPrinter.formatString(this._externalFileManager.getWorkspaceSettingsDirPath()))); 88 | 89 | let activeDocumentUri: Code.Uri | null = null; 90 | 91 | // #if TARGET == 'vscode' 92 | const activeTextEditor: Code.TextEditor | undefined = Code.window.activeTextEditor; 93 | if (activeTextEditor != null) activeDocumentUri = activeTextEditor.document.uri; 94 | // #elseif TARGET == 'coc.nvim' 95 | // const activeDocument: Code.Document = await Code.workspace.document; 96 | // activeDocumentUri = Code.Uri.parse(activeDocument.uri); 97 | // #endif 98 | 99 | let workspaceFolderSettingsDirPath: string | null = null; 100 | 101 | if (activeDocumentUri != null) { 102 | workspaceFolderSettingsDirPath = this._externalFileManager.getWorkspaceFolderSettingsDirPath( 103 | activeDocumentUri); 104 | } 105 | 106 | Logger.log(i18n('workspaceFolderSettingsDirPath', 107 | StatusPrinter.formatString(workspaceFolderSettingsDirPath))); 108 | Logger.log(i18n('version', 109 | StatusPrinter.formatString(this._dependencyManager.vscodeLtexVersion))); 110 | 111 | Logger.log(i18n('ltexLsInfo')); 112 | Logger.log(i18n('version', 113 | StatusPrinter.formatString(this._dependencyManager.ltexLsVersion))); 114 | Logger.log(i18n('processId', StatusPrinter.formatNumber(ltexLsStatus.processId))); 115 | Logger.log(i18n('wallClockDuration', 116 | StatusPrinter.formatDuration(ltexLsStatus.wallClockDuration))); 117 | Logger.log(i18n('cpuDuration', StatusPrinter.formatDuration(ltexLsStatus.cpuDuration))); 118 | Logger.log(i18n('cpuUsage', StatusPrinter.formatFraction(ltexLsStatus.cpuUsage))); 119 | Logger.log(i18n('totalMemory', StatusPrinter.formatMemory(ltexLsStatus.totalMemory))); 120 | Logger.log(i18n('usedMemory', StatusPrinter.formatMemory(ltexLsStatus.usedMemory))); 121 | Logger.log(i18n('isBusyChecking', 122 | StatusPrinter.formatBoolean(ltexLsStatus.isChecking))); 123 | Logger.log(i18n('documentUriBeingChecked', 124 | StatusPrinter.formatString(ltexLsStatus.documentUriBeingChecked))); 125 | 126 | const watchedExternalFiles: string[] = this._externalFileManager.getAllWatchedExternalFiles(); 127 | 128 | if (watchedExternalFiles.length > 0) { 129 | Logger.log(i18n('currentlyWatchedExternalSettingFiles')); 130 | 131 | for (const filePath of this._externalFileManager.getAllWatchedExternalFiles()) { 132 | Logger.log(i18n('externalSettingFile', filePath)); 133 | } 134 | } else { 135 | Logger.log(i18n('currentlyWatchedExternalSettingFilesNa')); 136 | } 137 | 138 | Logger.log(i18n('separationLine')); 139 | 140 | return Promise.resolve(); 141 | } 142 | 143 | private static formatString(str: string | null): string { 144 | return ((str != null) ? str : i18n('na')); 145 | } 146 | 147 | private static formatBoolean(x: boolean | null): string { 148 | return ((x != null) ? (x ? i18n('true') : i18n('false')) : i18n('na')); 149 | } 150 | 151 | private static formatNumber(x: number | null): string { 152 | return ((x != null) ? `${x}` : i18n('na')); 153 | } 154 | 155 | private static formatFraction(fraction: number | null): string { 156 | return ((fraction != null) ? `${(100 * fraction).toFixed(1)}%` : i18n('na')); 157 | } 158 | 159 | private static formatDuration(duration: number | null): string { 160 | if (duration == null) return i18n('na'); 161 | const milliseconds: number = Math.floor(1000 * (duration % 1)); 162 | const seconds: number = Math.floor(duration % 60); 163 | const minutes: number = Math.floor(duration / 60) % 60; 164 | const hours: number = Math.floor(duration / 3600); 165 | const pad: ((x: number, length: number) => string) = 166 | ((x: number, length: number) => x.toString().padStart(length, '0')); 167 | 168 | if (hours > 0) { 169 | return `${hours}h${pad(minutes, 2)}m${pad(seconds, 2)}s.${pad(milliseconds, 3)}`; 170 | } else if (minutes > 0) { 171 | return `${minutes}m${pad(seconds, 2)}s.${pad(milliseconds, 3)}`; 172 | } else { 173 | return `${seconds}s.${pad(milliseconds, 3)}`; 174 | } 175 | } 176 | 177 | private static formatMemory(memory: number | null): string { 178 | return ((memory != null) ? `${(memory / 1e6).toFixed(2)}MB` : i18n('na')); 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2019-2021 Julian Valentin, LTeX Development Community 2 | * 3 | * This Source Code Form is subject to the terms of the Mozilla Public 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this 5 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 | */ 7 | 8 | // #if TARGET == 'vscode' 9 | import * as Code from 'vscode'; 10 | import * as CodeLanguageClient from 'vscode-languageclient/node'; 11 | // #elseif TARGET == 'coc.nvim' 12 | // import * as Code from 'coc.nvim'; 13 | // import CodeLanguageClient = Code; 14 | // #endif 15 | 16 | import BugReporter from './BugReporter'; 17 | import CommandHandler from './CommandHandler'; 18 | import DependencyManager from './DependencyManager'; 19 | import ExternalFileManager from './ExternalFileManager'; 20 | import {I18n, i18n} from './I18n'; 21 | import Logger from './Logger'; 22 | import LoggingOutputChannel from './LoggingOutputChannel'; 23 | import StatusBarItemManager from './StatusBarItemManager'; 24 | import StatusPrinter from './StatusPrinter'; 25 | import WorkspaceConfigurationRequestHandler from './WorkspaceConfigurationRequestHandler'; 26 | 27 | export class Api { 28 | public languageClient: CodeLanguageClient.LanguageClient | null = null; 29 | public clientOutputChannel: LoggingOutputChannel | null = null; 30 | public serverOutputChannel: LoggingOutputChannel | null = null; 31 | } 32 | 33 | let dependencyManager: DependencyManager | null = null; 34 | let api: Api | null = null; 35 | 36 | async function languageClientIsReady(languageClient: CodeLanguageClient.LanguageClient, 37 | externalFileManager: ExternalFileManager, 38 | statusBarItemManager: StatusBarItemManager): Promise { 39 | statusBarItemManager.setStatusToReady(); 40 | languageClient.onNotification('$/progress', 41 | statusBarItemManager.handleProgressNotification.bind(statusBarItemManager)); 42 | 43 | const workspaceConfigurationRequestHandler: WorkspaceConfigurationRequestHandler = 44 | new WorkspaceConfigurationRequestHandler(externalFileManager); 45 | languageClient.onRequest('ltex/workspaceSpecificConfiguration', 46 | workspaceConfigurationRequestHandler.handle.bind(workspaceConfigurationRequestHandler)); 47 | } 48 | 49 | async function startLanguageClient(context: Code.ExtensionContext, 50 | externalFileManager: ExternalFileManager, statusPrinter: StatusPrinter): 51 | Promise { 52 | let serverOptions: CodeLanguageClient.ServerOptions | null = null; 53 | 54 | // #if TARGET == 'vscode' 55 | if (context.extensionMode == Code.ExtensionMode.Development) { 56 | serverOptions = DependencyManager.getDebugServerOptions(); 57 | } 58 | // #endif 59 | 60 | if (serverOptions == null) { 61 | if (dependencyManager == null) { 62 | Logger.error('DependencyManager not initialized!'); 63 | return Promise.resolve(null); 64 | } 65 | 66 | const success: boolean = await dependencyManager.install(); 67 | if (success !== true) return Promise.resolve(null); 68 | serverOptions = await dependencyManager.getLtexLsExecutable(); 69 | } 70 | 71 | const statusBarItemManager: StatusBarItemManager = new StatusBarItemManager(context); 72 | 73 | const workspaceConfig: Code.WorkspaceConfiguration = Code.workspace.getConfiguration('ltex'); 74 | const enabled: any = workspaceConfig.get('enabled'); 75 | let enabledCodeLanguageIds: string[]; 76 | 77 | if ((enabled === true) || (enabled === false)) { 78 | enabledCodeLanguageIds = (enabled ? CommandHandler.getDefaultCodeLanguageIds() : []); 79 | } else { 80 | enabledCodeLanguageIds = enabled; 81 | } 82 | 83 | const documentSelector: CodeLanguageClient.DocumentFilter[] = []; 84 | 85 | for (const codeLanguageId of enabledCodeLanguageIds) { 86 | documentSelector.push({scheme: 'file', language: codeLanguageId}); 87 | documentSelector.push({scheme: 'untitled', language: codeLanguageId}); 88 | documentSelector.push({scheme: 'vscode-notebook-cell', language: codeLanguageId}); 89 | } 90 | 91 | const clientOptions: CodeLanguageClient.LanguageClientOptions = { 92 | documentSelector: documentSelector, 93 | synchronize: { 94 | configurationSection: 'ltex', 95 | }, 96 | // LSP sends locale itself since LSP 3.16.0. However, this would require VS Code 1.53.0. 97 | // Currently, we only require VS Code 1.52.0. 98 | initializationOptions: { 99 | // #if TARGET == 'vscode' 100 | locale: Code.env.language, 101 | // #endif 102 | customCapabilities: { 103 | workspaceSpecificConfiguration: true, 104 | }, 105 | }, 106 | revealOutputChannelOn: CodeLanguageClient.RevealOutputChannelOn.Never, 107 | // #if TARGET == 'vscode' 108 | traceOutputChannel: Logger.clientOutputChannel, 109 | // #endif 110 | outputChannel: Logger.serverOutputChannel, 111 | }; 112 | 113 | const languageClient: CodeLanguageClient.LanguageClient = new CodeLanguageClient.LanguageClient( 114 | 'ltex', i18n('ltexLanguageServer'), serverOptions, clientOptions); 115 | 116 | languageClient.onReady().then(languageClientIsReady.bind( 117 | null, languageClient, externalFileManager, statusBarItemManager)); 118 | statusPrinter.languageClient = languageClient; 119 | 120 | if ('command' in serverOptions) { 121 | Logger.log(i18n('startingLtexLs')); 122 | Logger.logExecutable(serverOptions); 123 | Logger.log(''); 124 | } 125 | 126 | languageClient.info(i18n('startingLtexLs')); 127 | const languageClientDisposable: Code.Disposable = languageClient.start(); 128 | context.subscriptions.push(languageClientDisposable); 129 | 130 | return Promise.resolve(languageClient); 131 | } 132 | 133 | export async function activate(context: Code.ExtensionContext): Promise { 134 | Logger.createOutputChannels(context); 135 | I18n.initialize(context); 136 | 137 | api = new Api(); 138 | api.clientOutputChannel = Logger.clientOutputChannel; 139 | api.serverOutputChannel = Logger.serverOutputChannel; 140 | 141 | dependencyManager = new DependencyManager(context); 142 | 143 | const externalFileManager: ExternalFileManager = new ExternalFileManager(context); 144 | const statusPrinter: StatusPrinter = new StatusPrinter( 145 | context, dependencyManager, externalFileManager); 146 | const bugReporter: BugReporter = new BugReporter(context, dependencyManager, statusPrinter); 147 | const commandHandler: CommandHandler = new CommandHandler( 148 | context, externalFileManager, statusPrinter, bugReporter); 149 | 150 | const workspaceConfig: Code.WorkspaceConfiguration = Code.workspace.getConfiguration('ltex'); 151 | const enabled: any = workspaceConfig.get('enabled'); 152 | 153 | if ((enabled === true) || (enabled.length > 0)) { 154 | try { 155 | api.languageClient = await startLanguageClient(context, externalFileManager, statusPrinter); 156 | commandHandler.languageClient = api.languageClient; 157 | } catch (e: unknown) { 158 | Logger.error(i18n('couldNotStartLanguageClient'), e); 159 | Logger.showClientOutputChannel(); 160 | Code.window.showErrorMessage(i18n('couldNotStartLanguageClient')); 161 | } 162 | } 163 | 164 | return Promise.resolve(api); 165 | } 166 | 167 | export function deactivate(): Thenable | undefined { 168 | if (!api) { 169 | return undefined; 170 | } 171 | if (!api.languageClient) { 172 | return undefined; 173 | } 174 | return api.languageClient.stop(); 175 | } 176 | -------------------------------------------------------------------------------- /tools/validateJsonFiles.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import ast 4 | import base64 5 | import json 6 | import os 7 | import pathlib 8 | import re 9 | import sys 10 | from typing import Any, List 11 | 12 | import jsonschema 13 | 14 | sys.path.append(str(pathlib.Path(__file__).parent)) 15 | import common 16 | 17 | 18 | 19 | packageJsonFilePath = common.repoDirPath.joinpath("package.json") 20 | with open(packageJsonFilePath, "r") as f: packageJson = json.load(f) 21 | 22 | 23 | 24 | def getJsonSchemaFromVsCodeGitHub(typeScriptPath: str, regex: re.Pattern[str]) -> Any: 25 | response = common.requestFromGitHub( 26 | f"https://api.github.com/repos/microsoft/vscode/contents/{typeScriptPath}") 27 | assert(response["encoding"] == "base64") 28 | configurationExtensionPointJavaScript = base64.b64decode(response["content"]).decode() 29 | 30 | regexMatch = regex.search(configurationExtensionPointJavaScript) 31 | assert regexMatch is not None 32 | string = regexMatch.group(1) 33 | 34 | string = re.sub(r"^([ \t]*)([$A-Za-z]+):", r'\1"\2":', string, flags=re.MULTILINE) 35 | string = string.replace("[{ body: {", "[{ \"body\": {") 36 | string = string.replace("{ command: ", "{ \"command\": ") 37 | string = string.replace("{ title: '', properties: {} }", "{ \"title\": '', \"properties\": {} }") 38 | 39 | string = re.sub(r"^([ \t]*)\"([$A-Za-z]+)\": false(,)?$", r'\1"\2": False\3', string, 40 | flags=re.MULTILINE) 41 | string = re.sub(r"^([ \t]*)\"([$A-Za-z]+)\": true(,)?$", r'\1"\2": True\3', string, 42 | flags=re.MULTILINE) 43 | 44 | stringRegex = r"(?:'.*?'|\".*?\"|`.*?`)" 45 | string = re.sub(rf"(?:nls\.)?localize\({stringRegex}, ({stringRegex})(?:, {stringRegex})*\)", 46 | lambda x: f'"{x.group(1)[1:-1]}"', string) 47 | jsonSchema = ast.literal_eval(string) 48 | 49 | return jsonSchema 50 | 51 | 52 | 53 | def validatePackageJsonWithSchema() -> None: 54 | print("Validating package.json with schema from JSON Schema Store...") 55 | 56 | packageJsonSchemaFilePath = common.repoDirPath.joinpath("schemas", "package.schema.json") 57 | with open(packageJsonSchemaFilePath, "r") as f: packageJsonSchema = json.load(f) 58 | jsonschema.validate(packageJson, packageJsonSchema) 59 | 60 | 61 | 62 | def validatePackageJsonWalkthroughsWithSchema() -> None: 63 | print("Validating package.json walkthroughs with schema from VS Code...") 64 | 65 | jsonSchema = getJsonSchemaFromVsCodeGitHub( 66 | "src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStartedExtensionPoint.ts", 67 | re.compile(r"const walkthroughsExtensionPoint.* = (?s:.*?^\tjsonSchema: (\{.*?^\t\}))", 68 | flags=re.MULTILINE)) 69 | jsonschema.validate(packageJson["contributes"]["walkthroughs"], jsonSchema) 70 | 71 | 72 | 73 | def validatePackageJsonConfigurationWithSchema() -> None: 74 | print("Validating package.json configuration with schema from VS Code...") 75 | 76 | configurationEntrySchema = getJsonSchemaFromVsCodeGitHub( 77 | "src/vs/workbench/api/common/configurationExtensionPoint.ts", 78 | re.compile(r"const configurationEntrySchema.* = (?s:(\{.*?^\}))", 79 | flags=re.MULTILINE)) 80 | configurationSchema = { 81 | "description" : "Contributes configuration settings.", 82 | "oneOf" : [ 83 | configurationEntrySchema, 84 | { 85 | "type" : "array", 86 | "items" : configurationEntrySchema, 87 | }, 88 | ], 89 | } 90 | 91 | jsonschema.validate(packageJson["contributes"]["configuration"], configurationSchema) 92 | 93 | 94 | 95 | def validatePackageJsonConfigurationWithCustomConstraints() -> None: 96 | print("Validating package.json configuration with custom constraints...") 97 | 98 | for settingName, setting in packageJson["contributes"]["configuration"]["properties"].items(): 99 | try: 100 | jsonschema.validate(setting["default"], setting) 101 | for example in setting.get("examples", []): jsonschema.validate(example, setting) 102 | 103 | assert ("markdownDescription" in setting) or ( 104 | ("markdownDeprecationMessage" in setting) and ("deprecationMessage" in setting)) 105 | validateSetting(setting) 106 | except: 107 | print("") 108 | print(f"Could not validate '{settingName}'!") 109 | raise 110 | 111 | 112 | 113 | def validateSetting(setting: Any) -> None: 114 | if isinstance(setting, list): 115 | for entry in setting: validateSetting(entry) 116 | return 117 | elif not isinstance(setting, dict): 118 | return 119 | 120 | if "enum" in setting: 121 | assert len(setting["markdownEnumDescriptions"]) == len(setting["enum"]) 122 | assert len(setting["enumDescriptions"]) == len(setting["enum"]) 123 | 124 | if setting.get("type", None) == "object": 125 | assert setting["additionalProperties"] == False 126 | 127 | for key, value in setting.items(): 128 | if key == "propertyNames": continue 129 | validateSetting(value) 130 | 131 | 132 | 133 | def validatePackageNlsJson() -> None: 134 | usedNlsKeys = getUsedNlsKeysFromPackageNlsJson(packageJson) 135 | 136 | for childPath in common.repoDirPath.iterdir(): 137 | if not childPath.is_file(): continue 138 | regexMatch = re.match(r"^package\.nls(\..+)?\.json$", childPath.name) 139 | if regexMatch is None: continue 140 | 141 | print(f"Validating {childPath.name}...") 142 | with open(childPath, "r") as f: packageNlsJson = json.load(f) 143 | 144 | for nlsKey in sorted(list(packageNlsJson)): 145 | if nlsKey.endswith(".fullMarkdownDescription"): continue 146 | assert nlsKey in usedNlsKeys, f"NLS key '{nlsKey}' is defined, but unused" 147 | 148 | for nlsKey in usedNlsKeys: 149 | assert nlsKey in packageNlsJson, f"NLS key '{nlsKey}' is used, but undefined" 150 | 151 | 152 | 153 | def getUsedNlsKeysFromPackageNlsJson(setting: Any) -> List[str]: 154 | usedNlsKeys = [] 155 | 156 | if isinstance(setting, list): 157 | for entry in setting: usedNlsKeys.extend(getUsedNlsKeysFromPackageNlsJson(entry)) 158 | elif isinstance(setting, dict): 159 | for value in setting.values(): usedNlsKeys.extend(getUsedNlsKeysFromPackageNlsJson(value)) 160 | elif isinstance(setting, str): 161 | if setting.startswith("%") and setting.endswith("%"): usedNlsKeys.append(setting[1 : -1]) 162 | 163 | return sorted(list(set(usedNlsKeys))) 164 | 165 | 166 | 167 | def validateMessagesNlsJson() -> None: 168 | usedNlsKeys = getUsedNlsKeysFromTypeScript() 169 | 170 | for childPath in common.repoDirPath.joinpath("i18n").iterdir(): 171 | if not childPath.is_file(): continue 172 | regexMatch = re.match(r"^messages\.nls(\..+)?\.json$", childPath.name) 173 | if regexMatch is None: continue 174 | 175 | print(f"Validating {childPath.name}...") 176 | with open(childPath, "r") as f: messagesNlsJson = json.load(f) 177 | 178 | for nlsKey in sorted(list(messagesNlsJson)): 179 | assert nlsKey in usedNlsKeys, f"NLS key '{nlsKey}' is defined, but unused" 180 | 181 | previousNlsKey = None 182 | 183 | for nlsKey in messagesNlsJson: 184 | if previousNlsKey is None: 185 | previousNlsKey = nlsKey 186 | else: 187 | assert nlsKey > previousNlsKey, \ 188 | f"NLS key '{previousNlsKey}' should come after NLS key '{nlsKey}', but comes before" 189 | 190 | if childPath.name == "messages.nls.json": 191 | for nlsKey in usedNlsKeys: 192 | assert nlsKey in messagesNlsJson, f"NLS key '{nlsKey}' is used, but undefined" 193 | 194 | 195 | 196 | def getUsedNlsKeysFromTypeScript() -> List[str]: 197 | usedNlsKeys = [] 198 | 199 | for rootPathString, dirNames, fileNames in os.walk(common.repoDirPath.joinpath("src")): 200 | rootPath = pathlib.Path(rootPathString) 201 | dirNames.sort() 202 | fileNames.sort() 203 | 204 | for fileName in fileNames: 205 | filePath = rootPath.joinpath(fileName) 206 | with open(filePath, "r") as f: typeScript = f.read() 207 | usedNlsKeys.extend(re.findall("i18n\('(.*?)'", typeScript)) 208 | 209 | return sorted(list(set(usedNlsKeys))) 210 | 211 | 212 | 213 | def main() -> None: 214 | validatePackageJsonWithSchema() 215 | validatePackageJsonWalkthroughsWithSchema() 216 | validatePackageJsonConfigurationWithSchema() 217 | validatePackageJsonConfigurationWithCustomConstraints() 218 | validatePackageNlsJson() 219 | validateMessagesNlsJson() 220 | 221 | 222 | 223 | if __name__ == "__main__": 224 | main() 225 | -------------------------------------------------------------------------------- /src/BugReporter.ts: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2019-2021 Julian Valentin, LTeX Development Community 2 | * 3 | * This Source Code Form is subject to the terms of the Mozilla Public 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this 5 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 | */ 7 | 8 | // #if TARGET == 'vscode' 9 | import * as Code from 'vscode'; 10 | // #elseif TARGET == 'coc.nvim' 11 | // import clipboard from 'clipboardy'; 12 | // import * as Code from 'coc.nvim'; 13 | // #endif 14 | import * as Fs from 'fs'; 15 | import * as Os from 'os'; 16 | import * as Path from 'path'; 17 | import * as Url from 'url'; 18 | 19 | import DependencyManager from './DependencyManager'; 20 | import {i18n} from './I18n'; 21 | import Logger from './Logger'; 22 | import StatusPrinter from './StatusPrinter'; 23 | 24 | export default class BugReporter { 25 | private _context: Code.ExtensionContext; 26 | private _dependencyManager: DependencyManager; 27 | private _statusPrinter: StatusPrinter; 28 | 29 | private static readonly _maxNumberOfDocumentLines: number = 200; 30 | private static readonly _maxNumberOfConfigLines: number = 1000; 31 | private static readonly _maxNumberOfServerLogLines: number = 100; 32 | private static readonly _maxNumberOfClientLogLines: number = 1000; 33 | private static readonly _bugReportUrl: string = 'https://github.com/neo-ltex/vscode-ltex/' 34 | + 'issues/new?assignees=&labels=1-bug%20%F0%9F%90%9B%2C+2-unconfirmed&' 35 | + 'template=bug-report.md&title=&body='; 36 | 37 | public constructor(context: Code.ExtensionContext, dependencyManager: DependencyManager, 38 | statusInformationPrinter: StatusPrinter) { 39 | this._context = context; 40 | this._dependencyManager = dependencyManager; 41 | this._statusPrinter = statusInformationPrinter; 42 | } 43 | 44 | private async createReport(): Promise { 45 | this._statusPrinter.print(); 46 | 47 | const templatePath: string = Path.resolve( 48 | this._context.extensionPath, '.github', 'ISSUE_TEMPLATE', 'bug-report.md'); 49 | let bugReport: string = Fs.readFileSync(templatePath, {encoding: 'utf-8'}); 50 | let pos: number; 51 | 52 | pos = bugReport.indexOf('---'); 53 | 54 | if (pos > -1) { 55 | pos = bugReport.indexOf('---', pos + 3); 56 | 57 | if (pos > -1) { 58 | pos = bugReport.indexOf('**', pos + 3); 59 | if (pos > -1) bugReport = bugReport.substring(pos); 60 | } 61 | } 62 | 63 | const document: Code.TextDocument | undefined = 64 | // #if TARGET == 'vscode' 65 | Code.window.activeTextEditor?.document; 66 | // #elseif TARGET == 'coc.nvim' 67 | // (await Code.workspace.document).textDocument; 68 | // #endif 69 | 70 | if (document != null) { 71 | let codeLanguage: string; 72 | 73 | switch (document.languageId) { 74 | case 'bibtex': 75 | case 'latex': 76 | case 'markdown': { 77 | codeLanguage = document.languageId; 78 | break; 79 | } 80 | default: { 81 | codeLanguage = 'plaintext'; 82 | break; 83 | } 84 | } 85 | 86 | pos = bugReport.indexOf('REPLACE_THIS_WITH_SAMPLE_DOCUMENT'); 87 | 88 | if (pos > -1) { 89 | pos = bugReport.lastIndexOf('```', pos); 90 | 91 | if (pos > -1) { 92 | bugReport = bugReport.substring(0, pos + 3) + codeLanguage + bugReport.substring(pos + 3); 93 | } 94 | } 95 | 96 | const documentText: string = BugReporter.truncateStringAtEnd( 97 | document.getText(), BugReporter._maxNumberOfDocumentLines); 98 | bugReport = bugReport.replace('REPLACE_THIS_WITH_SAMPLE_DOCUMENT', documentText); 99 | } 100 | 101 | const config: any = JSON.parse(JSON.stringify(Code.workspace.getConfiguration('ltex'))); 102 | let configJson: string = JSON.stringify(config, null, 2); 103 | configJson = BugReporter.truncateStringAtEnd(configJson, BugReporter._maxNumberOfConfigLines); 104 | bugReport = bugReport.replace('REPLACE_THIS_WITH_LTEX_CONFIGURATION', configJson); 105 | 106 | const serverLog: string = BugReporter.truncateStringAtStart( 107 | Logger.serverOutputChannel.content, BugReporter._maxNumberOfServerLogLines); 108 | bugReport = bugReport.replace('REPLACE_THIS_WITH_LTEX_LANGUAGE_SERVER_LOG', serverLog); 109 | 110 | let clientLog: string = Logger.clientOutputChannel.content; 111 | clientLog = BugReporter.truncateStringAtStart( 112 | clientLog, BugReporter._maxNumberOfClientLogLines); 113 | bugReport = bugReport.replace('REPLACE_THIS_WITH_LTEX_LANGUAGE_CLIENT_LOG', clientLog); 114 | 115 | const platform: string = `${Os.type} (${Os.platform}), ${Os.arch}, ${Os.release}`; 116 | bugReport = bugReport.replace(/^- Operating system: .*$/m, `- Operating system: ${platform}`); 117 | 118 | const vscodeReplacement: string = 119 | // #if TARGET == 'vscode' 120 | `- VS Code: ${Code.version}`; 121 | // #elseif TARGET == 'coc.nvim' 122 | // '- coc.nvim'; 123 | // #endif 124 | bugReport = bugReport.replace(/^- VS Code: .*$/m, vscodeReplacement); 125 | 126 | // deprecated: replace with self._context.extension starting with VS Code 1.55.0 127 | const extension: Code.Extension | undefined = 128 | // #if TARGET == 'vscode' 129 | Code.extensions.getExtension('neo-ltex.ltex'); 130 | // #elseif TARGET == 'coc.nvim' 131 | // Code.extensions.all.find( 132 | // (extension: Code.Extension) => extension.id == 'coc-ltex'); 133 | // #endif 134 | 135 | if (extension != null) { 136 | bugReport = bugReport.replace(/^- neo-ltex.ltex: .*$/m, 137 | `- neo-ltex.ltex: ${extension.packageJSON.version}`); 138 | } 139 | 140 | if (this._dependencyManager != null) { 141 | const ltexLsVersion: string | null = this._dependencyManager.ltexLsVersion; 142 | 143 | if (ltexLsVersion != null) { 144 | bugReport = bugReport.replace(/^- ltex-ls: .*$/m, `- ltex-ls: ${ltexLsVersion}`); 145 | } 146 | 147 | const javaVersion: string | null = this._dependencyManager.javaVersion; 148 | 149 | if (javaVersion != null) { 150 | bugReport = bugReport.replace(/^- Java: .*$/m, `- Java: ${javaVersion}`); 151 | } 152 | } 153 | 154 | return Promise.resolve(bugReport); 155 | } 156 | 157 | private static truncateStringAtStart(str: string, maxNumberOfLines: number): string { 158 | const lines: string[] = str.split('\n'); 159 | return ((lines.length > maxNumberOfLines) 160 | ? ('[... truncated]\n' + lines.slice(-maxNumberOfLines).join('\n')) : str); 161 | } 162 | 163 | private static truncateStringAtEnd(str: string, maxNumberOfLines: number): string { 164 | const lines: string[] = str.split('\n'); 165 | return ((lines.length > maxNumberOfLines) 166 | ? (lines.slice(0, maxNumberOfLines).join('\n') + '\n[... truncated]') : str); 167 | } 168 | 169 | public async report(): Promise { 170 | Logger.log(i18n('creatingBugReport')); 171 | const bugReport: string = await this.createReport(); 172 | 173 | Code.window.showInformationMessage(i18n('thanksForReportingBug'), 174 | i18n('setLtexTraceServerToVerbose'), i18n('copyReportAndCreateIssue')).then( 175 | async (selectedItem: string | undefined) => { 176 | if (selectedItem == i18n('setLtexTraceServerToVerbose')) { 177 | const config: Code.WorkspaceConfiguration = Code.workspace.getConfiguration('ltex'); 178 | // #if TARGET == 'vscode' 179 | config.update('trace.server', 'verbose', Code.ConfigurationTarget.Global); 180 | // #elseif TARGET == 'coc.nvim' 181 | // config.update('trace.server', 'verbose'); 182 | // #endif 183 | Code.window.showInformationMessage(i18n('ltexTraceServerSetToVerbose')); 184 | } else if (selectedItem == i18n('copyReportAndCreateIssue')) { 185 | const url: Url.URL = new Url.URL(BugReporter._bugReportUrl); 186 | url.searchParams.set('body', i18n('enterSummaryOfIssueInTitleFieldAndReplaceSentence')); 187 | 188 | // #if TARGET == 'vscode' 189 | Code.env.clipboard.writeText(bugReport); 190 | Code.env.openExternal(Code.Uri.parse(url.toString())); 191 | // #elseif TARGET == 'coc.nvim' 192 | // await clipboard.write(bugReport); 193 | // Code.workspace.openResource(url.toString()); 194 | // #endif 195 | } 196 | }); 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /i18n/messages.nls.json: -------------------------------------------------------------------------------- 1 | { 2 | "appendedNewEntriesToExternalSettingFile": "Appended new entries to external setting file '{0}'.", 3 | "checkingAllDocumentsInWorkspace": "Checking all documents in workspace...", 4 | "checkingDocumentN": "Checking document {0}/{1}... ({2})", 5 | "copyReportAndCreateIssue": "Copy report and create issue", 6 | "couldNotAppendNewEntriesToExternalSettingFile": "Could not append new entries to external setting file '{0}'.", 7 | "couldNotCheckDocument": "Could not check document '{0}': '{1}'", 8 | "couldNotCheckDocumentsAsNoDocumentsWereFound": "Could not check documents as no *.md or *.tex documents were found. Only documents that are saved in one of the opened workspace folders can be checked. [More information...](https://valentjn.github.io/ltex/vscode-ltex/commands.html#ltex-check-all-documents-in-workspace)", 9 | "couldNotCheckDocumentsAsNoFoldersWereOpened": "Could not check documents as no folders are opened in the current workspace. Please open a folder in the workspace first. [More information...](https://valentjn.github.io/ltex/vscode-ltex/commands.html#ltex-check-all-documents-in-workspace)", 10 | "couldNotCreateDirectoryForExternalSettingFile": "Could not create directory '{0}' for external setting file '{1}'.", 11 | "couldNotCreateExternalSettingFile": "Could not create external setting file '{0}'.", 12 | "couldNotDeleteLeavingTemporaryDirectoryOnDisk": "Could not delete '{0}', leaving temporary directory on disk.", 13 | "couldNotDeleteLeavingTemporaryFileOnDisk": "Could not delete '{0}', leaving temporary file on disk.", 14 | "couldNotDownloadOrExtractLtexLs": "Could not download or extract ltex-ls.", 15 | "couldNotFindDirectoryAfterExtractingArchive": "Could not find a directory after extracting the archive.", 16 | "couldNotFindVersionOfLtexLsIn": "Could not find a version of ltex-ls in '{0}'.", 17 | "couldNotFinishTask": "Could not finish task, task stack already empty.", 18 | "couldNotGetLtexLsExecutable": "Could not get ltex-ls executable, has to be installed first.", 19 | "couldNotGetPathNameFromUrl": "Could not get path name from URL '{0}'.", 20 | "couldNotGetVscodeLtexVersion": "Could not get vscode-ltex version.", 21 | "couldNotInstallLtexLs": "Could not install ltex-ls, please see the output panel 'LTeX Language Client' for details.", 22 | "couldNotParseTokenInProgressNotification": "Could not parse token '{0}' in progress notification parameters '{1}', ignoring.", 23 | "couldNotReadExternalSettingFile": "Could not read external setting file '{0}'.", 24 | "couldNotRunLtexLs": "Could not run ltex-ls with Java, please see the output panel 'LTeX Language Client' for details.", 25 | "couldNotSetConfiguration": "Could not set configuration '{0}'.", 26 | "couldNotStartLanguageClient": "Could not start the language client!", 27 | "couldNotVerifyDownloadedFile": "Could not verify the downloaded file '{0}' due to a mismatch of hash digests: expected '{1}', actual '{2}'. Maybe there was a transmission error during the download, or the hard-coded hash digests have not been updated.", 28 | "cpuDuration": "- CPU duration: {0}", 29 | "cpuUsage": "- CPU usage: {0}", 30 | "createdDirectoryForExternalSettingFile": "Created directory '{0}' for external setting file '{1}' as it didn't exist.", 31 | "createdExternalSettingFile": "Created external setting file '{0}' as it didn't exist.", 32 | "createIssue": "Create issue", 33 | "creating": "Creating '{0}'...", 34 | "creatingBugReport": "Creating bug report...", 35 | "currentlyWatchedExternalSettingFiles": "Currently watched external setting files:", 36 | "currentlyWatchedExternalSettingFilesNa": "Currently watched external setting files: n/a", 37 | "deleting": "Deleting '{0}'...", 38 | "didNotMoveAsTargetAlreadyExists": "Did not move '{0}' to '{1}', as target already exists.", 39 | "documentUriBeingChecked": "- Document URI being checked: {0}", 40 | "downloading": "Downloading {0}...", 41 | "downloadingAndExtractingLtexLs": "Downloading and extracting ltex-ls", 42 | "downloadingFromTo": "Downloading {0} from '{1}' to '{2}'...", 43 | "downloadOrExtractionOfLtexLsFailed": "The download or extraction of ltex-ls failed!", 44 | "enterSummaryOfIssueInTitleFieldAndReplaceSentence": "Enter a summary of the issue in the title field above and replace this sentence with the bug report from your clipboard.", 45 | "extensionDirPath": "- Path to extension directory: {0}", 46 | "externalSettingFile": "- {0}", 47 | "externalSettingFileChangedReReading": "External setting file '{0}' changed, re-reading.", 48 | "extracting": "Extracting {0}...", 49 | "extractingTo": "Extracting '{0}' to '{1}'...", 50 | "false": "false", 51 | "findingAllDocumentsInWorkspace": "Finding all documents in workspace...", 52 | "foundExtractedDirectory": "Found extracted directory '{0}'.", 53 | "foundMultipleDirectoriesAfterExtraction": "Found multiple directories after extraction: '{0}' and '{1}', using the former.", 54 | "initiatingDownloadOfLtexLs": "Initiating download of ltex-ls...", 55 | "invalidValueForConfigurationTarget": "Invalid value '{0}' for configurationTarget.", 56 | "isBusyChecking": "- Is busy checking: {0}", 57 | "ltexIsChecking": "LTeX is checking...", 58 | "ltexLanguageServer": "LTeX Language Server", 59 | "ltexLsDidNotPrintExpectVersionInformation": "ltex-ls did not print expected version information to stdout.", 60 | "ltexLsFoundIn": "ltex-ls found in '{0}'.", 61 | "ltexLsInfo": "Info about ltex-ls:", 62 | "ltexLsTerminatedDueToSignal": "ltex-ls terminated due to signal '{0}'.", 63 | "ltexLsTerminatedWithNonZeroExitCode": "ltex-ls terminated with non-zero exit code {0}.", 64 | "ltexLtexLsPathNotSet": "ltex.ltex-ls.path not set.", 65 | "ltexLtexLsPathSetTo": "ltex.ltex-ls.path set to '{0}'.", 66 | "ltexNotInitialized": "LTeX has not been initialized due to an earlier error.", 67 | "ltexReady": "LTeX ready", 68 | "ltexStatus": "LTeX Status", 69 | "ltexTraceServerSetToVerbose": "ltex.trace.server set to \"verbose\" in your user settings.", 70 | "movingTo": "Moving '{0}' to '{1}'...", 71 | "na": "n/a", 72 | "noEditorOpenToCheckDocument": "No editor open to check a document.", 73 | "offlineInstructions": "Offline instructions", 74 | "processId": "- Process ID: {0}", 75 | "readExternalSettingFile": "Read external setting file '{0}'.", 76 | "receivedRedirectionStatusCodeWithoutLocationHeader": "Received redirection status code {0}, but no location header.", 77 | "redirectedTo": "Redirected to '{0}'...", 78 | "requestFailedWithStatusCode": "Request failed with status code {0}.", 79 | "searchingForDirectory": "Searching for a directory in '{0}'...", 80 | "searchingForLtexLsIn": "Searching for ltex-ls in '{0}'...", 81 | "separationLine": "----------------------------------------", 82 | "setLtexTraceServerToVerbose": "Set ltex.trace.server to \"verbose\"", 83 | "startedWatchingExternalSettingFile": "Started watching external setting file '{0}'.", 84 | "startingLtex": "Starting LTeX...", 85 | "startingLtexLs": "Starting ltex-ls...", 86 | "stderrOfLtexLs": "stderr of ltex-ls:", 87 | "stdoutOfLtexLs": "stdout of ltex-ls:", 88 | "stoppedWatchingExternalSettingFileDueToDeletion": "Stopped watching external setting file '{0}' as it has been deleted.", 89 | "testFailed": "Test failed.", 90 | "testingLtexLs": "Testing ltex-ls...", 91 | "testSuccessful": "Test successful!", 92 | "thanksForReportingBug": "Thanks for helping to improve LTeX. Be sure to [read the instructions on how to report bugs](https://valentjn.github.io/ltex/vscode-ltex/contributing.html#how-to-report-bugs) first. When you create the issue on GitHub, paste the bug report from the clipboard into the description of the issue.", 93 | "thanksForRequestingFeature": "Thanks for helping to improve LTeX. Be sure to [read the instructions on how to request features](https://valentjn.github.io/ltex/vscode-ltex/contributing.html#how-to-request-features) first. If you're ready, request the feature on GitHub.", 94 | "totalMemory": "- Total allocated memory: {0}", 95 | "true": "true", 96 | "tryAgain": "Try again", 97 | "unknownOperationInProgressNotification": "Unknown operation '{0}' in progress notification '{1}', ignoring.", 98 | "usedMemory": "- Used memory: {0}", 99 | "userSettingsDirPath": "- Path to directory with LTeX user settings (global storage directory): {0}", 100 | "usingJavaBundledWithLtexLs": "Using Java bundled with ltex-ls as ltex.java.path is not set.", 101 | "usingJavaFrom": "Using Java from '{0}' (set in ltex.java.path).", 102 | "usingLtexLsFrom": "Using ltex-ls from '{0}'.", 103 | "verifying": "Verifying {0}...", 104 | "version": "- Version: {0}", 105 | "vscodeLtexInfo": "Info about vscode-ltex:", 106 | "wallClockDuration": "- Wall clock duration: {0}", 107 | "workspaceFolderSettingsDirPath": "- Path to directory with LTeX workspace folder settings (for the workspace folder of the currently active text editor): {0}", 108 | "workspaceSettingsDirPath": "- Path to directory with LTeX workspace settings (for the currently opened workspace): {0}", 109 | "youMightWantToTryOfflineInstallation": "You might want to try offline installation.", 110 | "youMightWantToTryOfflineInstallationSee": "You might want to try offline installation, see {0}." 111 | } 112 | -------------------------------------------------------------------------------- /tools/patchForTarget.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # Copyright (C) 2019-2021 Julian Valentin, LTeX Development Community 4 | # 5 | # This Source Code Form is subject to the terms of the Mozilla Public 6 | # License, v. 2.0. If a copy of the MPL was not distributed with this 7 | # file, You can obtain one at https://mozilla.org/MPL/2.0/. 8 | 9 | import argparse 10 | import json 11 | import os 12 | import pathlib 13 | import re 14 | import sys 15 | from typing import Any, Callable, Dict, List, Tuple, Union 16 | 17 | sys.path.append(str(pathlib.Path(__file__).parent)) 18 | import common 19 | 20 | allTargets = ["coc.nvim", "vscode"] 21 | 22 | 23 | 24 | def setInListOrDict(x: Union[List[Any], Dict[str, Any]], key: str, value: str) -> None: 25 | if isinstance(x, list): 26 | if key not in x: x.append(key) 27 | else: 28 | x[key] = value 29 | 30 | def deleteFromListOrDict(x: Union[List[Any], Dict[str, Any]], key: str) -> None: 31 | if isinstance(x, list): 32 | if key in x: x.remove(key) 33 | else: 34 | if key in x: del x[key] 35 | 36 | def sortDict(d: Dict[str, Any]) -> None: 37 | key: Callable[[Tuple[str, Any]], str] = lambda x: x[0] 38 | items = sorted(list(d.items()), key=key) 39 | d.clear() 40 | d.update(items) 41 | 42 | def moveFromDictToDict(sourceDict: Dict[str, Any], targetDict: Dict[str, Any], 43 | keys: List[str]) -> List[str]: 44 | for dependency in keys: 45 | targetDict[dependency] = sourceDict[dependency] 46 | deleteFromListOrDict(sourceDict, dependency) 47 | 48 | return keys 49 | 50 | 51 | 52 | def patchPackageJson(newTarget: str) -> str: 53 | packageJsonFilePath = common.repoDirPath.joinpath("package.json") 54 | print(f"Patching '{packageJsonFilePath}'...") 55 | with open(packageJsonFilePath, "r") as f: packageJson: Dict[str, Any] = json.load(f) 56 | 57 | ltexAdditionalInformation: Dict[str, Any] = packageJson["ltexAdditionalInformation"] 58 | oldTarget: str = ltexAdditionalInformation["currentTarget"] 59 | if oldTarget == newTarget: return oldTarget 60 | ltexAdditionalInformation["currentTarget"] = newTarget 61 | 62 | packageJson.setdefault("devDependencies", {}) 63 | packageJson.setdefault("dependencies", {}) 64 | 65 | for target in allTargets: 66 | if target == newTarget: continue 67 | ltexAdditionalInformation["targetChanges"].setdefault(target, {}) 68 | 69 | movedDevDependencies = moveFromDictToDict( 70 | packageJson["devDependencies"], packageJson["dependencies"], 71 | ltexAdditionalInformation["targetChanges"][newTarget].get( 72 | "moveFromDevDependenciesToDependencies", [])) 73 | 74 | for (changeKey, changeValue) in ltexAdditionalInformation["targetChanges"][newTarget].items(): 75 | if changeKey in ["moveFromDependenciesToDevDependencies", 76 | "moveFromDevDependenciesToDependencies"]: 77 | continue 78 | 79 | for target in allTargets: 80 | if target == newTarget: continue 81 | ltexAdditionalInformation["targetChanges"][target].setdefault(changeKey, {}) 82 | 83 | if isinstance(changeValue, str): 84 | for target in allTargets: 85 | if target == newTarget: continue 86 | ltexAdditionalInformation["targetChanges"][target][changeKey] = packageJson[changeKey] 87 | 88 | packageJson[changeKey] = changeValue 89 | elif isinstance(changeValue, dict): 90 | for key, value in changeValue.items(): 91 | for target in allTargets: 92 | if target == newTarget: continue 93 | targetChanges = ltexAdditionalInformation["targetChanges"][target][changeKey] 94 | 95 | if key not in targetChanges: 96 | if isinstance(packageJson[changeKey], list): 97 | targetChanges[key] = ("+" if key in packageJson[changeKey] else "-") 98 | else: 99 | targetChanges[key] = packageJson[changeKey].get(key, "-") 100 | 101 | if value == targetChanges[key]: deleteFromListOrDict(targetChanges, key) 102 | 103 | if value == "-": 104 | deleteFromListOrDict(packageJson[changeKey], key) 105 | else: 106 | setInListOrDict(packageJson[changeKey], key, value) 107 | else: 108 | raise RuntimeError(f"Unsupported value type '{type(changeValue)}'") 109 | 110 | movedDependencies = moveFromDictToDict( 111 | packageJson["dependencies"], packageJson["devDependencies"], 112 | ltexAdditionalInformation["targetChanges"][newTarget].get( 113 | "moveFromDependenciesToDevDependencies", [])) 114 | 115 | for devDependency in list(movedDevDependencies): 116 | if devDependency not in packageJson["dependencies"]: movedDevDependencies.remove(devDependency) 117 | 118 | for dependency in list(movedDependencies): 119 | if dependency not in packageJson["devDependencies"]: movedDependencies.remove(dependency) 120 | 121 | for target in allTargets: 122 | if target == newTarget: continue 123 | 124 | if len(movedDevDependencies) > 0: 125 | ltexAdditionalInformation["targetChanges"][target] \ 126 | ["moveFromDependenciesToDevDependencies"] = movedDevDependencies 127 | 128 | if len(movedDependencies) > 0: 129 | ltexAdditionalInformation["targetChanges"][target] \ 130 | ["moveFromDevDependenciesToDependencies"] = movedDependencies 131 | 132 | sortDict(packageJson["devDependencies"]) 133 | sortDict(packageJson["dependencies"]) 134 | if len(packageJson["devDependencies"]) == 0: deleteFromListOrDict(packageJson, "devDependencies") 135 | if len(packageJson["dependencies"]) == 0: deleteFromListOrDict(packageJson, "dependencies") 136 | 137 | deleteFromListOrDict(ltexAdditionalInformation["targetChanges"], newTarget) 138 | 139 | deleteFromListOrDict(packageJson, "ltexAdditionalInformation") 140 | packageJson["ltexAdditionalInformation"] = ltexAdditionalInformation 141 | 142 | with open(packageJsonFilePath, "w") as f: 143 | json.dump(packageJson, f, indent=2, ensure_ascii=False) 144 | f.write("\n") 145 | 146 | return oldTarget 147 | 148 | 149 | 150 | def patchSourceFiles(sourceDirPath: pathlib.Path, oldTarget: str, newTarget: str) -> None: 151 | assert sourceDirPath.is_dir(), f"'{sourceDirPath}' is not a directory" 152 | 153 | for rootDirPathStr, dirPathStrs, filePathStrs in os.walk(sourceDirPath): 154 | dirPathStrs.sort() 155 | filePathStrs.sort() 156 | 157 | for filePathStr in filePathStrs: 158 | patchSourceFile(pathlib.Path(rootDirPathStr, filePathStr), oldTarget, newTarget) 159 | 160 | 161 | 162 | def patchSourceFile(sourceFilePath: pathlib.Path, oldTarget: str, newTarget: str) -> None: 163 | print(f"Patching '{sourceFilePath}'...") 164 | 165 | with open(sourceFilePath, "r") as f: sourceLines = f.read().split("\n") 166 | 167 | if sourceFilePath.suffix == ".md": 168 | lineCommentStart = r"" 170 | elif sourceFilePath.suffix == ".ts": 171 | lineCommentStart = r"// " 172 | lineCommentEnd = r"" 173 | else: 174 | raise RuntimeError(f"Unknown source file suffix '{sourceFilePath.suffix}'") 175 | 176 | ifElseIfLineRegex = (r"^[ \t]*" + re.escape(lineCommentStart) 177 | + r"#(?:else)?if TARGET == '(.*?)'" + re.escape(lineCommentEnd) + r"$") 178 | endIfLineRegex = (r"^[ \t]*" + re.escape(lineCommentStart) + r"#endif" 179 | + re.escape(lineCommentEnd) + r"$") 180 | commentLineRegex = ("^([ \t]*)(?:" + re.escape(lineCommentStart) + r"(.*)" 181 | + re.escape(lineCommentEnd) + r"|" + re.escape(lineCommentStart.rstrip()) + ")$") 182 | 183 | blockTarget = None 184 | newSourceLines = [] 185 | 186 | for sourceLine in sourceLines: 187 | newSourceLine = sourceLine 188 | 189 | if ((regexMatch := re.match(ifElseIfLineRegex, sourceLine)) is not None): 190 | blockTarget = regexMatch.group(1) 191 | elif re.match(endIfLineRegex, sourceLine) is not None: 192 | blockTarget = None 193 | elif len(sourceLine) > 0: 194 | if oldTarget != blockTarget == newTarget: 195 | regexMatch = re.match(commentLineRegex, sourceLine) 196 | assert regexMatch is not None 197 | commentedSourceLine = regexMatch.group(2) 198 | newSourceLine = (regexMatch.group(1) + commentedSourceLine 199 | if commentedSourceLine is not None else "") 200 | elif oldTarget == blockTarget != newTarget: 201 | regexMatch = re.match("^([ \t]*)(.*)$", sourceLine) 202 | assert regexMatch is not None 203 | newSourceLine = (regexMatch.group(1) + lineCommentStart + regexMatch.group(2) 204 | + lineCommentEnd).rstrip() 205 | 206 | newSourceLines.append(newSourceLine) 207 | 208 | with open(sourceFilePath, "w") as f: f.write("\n".join(newSourceLines)) 209 | 210 | 211 | 212 | def patchForTarget(target: str) -> None: 213 | oldTarget = patchPackageJson(target) 214 | patchSourceFile(common.repoDirPath.joinpath("README.md"), oldTarget, target) 215 | patchSourceFiles(common.repoDirPath.joinpath("src"), oldTarget, target) 216 | patchSourceFiles(common.repoDirPath.joinpath("test"), oldTarget, target) 217 | 218 | 219 | 220 | def main() -> None: 221 | parser = argparse.ArgumentParser( 222 | description="Patch package.json and source files for specific build target") 223 | parser.add_argument("--target", choices=allTargets, default="coc.nvim", 224 | help="Build target to patch files for") 225 | args = parser.parse_args() 226 | patchForTarget(args.target) 227 | 228 | 229 | 230 | if __name__ == "__main__": 231 | main() 232 | -------------------------------------------------------------------------------- /tools/convertChangelog.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # Copyright (C) 2019-2021 Julian Valentin, LTeX Development Community 4 | # 5 | # This Source Code Form is subject to the terms of the Mozilla Public 6 | # License, v. 2.0. If a copy of the MPL was not distributed with this 7 | # file, You can obtain one at https://mozilla.org/MPL/2.0/. 8 | 9 | import argparse 10 | import datetime 11 | import pathlib 12 | import re 13 | import sys 14 | from typing import Optional, Tuple 15 | import xml.etree.ElementTree as et 16 | import xml.dom.minidom 17 | 18 | sys.path.append(str(pathlib.Path(__file__).parent)) 19 | import common 20 | 21 | 22 | 23 | def parseIssue(issue: str) -> Tuple[str, str, int, str]: 24 | issueMatch = re.search(r"(?:(.*?)/)?(.*?)?#([0-9]+)", issue) 25 | assert issueMatch is not None, issue 26 | issueOrganization = (issueMatch.group(1) if issueMatch.group(1) is not None else 27 | common.organization) 28 | issueRepository = (issueMatch.group(2) if issueMatch.group(2) != "" else common.repository) 29 | issueNumber = int(issueMatch.group(3)) 30 | 31 | if issueOrganization == common.organization: 32 | if issueRepository == common.repository: 33 | normalizedIssue = f"#{issueNumber}" 34 | else: 35 | normalizedIssue = f"{issueRepository}#{issueNumber}" 36 | else: 37 | normalizedIssue = f"{issueOrganization}/{issueRepository}#{issueNumber}" 38 | 39 | return issueOrganization, issueRepository, issueNumber, normalizedIssue 40 | 41 | 42 | 43 | def replaceUnicodeWithXmlEntities(string: str) -> str: 44 | return re.sub(r"[\u007f-\U0010ffff]", (lambda x: f"&#x{ord(x.group()):04x};"), string) 45 | 46 | 47 | 48 | def convertChangelogFromXmlToMarkdown(xmlFilePath: pathlib.Path, 49 | version: Optional[str] = None) -> str: 50 | document = et.parse(xmlFilePath).getroot() 51 | 52 | if version is None: 53 | markdown = """ 60 | 61 | """ 62 | 63 | title = document.findtext("./{http://maven.apache.org/changes/1.0.0}properties" 64 | "/{http://maven.apache.org/changes/1.0.0}title") 65 | markdown += f"# {title}\n" 66 | markdown += """ 67 | ## Upcoming Fundamental Changes 68 | 69 | - New versions of LTEX released on or after January 14, 2022, will require VS Code 1.61.0 or later 70 | """ 71 | else: 72 | markdown = "" 73 | 74 | releases = document.findall("./{http://maven.apache.org/changes/1.0.0}body" 75 | "/{http://maven.apache.org/changes/1.0.0}release") 76 | 77 | for release in releases: 78 | curVersion = release.attrib["version"] 79 | if version == "latest": version = curVersion 80 | if (version is not None) and (curVersion != version): continue 81 | 82 | description = release.attrib.get("description", "") 83 | if len(description) > 0: description = f" \u2014 \u201c{description}\u201d" 84 | dateStr = release.attrib["date"] 85 | dateStr = ( 86 | datetime.datetime.strptime(dateStr, "%Y-%m-%d").strftime("%B %d, %Y").replace(" 0", " ") 87 | if dateStr != "upcoming" else dateStr) 88 | if version is None: markdown += f"\n## {curVersion}{description} ({dateStr})\n\n" 89 | 90 | for action in release.findall("./{http://maven.apache.org/changes/1.0.0}action"): 91 | type_ = action.attrib["type"] 92 | typeEmoji = { 93 | "add" : "\u2728", 94 | "fix": "\U0001f41b", 95 | "remove" : "\U0001f5d1", 96 | "update" : "\U0001f527", 97 | }[type_] 98 | typeStr = {"add" : "New", "fix": "Bug fix", "remove" : "Removal", "update" : "Change"}[type_] 99 | 100 | additionalInfo = "" 101 | 102 | if "issue" in action.attrib: 103 | for issue in action.attrib["issue"].split(","): 104 | issueOrganization, issueRepository, issueNumber, issue = parseIssue(issue) 105 | additionalInfo += (" \u2014 " if additionalInfo == "" else ", ") 106 | additionalInfo += (f"[{issue}](https://github.com/{issueOrganization}/" 107 | f"{issueRepository}/issues/{issueNumber})") 108 | 109 | if "due-to" in action.attrib: 110 | for author in action.attrib["due-to"].split(","): 111 | additionalInfo += (" \u2014 " if additionalInfo == "" else ", ") 112 | userMatch = re.search(r"@([^)]+)", author) 113 | if userMatch is not None: author = f"[{author}](https://github.com/{userMatch.group(1)})" 114 | additionalInfo += author 115 | 116 | change = action.text 117 | assert change is not None 118 | change = change.strip() 119 | change = change.replace("LaTeX", "LATEX") 120 | change = re.sub(r"(?EX", change) 121 | 122 | markdown += f"- {typeEmoji} *{typeStr}:* {change}{additionalInfo}\n" 123 | 124 | markdown = replaceUnicodeWithXmlEntities(markdown) 125 | return markdown 126 | 127 | 128 | 129 | def convertReleaseFromMarkdownToXml(body: et.Element, version: str, name: str, dateStr: str, 130 | changes: str) -> et.Element: 131 | attributes = {"version" : version} 132 | if len(name) > 0: attributes["description"] = name 133 | attributes["date"] = (datetime.datetime.strptime(dateStr, "%B %d, %Y").strftime("%Y-%m-%d") 134 | if dateStr != "upcoming" else dateStr) 135 | release = et.SubElement(body, "release", attributes) 136 | 137 | changes = changes.strip() 138 | 139 | for change in changes.split("\n"): 140 | change = re.sub(r"^- ", "", change).strip() 141 | attributes = {} 142 | 143 | if change.startswith("Remove "): 144 | attributes["type"] = "remove" 145 | elif (change.startswith("Add ") or ("support" in change.lower()) 146 | or (change == "Initial release")): 147 | attributes["type"] = "add" 148 | elif (change.startswith("Fix ") 149 | or any(x in change.lower() for x in ["error", "warning", "prevent"])): 150 | attributes["type"] = "fix" 151 | else: 152 | attributes["type"] = "update" 153 | 154 | issues = [] 155 | authors = [] 156 | issueFound = True 157 | 158 | while issueFound: 159 | issueMatch1 = re.search( 160 | r" \((?:fixes|fixes part of|see) \[([^\]]*?#[0-9]+)\]\(.*?\)\)", change) 161 | issueMatch2 = re.search( 162 | r"(?:[;,]| and) (?:fixes |see )?\[([^\]]*?#[0-9]+)\]\(.*?\)", change) 163 | issueMatch3 = re.search( 164 | r" \((?:\[PR |PR \[)(.*?#[0-9]+)\]\([^\]]*?\) by \[([^\]]*?)\]\(.*?\)\)", change) 165 | issueMatch4 = re.search( 166 | r"(?:[;,]| and) \[PR (.*?#[0-9]+)\]\([^\]]*?\) by \[([^\]]*?)\]\(.*?\)", change) 167 | issueFound = False 168 | 169 | for issueMatch in [issueMatch1, issueMatch2, issueMatch3, issueMatch4]: 170 | if issueMatch is not None: 171 | issueFound = True 172 | _, _, _, issue = parseIssue(issueMatch.group(1)) 173 | issues.append(issue) 174 | if len(issueMatch.groups()) > 1: authors.append(issueMatch.group(2)) 175 | change = change[:issueMatch.start()] + change[issueMatch.end():] 176 | 177 | if len(issues) > 0: attributes["issue"] = ",".join(sorted(issues)) 178 | if len(authors) > 0: attributes["due-to"] = ",".join(sorted(authors)) 179 | 180 | change = change.replace("TEX", "TeX").replace("LA", "La") 181 | 182 | assert f"github.com/{common.organization}" not in change, change 183 | assert " -" not in change, change 184 | 185 | action = et.SubElement(release, "action", attributes) 186 | action.text = f"\n {change}\n " 187 | 188 | return release 189 | 190 | 191 | 192 | def convertChangelogFromMarkdownToXml(markdownFilePath: pathlib.Path, 193 | version: Optional[str] = None) -> str: 194 | with open(markdownFilePath, "r") as f: changelog = f.read() 195 | 196 | document = et.Element("document", { 197 | "xmlns" : "http://maven.apache.org/changes/1.0.0", 198 | "xmlns:xsi" : "http://www.w3.org/2001/XMLSchema-instance", 199 | "xsi:schemaLocation" : "http://maven.apache.org/changes/1.0.0 " 200 | "https://maven.apache.org/xsd/changes-1.0.0.xsd" 201 | }) 202 | 203 | properties = et.SubElement(document, "properties") 204 | title = et.SubElement(properties, "title") 205 | title.text = "Changelog" 206 | author = et.SubElement(properties, "author") 207 | author.text = "Julian Valentin, LTeX Development Community" 208 | 209 | body = et.SubElement(document, "body") 210 | 211 | regexMatches = re.findall( 212 | r"\n## ([^ ]+)(?: \u2014 \u201c(.*?)\u201d)? \((.*)\)\n\n((?:.|\n)+?)(?=$|\n## )", changelog) 213 | for regexMatch in regexMatches: 214 | curVersion = regexMatch[0] 215 | if version == "latest": version = curVersion 216 | if (version is not None) and (curVersion != version): continue 217 | release = convertReleaseFromMarkdownToXml(body, *regexMatch) 218 | 219 | if version is not None: 220 | document = release 221 | break 222 | 223 | xmlStr = et.tostring(document, encoding="unicode", xml_declaration=True) 224 | xmlStr = xml.dom.minidom.parseString(xmlStr).toprettyxml(indent=" ") 225 | xmlStr = re.sub(r"^<\?xml version=\"1.0\" \?>\n", 226 | (""" 227 | 234 | """ if version is None else ""), xmlStr) 235 | xmlStr = replaceUnicodeWithXmlEntities(xmlStr) 236 | 237 | return xmlStr 238 | 239 | 240 | 241 | def main() -> None: 242 | parser = argparse.ArgumentParser( 243 | description="Convert changelog from XML to Markdown and vice-versa.") 244 | xmlFileArgument = parser.add_argument("--xml-file", type=pathlib.Path, metavar="PATH", 245 | help="XML file to convert to Markdown") 246 | parser.add_argument("--markdown-file", type=pathlib.Path, metavar="PATH", 247 | help="Markdown file to convert to XML") 248 | parser.add_argument("--output-file", type=pathlib.Path, default=pathlib.Path("-"), metavar="PATH", 249 | help="Output file; '-' is standard output (default)") 250 | parser.add_argument("--version", 251 | help="Version to convert; 'latest' is permitted; all versions are converted if omitted") 252 | arguments = parser.parse_args() 253 | 254 | if arguments.xml_file is not None: 255 | output = convertChangelogFromXmlToMarkdown(arguments.xml_file, arguments.version) 256 | elif arguments.markdown_file is not None: 257 | output = convertChangelogFromMarkdownToXml(arguments.markdown_file, arguments.version) 258 | else: 259 | raise argparse.ArgumentError(xmlFileArgument, 260 | "One of --xml-file or --markdown-file is required") 261 | 262 | if str(arguments.output_file) == "-": 263 | print(output, end="") 264 | else: 265 | with open(arguments.output_file, "w") as f: f.write(output) 266 | 267 | 268 | 269 | if __name__ == "__main__": 270 | main() 271 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2019-2021 Julian Valentin, LTeX Development Community 2 | # 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 | 7 | name: "CI" 8 | 9 | on: 10 | push: 11 | branches: 12 | - "develop" 13 | tags: 14 | - "*" 15 | pull_request: 16 | branches: 17 | - "develop" 18 | workflow_dispatch: 19 | 20 | jobs: 21 | buildForVsCode: 22 | name: "CI - Build for VS Code Job" 23 | runs-on: "${{ matrix.os }}" 24 | 25 | strategy: 26 | matrix: 27 | os: 28 | - "ubuntu-20.04" 29 | - "macos-11.0" 30 | - "windows-2019" 31 | 32 | steps: 33 | - name: "Checkout Repository" 34 | uses: "actions/checkout@v2" 35 | 36 | - name: "Set up Node.js" 37 | uses: "actions/setup-node@v1" 38 | with: 39 | node-version: "16.11.0" 40 | 41 | - name: "Install Node.js Dependencies" 42 | run: "npm install && npm install -g vsce@1.100.1" 43 | 44 | - name: "Set up Python" 45 | uses: "actions/setup-python@v2" 46 | with: 47 | python-version: "3.9.0" 48 | 49 | - name: "Install Python Dependencies" 50 | run: "python -u -m pip install --upgrade pip && pip install semver==2.13.0" 51 | 52 | - name: "Start X Virtual Frame Buffer" 53 | if: "${{ startsWith(matrix.os, 'ubuntu-') }}" 54 | run: "echo \"DISPLAY=:99.0\" >> $GITHUB_ENV && Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 &" 55 | 56 | - name: "Build Package with vsce" 57 | run: "vsce package" 58 | 59 | - name: "Run ESLint" 60 | run: "npm run lint" 61 | 62 | - name: "Run Mocha Tests (Linux)" 63 | if: "${{ startsWith(matrix.os, 'ubuntu-') }}" 64 | env: 65 | LTEX_GITHUB_OAUTH_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 66 | # to suppress "async hook stack has become corrupted" errors 67 | # (https://github.com/microsoft/vscode/issues/85601) 68 | NODE_OPTIONS: "--no-force-async-hooks-checks" 69 | run: "npm run test" 70 | 71 | - name: "Run Mocha Tests (Windows)" 72 | if: "${{ startsWith(matrix.os, 'windows-') }}" 73 | env: 74 | LTEX_GITHUB_OAUTH_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 75 | # to suppress "async hook stack has become corrupted" errors 76 | # (https://github.com/microsoft/vscode/issues/85601) 77 | NODE_OPTIONS: "--no-force-async-hooks-checks" 78 | # otherwise, git clean fails to remove lib/ with "Invalid argument" errors 79 | run: "npm run test -- --fast" 80 | 81 | # buildForCocNvim: 82 | # name: "CI - Build for coc.nvim Job" 83 | # runs-on: "ubuntu-20.04" 84 | 85 | # steps: 86 | # - name: "Checkout Repository" 87 | # uses: "actions/checkout@v2" 88 | 89 | # - name: "Set up Node.js" 90 | # uses: "actions/setup-node@v1" 91 | # with: 92 | # node-version: "16.11.0" 93 | 94 | # - name: "Set up Python" 95 | # uses: "actions/setup-python@v2" 96 | # with: 97 | # python-version: "3.9.0" 98 | 99 | # - name: "Install Python Dependencies" 100 | # run: "python -u -m pip install --upgrade pip && pip install semver==2.13.0" 101 | 102 | # - name: "Patch for coc.nvim Target" 103 | # run: "python -u tools/patchForTarget.py --target coc.nvim" 104 | 105 | # - name: "Install Node.js Dependencies" 106 | # run: "npm install" 107 | 108 | # - name: "Compile and Package" 109 | # run: "npm run coc.nvim:prepublish" 110 | 111 | # - name: "Run ESLint" 112 | # run: "npm run lint" 113 | 114 | validate: 115 | name: "CI - Validate Job" 116 | runs-on: "ubuntu-20.04" 117 | 118 | steps: 119 | - name: "Checkout Repository" 120 | uses: "actions/checkout@v2" 121 | 122 | - name: "Set up Python" 123 | uses: "actions/setup-python@v2" 124 | with: 125 | python-version: "3.9.0" 126 | 127 | - name: "Install Python Dependencies" 128 | run: "python -u -m pip install --upgrade pip && pip install jsonschema==3.2.0 xmlschema==1.6.4" 129 | 130 | - name: "Validate changelog.xml" 131 | run: "python -u -c 'import xmlschema; xmlschema.XMLSchema(\"schemas/changes-1.0.0.xsd\").validate(\"changelog.xml\")'" 132 | 133 | # No need to validate JSON files 134 | # even if wants to, should do it locally 135 | # - name: "Validate JSON files" 136 | # run: "python -u tools/validateJsonFiles.py" 137 | 138 | deployForVsCode: 139 | name: "CI - Deploy for VS Code Job" 140 | if: startsWith(github.ref, 'refs/tags/') 141 | needs: 142 | - "buildForVsCode" 143 | - "validate" 144 | runs-on: "ubuntu-20.04" 145 | 146 | steps: 147 | - name: "Checkout Repository" 148 | uses: "actions/checkout@v2" 149 | 150 | - name: "Set up Node.js" 151 | uses: "actions/setup-node@v1" 152 | with: 153 | node-version: "16.11.0" 154 | 155 | - name: "Install Node.js Dependencies" 156 | run: "npm install && npm install -g vsce@1.100.1 ovsx@0.2.1" 157 | 158 | - name: "Set up Python" 159 | uses: "actions/setup-python@v2" 160 | with: 161 | python-version: "3.9.0" 162 | 163 | - name: "Install Python Dependencies" 164 | run: "python -u -m pip install --upgrade pip && pip install semver==2.13.0" 165 | 166 | - name: "Set VSCODE_LTEX_VERSION" 167 | run: "echo \"VSCODE_LTEX_VERSION=$(python -u -c \"import json; print(json.load(open('package.json', 'r'))['version'], end='')\")\" >> $GITHUB_ENV" 168 | 169 | - name: "Check VSCODE_LTEX_VERSION" 170 | run: "if [[ -z \"$VSCODE_LTEX_VERSION\" ]]; then echo 'Error: VSCODE_LTEX_VERSION not set!'; (exit 1); fi; echo \"VSCODE_LTEX_VERSION set to '$VSCODE_LTEX_VERSION'\"" 171 | 172 | - name: "Set VSCODE_LTEX_IS_PRERELEASE" 173 | run: "if [[ -z \"$VSCODE_LTEX_VERSION\" ]]; then echo 'Error: VSCODE_LTEX_VERSION not set!'; (exit 1); fi; echo \"VSCODE_LTEX_IS_PRERELEASE=$(python -u -c \"import semver; print('true' if semver.VersionInfo.parse('$VSCODE_LTEX_VERSION').prerelease is not None else 'false', end='')\")\" >> $GITHUB_ENV" 174 | 175 | - name: "Check VSCODE_LTEX_IS_PRERELEASE" 176 | run: "if [[ -z \"$VSCODE_LTEX_IS_PRERELEASE\" ]]; then echo 'Error: VSCODE_LTEX_IS_PRERELEASE not set!'; (exit 1); fi; echo \"VSCODE_LTEX_IS_PRERELEASE set to '$VSCODE_LTEX_IS_PRERELEASE'\"" 177 | 178 | - name: "Set VSCODE_LTEX_CHANGELOG" 179 | run: "if [ \"$VSCODE_LTEX_IS_PRERELEASE\" = \"false\" ]; then echo \"VSCODE_LTEX_CHANGELOG<> $GITHUB_ENV; python -u tools/convertChangelog.py --xml-file changelog.xml --version latest >> $GITHUB_ENV; echo \"EOF\" >> $GITHUB_ENV; else echo \"VSCODE_LTEX_CHANGELOG=This is a pre-release. Use at your own risk.\" >> $GITHUB_ENV; fi" 180 | 181 | - name: "Build Package with vsce" 182 | run: "vsce package" 183 | 184 | - name: "Build Offline Packages" 185 | env: 186 | LTEX_GITHUB_OAUTH_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 187 | run: "python -u tools/createOfflinePackages.py" 188 | 189 | - name: "Create GitHub Release" 190 | uses: "softprops/action-gh-release@v1" 191 | with: 192 | # token: "${{ secrets.VSCODE_LTEX_CREATE_GITHUB_RELEASE_TOKEN }}" 193 | prerelease: "${{ env.VSCODE_LTEX_IS_PRERELEASE }}" 194 | body: "${{ env.VSCODE_LTEX_CHANGELOG }}" 195 | files: | 196 | ltex-${{ env.VSCODE_LTEX_VERSION }}.vsix 197 | ltex-${{ env.VSCODE_LTEX_VERSION }}-offline-linux-x64.vsix 198 | ltex-${{ env.VSCODE_LTEX_VERSION }}-offline-mac-x64.vsix 199 | ltex-${{ env.VSCODE_LTEX_VERSION }}-offline-windows-x64.vsix 200 | 201 | - run: "npm run convert-changelog" 202 | 203 | - name: "Publish Package on VS Marketplace" 204 | if: "${{ env.VSCODE_LTEX_IS_PRERELEASE == 'false' }}" 205 | run: vsce publish 206 | env: 207 | VSCE_PAT: "${{ secrets.VSCODE_LTEX_VSCE_TOKEN }}" 208 | 209 | - name: "Publish Package on Open VSX" 210 | if: "${{ env.VSCODE_LTEX_IS_PRERELEASE == 'false' }}" 211 | # env: 212 | # VSCODE_LTEX_OVSX_TOKEN: "${{ secrets.VSCODE_LTEX_OVSX_TOKEN }}" 213 | run: "ovsx publish -p ${{ secrets.VSCODE_LTEX_OVSX_TOKEN }}" 214 | 215 | # not ready for coc:nvim deployment 216 | # the package name needs to be changed to `@neo-ltex/coc-ltex` 217 | # But the consumption rate is pretty low, so this is lower priority. 218 | # deployForCocNvim: 219 | # name: "CI - Deploy for coc.nvim Job" 220 | # if: "${{ startsWith(github.ref, 'refs/tags/') }}" 221 | # needs: 222 | # - "buildForVsCode" 223 | # - "buildForCocNvim" 224 | # - "validate" 225 | # runs-on: "ubuntu-20.04" 226 | 227 | # steps: 228 | # - name: "Checkout Repository" 229 | # uses: "actions/checkout@v2" 230 | 231 | # - name: "Set up Node.js" 232 | # uses: "actions/setup-node@v1" 233 | # with: 234 | # node-version: "16.11.0" 235 | # registry-url: "https://registry.npmjs.org" 236 | 237 | # - name: "Set up Python" 238 | # uses: "actions/setup-python@v2" 239 | # with: 240 | # python-version: "3.9.0" 241 | 242 | # - name: "Install Python Dependencies" 243 | # run: "python -u -m pip install --upgrade pip && pip install semver==2.13.0" 244 | 245 | # - name: "Patch for coc.nvim Target" 246 | # run: "python -u tools/patchForTarget.py --target coc.nvim" 247 | 248 | # - name: "Install Node.js Dependencies" 249 | # run: "npm install" 250 | 251 | # - name: "Set VSCODE_LTEX_VERSION" 252 | # run: "echo \"VSCODE_LTEX_VERSION=$(python -u -c \"import json; print(json.load(open('package.json', 'r'))['version'], end='')\")\" >> $GITHUB_ENV" 253 | 254 | # - name: "Check VSCODE_LTEX_VERSION" 255 | # run: "if [[ -z \"$VSCODE_LTEX_VERSION\" ]]; then echo 'Error: VSCODE_LTEX_VERSION not set!'; (exit 1); fi; echo \"VSCODE_LTEX_VERSION set to '$VSCODE_LTEX_VERSION'\"" 256 | 257 | # - name: "Set VSCODE_LTEX_IS_PRERELEASE" 258 | # run: "if [[ -z \"$VSCODE_LTEX_VERSION\" ]]; then echo 'Error: VSCODE_LTEX_VERSION not set!'; (exit 1); fi; echo \"VSCODE_LTEX_IS_PRERELEASE=$(python -u -c \"import semver; print('true' if semver.VersionInfo.parse('$VSCODE_LTEX_VERSION').prerelease is not None else 'false', end='')\")\" >> $GITHUB_ENV" 259 | 260 | # - name: "Check VSCODE_LTEX_IS_PRERELEASE" 261 | # run: "if [[ -z \"$VSCODE_LTEX_IS_PRERELEASE\" ]]; then echo 'Error: VSCODE_LTEX_IS_PRERELEASE not set!'; (exit 1); fi; echo \"VSCODE_LTEX_IS_PRERELEASE set to '$VSCODE_LTEX_IS_PRERELEASE'\"" 262 | 263 | # - name: "Compile and Package" 264 | # run: "npm run coc.nvim:prepublish" 265 | 266 | # - name: "Publish on npm (Dry Run)" 267 | # if: "${{ env.VSCODE_LTEX_IS_PRERELEASE == 'true' }}" 268 | # run: "npm publish --dry-run" 269 | 270 | # - name: "Publish on npm" 271 | # if: "${{ env.VSCODE_LTEX_IS_PRERELEASE == 'false' }}" 272 | # env: 273 | # NODE_AUTH_TOKEN: "${{ secrets.VSCODE_LTEX_NPM_TOKEN }}" 274 | # run: "npm publish" 275 | -------------------------------------------------------------------------------- /schemas/package.schema.json.LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | --------------------------------------------------------------------------------