├── .changeset ├── .markdownlint.json └── config.json ├── .editorconfig ├── .github ├── FUNDING.yml └── workflows │ ├── latest.yml │ └── push.yml ├── .gitignore ├── .markdownlint.json ├── .npmrc ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.txt ├── README.md ├── SECURITY.md ├── assets ├── locales.ts ├── locales │ ├── af │ │ └── translation.json │ ├── am │ │ └── translation.json │ ├── ar │ │ └── translation.json │ ├── be │ │ └── translation.json │ ├── bg │ │ └── translation.json │ ├── bn │ │ └── translation.json │ ├── ca │ │ └── translation.json │ ├── cs │ │ └── translation.json │ ├── da │ │ └── translation.json │ ├── de │ │ └── translation.json │ ├── el │ │ └── translation.json │ ├── en │ │ ├── asset.json │ │ ├── language.json │ │ └── translation.json │ ├── eo │ │ └── translation.json │ ├── es │ │ └── translation.json │ ├── eu │ │ └── translation.json │ ├── fa │ │ └── translation.json │ ├── fi │ │ └── translation.json │ ├── fr │ │ └── translation.json │ ├── gl │ │ └── translation.json │ ├── he │ │ └── translation.json │ ├── hi │ │ └── translation.json │ ├── hu │ │ └── translation.json │ ├── id │ │ └── translation.json │ ├── it │ │ └── translation.json │ ├── ja │ │ └── translation.json │ ├── ko │ │ └── translation.json │ ├── lv │ │ └── translation.json │ ├── ml │ │ └── translation.json │ ├── ms │ │ └── translation.json │ ├── nl │ │ └── translation.json │ ├── no │ │ └── translation.json │ ├── oc │ │ └── translation.json │ ├── pl │ │ └── translation.json │ ├── pt-BR │ │ └── translation.json │ ├── pt │ │ └── translation.json │ ├── ro │ │ └── translation.json │ ├── ru │ │ └── translation.json │ ├── se │ │ └── translation.json │ ├── sk │ │ └── translation.json │ ├── sq │ │ └── translation.json │ ├── sr │ │ └── translation.json │ ├── ta │ │ └── translation.json │ ├── te │ │ └── translation.json │ ├── th │ │ └── translation.json │ ├── tr │ │ └── translation.json │ ├── uk │ │ └── translation.json │ ├── ur │ │ └── translation.json │ ├── zh-Hans │ │ └── translation.json │ └── zh-Hant │ │ └── translation.json └── trailer.png ├── build ├── build.mjs ├── obsidian-install.mjs ├── util.mjs ├── version-post.mjs └── version.mjs ├── eslint.config.mjs ├── manifest-beta.json ├── manifest.json ├── package-lock.json ├── package.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── requirements.txt ├── src ├── @types │ ├── _index.d.ts │ ├── _obsidian-terminal.d.ts │ ├── electron.ts │ ├── i18next.ts │ ├── index.ts │ └── obsidian-terminal.ts ├── documentations.ts ├── get-package-version.py ├── icons.ts ├── import.ts ├── magic.ts ├── main.ts ├── modals.ts ├── patch.ts ├── settings-data.ts ├── settings.ts ├── styles.css ├── terminal │ ├── emulator-addons.ts │ ├── emulator.css │ ├── emulator.ts │ ├── load.ts │ ├── profile-presets.ts │ ├── profile-properties.ts │ ├── pseudoterminal.ts │ ├── spawn.ts │ ├── unix_pseudoterminal.py │ ├── util.ts │ ├── view.css │ ├── view.ts │ └── win32_resizer.py └── util.ts ├── tsconfig.json └── versions.json /.changeset/.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/DavidAnson/markdownlint/main/schema/markdownlint-config-schema.json", 3 | "MD041": false, 4 | "extends": "../.markdownlint.json" 5 | } -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.3.1/schema.json", 3 | "access": "restricted", 4 | "baseBranch": "main", 5 | "changelog": "@changesets/cli/changelog", 6 | "commit": false, 7 | "fixed": [], 8 | "ignore": [], 9 | "linked": [], 10 | "updateInternalDependencies": "patch" 11 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | insert_final_newline = true 8 | indent_style = tab 9 | indent_size = 2 10 | tab_width = 2 11 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # https://docs.github.com/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/displaying-a-sponsor-button-in-your-repository#about-funding-files 2 | community_bridge: 3 | github: 4 | - polyipseity 5 | issuehunt: 6 | ko_fi: 7 | liberapay: 8 | open_collective: 9 | otechie: 10 | patreon: 11 | tidelift: 12 | custom: 13 | - https://buymeacoffee.com/polyipseity 14 | -------------------------------------------------------------------------------- /.github/workflows/latest.yml: -------------------------------------------------------------------------------- 1 | jobs: 2 | release-latest: 3 | permissions: 4 | contents: write 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v3 8 | with: 9 | submodules: recursive 10 | - uses: actions/setup-node@v3 11 | with: 12 | cache: npm 13 | node-version: '*' 14 | - run: npm ci 15 | - run: npm run build 16 | - run: | 17 | npm pack --pack-destination=.github 18 | for file in .github/*.tgz; do mv "$file" "${file%-*}.tgz"; done 19 | - if: ${{github.ref == 'refs/tags/latest'}} 20 | env: 21 | GH_TOKEN: ${{github.token}} 22 | run: | 23 | gh release upload latest .github/*.tgz main.js manifest.json styles.css --clobber 24 | 25 | on: 26 | push: 27 | tags: 28 | - latest 29 | workflow_dispatch: 30 | 31 | permissions: {} 32 | -------------------------------------------------------------------------------- /.github/workflows/push.yml: -------------------------------------------------------------------------------- 1 | jobs: 2 | build: 3 | runs-on: ubuntu-latest 4 | steps: 5 | - uses: actions/checkout@v3 6 | with: 7 | submodules: recursive 8 | - uses: actions/setup-node@v3 9 | with: 10 | cache: npm 11 | node-version: '*' 12 | - run: npm ci 13 | - run: npm run build 14 | 15 | build-pnpm: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v3 19 | with: 20 | submodules: recursive 21 | - uses: pnpm/action-setup@v2 22 | with: 23 | version: '*' 24 | - uses: actions/setup-node@v3 25 | with: 26 | cache: pnpm 27 | node-version: '*' 28 | - run: pnpm install 29 | - run: pnpm build 30 | 31 | on: 32 | pull_request: 33 | branches: 34 | - main 35 | push: 36 | branches: 37 | - main 38 | workflow_dispatch: 39 | 40 | permissions: {} 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # https://github.com/github/gitignore/blob/1d4e709db80b4481888076b1f256a7e87eb84105/Node.gitignore { 2 | # Logs 3 | logs 4 | *.log 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | lerna-debug.log* 9 | .pnpm-debug.log* 10 | 11 | # Diagnostic reports (https://nodejs.org/api/report.html) 12 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 13 | 14 | # Runtime data 15 | pids 16 | *.pid 17 | *.seed 18 | *.pid.lock 19 | 20 | # Directory for instrumented libs generated by jscoverage/JSCover 21 | lib-cov 22 | 23 | # Coverage directory used by tools like istanbul 24 | coverage 25 | *.lcov 26 | 27 | # nyc test coverage 28 | .nyc_output 29 | 30 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 31 | .grunt 32 | 33 | # Bower dependency directory (https://bower.io/) 34 | bower_components 35 | 36 | # node-waf configuration 37 | .lock-wscript 38 | 39 | # Compiled binary addons (https://nodejs.org/api/addons.html) 40 | build/Release 41 | 42 | # Dependency directories 43 | node_modules/ 44 | jspm_packages/ 45 | 46 | # Snowpack dependency directory (https://snowpack.dev/) 47 | web_modules/ 48 | 49 | # TypeScript cache 50 | *.tsbuildinfo 51 | 52 | # Optional npm cache directory 53 | .npm 54 | 55 | # Optional eslint cache 56 | .eslintcache 57 | 58 | # Optional stylelint cache 59 | .stylelintcache 60 | 61 | # Microbundle cache 62 | .rpt2_cache/ 63 | .rts2_cache_cjs/ 64 | .rts2_cache_es/ 65 | .rts2_cache_umd/ 66 | 67 | # Optional REPL history 68 | .node_repl_history 69 | 70 | # Output of 'npm pack' 71 | *.tgz 72 | 73 | # Yarn Integrity file 74 | .yarn-integrity 75 | 76 | # dotenv environment variable files 77 | .env 78 | .env.development.local 79 | .env.test.local 80 | .env.production.local 81 | .env.local 82 | 83 | # parcel-bundler cache (https://parceljs.org/) 84 | .cache 85 | .parcel-cache 86 | 87 | # Next.js build output 88 | .next 89 | out 90 | 91 | # Nuxt.js build / generate output 92 | .nuxt 93 | dist 94 | 95 | # Gatsby files 96 | .cache/ 97 | # Comment in the public line in if your project uses Gatsby and not Next.js 98 | # https://nextjs.org/blog/next-9-1#public-directory-support 99 | # public 100 | 101 | # vuepress build output 102 | .vuepress/dist 103 | 104 | # vuepress v2.x temp and cache directory 105 | .temp 106 | .cache 107 | 108 | # Docusaurus cache and generated files 109 | .docusaurus 110 | 111 | # Serverless directories 112 | .serverless/ 113 | 114 | # FuseBox cache 115 | .fusebox/ 116 | 117 | # DynamoDB Local files 118 | .dynamodb/ 119 | 120 | # TernJS port file 121 | .tern-port 122 | 123 | # Stores VSCode versions used for testing VSCode extensions 124 | .vscode-test 125 | 126 | # yarn v2 127 | .yarn/cache 128 | .yarn/unplugged 129 | .yarn/build-state.yml 130 | .yarn/install-state.gz 131 | .pnp.* 132 | # } 133 | 134 | # self { 135 | /.obsidian/ 136 | /data.json 137 | /main.js 138 | /metafile.json 139 | /styles.css 140 | # } 141 | -------------------------------------------------------------------------------- /.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/DavidAnson/markdownlint/main/schema/markdownlint-config-schema.json", 3 | "MD013": false, 4 | "extends": null 5 | } -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | link-workspace-packages=deep 2 | prefer-workspace-packages=true 3 | sign-git-commit=true 4 | sign-git-tag=true 5 | tag-version-prefix= 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | See [README.md § Contributing](README.md#contributing). 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Terminal for Obsidian [![release](https://img.shields.io/github/v/release/polyipseity/obsidian-terminal)][latest release] [![Obsidian downloads](https://img.shields.io/badge/dynamic/json?logo=Obsidian&color=%238b6cef&label=downloads&query=$["terminal"].downloads&url=https://raw.githubusercontent.com/obsidianmd/obsidian-releases/master/community-plugin-stats.json)][community plugin] [![Python](https://img.shields.io/badge/Python-≥3.10-gold?labelColor=blue&logo=Python&logoColor=white)][Python] 2 | 3 | [Buy Me a Coffee]: https://buymeacoffee.com/polyipseity 4 | [Buy Me a Coffee/embed]: https://img.buymeacoffee.com/button-api/?text=Buy%20me%20a%20coffee&emoji=&slug=polyipseity&button_colour=40DCA5&font_colour=ffffff&font_family=Lato&outline_colour=000000&coffee_colour=FFDD00 5 | [Obsidian]: https://obsidian.md/ 6 | [Python]: https://python.org/downloads/ 7 | [changelog]: https://github.com/polyipseity/obsidian-terminal/blob/main/CHANGELOG.md 8 | [community plugin]: https://obsidian.md/plugins?id=terminal 9 | [latest release]: https://github.com/polyipseity/obsidian-terminal/releases/latest 10 | [other things]: https://github.com/polyipseity/obsidian-monorepo 11 | [plugin library]: https://github.com/polyipseity/obsidian-plugin-library 12 | [repository]: https://github.com/polyipseity/obsidian-terminal 13 | [trailer]: https://raw.githubusercontent.com/polyipseity/obsidian-terminal/main/assets/trailer.png 14 | 15 | Integrate consoles, shells, and terminals inside [Obsidian]. 16 | 17 | [![Buy Me a Coffee/embed]][Buy Me a Coffee] 18 | 19 | __[Repository] · [Changelog] · [Community plugin] · [Other things] · [Features](#features) · [Installation](#installation) · [Usage](#usage) · [Contributing](#contributing) · [Security](#security)__ 20 | 21 | ![Trailer] 22 | 23 | For first time users, read the [installation](#installation) section first! 24 | 25 | This file is automatically opened on first install. You can reopen it in settings or command palette. 26 | 27 | ## Features 28 | 29 | - Start external terminals from Obsidian. 30 | - Integrate terminals into Obsidian. 31 | - Has an emulated developer console usable on all platforms. 32 | - Supports multiple terminal profiles. 33 | - Has built-in keyboard shortcuts. 34 | - Automatically save and restore integrated terminal history. 35 | - Find in terminal. 36 | - Save terminal history as file. 37 | - Customize terminal appearance. 38 | 39 | ## Installation 40 | 41 | 1. Install plugin. 42 | - Community plugins 43 | 1. Install the [plugin][community plugin] from community plugins directly. 44 | - Manual 45 | 1. Create directory `terminal` under `.obsidian/plugins` of your vault. 46 | 2. Place `manifest.json`, `main.js`, and `styles.css` from the [latest release] into the directory. 47 | - Building (latest) 48 | 1. Clone this repository, including its submodules. 49 | 2. Install [npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm). 50 | 3. Run `npm install` in the root directory. 51 | 4. Run `npm run obsidian:install ` in the root directory. 52 | - [Obsidian42 - BRAT](https://obsidian.md/plugins?id=obsidian42-brat) (latest) 53 | - See [their readme](https://github.com/TfTHacker/obsidian42-brat#readme). 54 | 2. (optional for Windows, recommended) Install Python and dependencies. 55 | 1. Install [Python] 3.10/+. 56 | 2. (Windows only) Run `pip3 install psutil==5.9.5 pywinctl==0.0.50 typing_extensions==4.7.1`. 57 | 3. Configure Python executable in plugin settings. Press the "Check" button to validate the Python configuration. 58 | 3. Enable plugin. 59 | 4. (optional) Configure plugin settings. 60 | 61 | ## Usage 62 | 63 | - To start a new external or integrated terminal 64 | - Ribbon 65 | 1. Click on the `Open terminal` ribbon. 66 | 2. Choose the desired profile. 67 | - Context menu 68 | 1. Right-click on files, folders, or tab headers. 69 | 2. Choose the desired action (and profile). 70 | - Command palette 71 | 1. Press `Ctrl`+`P` or click on the `Open command palette` ribbon next to the left window border. 72 | 2. Choose the desired action (and profile). 73 | - To save and restore integrated terminal history 74 | 1. Keep the terminal open when exiting Obsidian. 75 | 2. Terminal history will be restored next time Obsidian is opened. 76 | - Additional actions 77 | - Includes 78 | - Find in terminal: (1), (4) 79 | - Clear terminal: (1), (4) 80 | - Restart terminal: (1) 81 | - Edit terminal: (1) 82 | - Save terminal history as file: (1) 83 | - Export, import, or edit settings: (2), (3) 84 | - Open documentation: (2), (3) 85 | - Available by 86 | - (1) Right-click on tab header/`More options` 87 | - (2) Open settings 88 | - (3) Open command palette 89 | - (4) Use keyboard shortcuts 90 | 91 | ### Keyboard shortcuts 92 | 93 | The keyboard shortcuts are not customizable for now. 94 | 95 | 96 | __Terminal tab is focused__ 97 | 98 | - Focus: `Ctrl`+`Shift`+`` ` `` 99 | - Inherits global keyboard shortcuts 100 | 101 | 102 | __Terminal is focused__ 103 | 104 | - Clear: `Ctrl`+`Shift`+`K`, `Command`+`K` (Apple) 105 | - Close: `Ctrl`+`Shift`+`W`, `Command`+`W` (Apple) 106 | - Find: `Ctrl`+`Shift`+`F`, `Command`+`F` (Apple) 107 | - Toggle focus: `Ctrl`+`Shift`+`` ` `` 108 | 109 | ### Theming 110 | 111 | Theming is possible. However, there is no user-friendly interface for now. 112 | 113 | 1. Open the profile editing modal. 114 | 2. Click on the `Edit` button labeled `Data`. It should open up a new modal in which there is a large textbox. 115 | 3. Notice `terminalOptions` in the text area labeled `Data`. Refer to the [`xterm.js` documentation](https://github.com/xtermjs/xterm.js/blob/master/typings/xterm.d.ts#L26) (`ITerminalOptions`) to set the options. Nested objects may need to be used. 116 | 4. Save the profile. Changes should apply immediately. 117 | 118 | ### Profiles 119 | 120 | This plugin comes with several profile presets that you can reference. 121 | 122 | When setting up a terminal profile, you need to distinguish between shells and terminal emulators. (Search online if needed.) Generally, integrated profiles only work with shells while external ones only work with terminal emulators. 123 | 124 | #### Examples 125 | 126 | 127 | __Shells__ 128 | 129 | - Bash: `bash` 130 | - Bourne shell: `sh` 131 | - Command Prompt: `cmd` 132 | - Dash: `dash` 133 | - Git Bash: `\bin\bash.exe` (e.g. `C:\Program Files\Git\bin\bash.exe`) 134 | - PowerShell Core: `pwsh` 135 | - Windows PowerShell: `powershell` 136 | - Windows Subsystem for Linux: `wsl` or `wsl -d ` 137 | - Z shell: `zsh` 138 | 139 | 140 | __Terminal emulators__ 141 | 142 | - Command Prompt: `cmd` 143 | - GNOME Terminal: `gnome-terminal` 144 | - Konsole: `konsole` 145 | - Terminal (macOS): `/System/Applications/Utilities/Terminal.app/Contents/macOS/Terminal "$PWD"` 146 | - Windows Terminal: `wt` 147 | - iTerm2: `/Applications/iTerm.app/Contents/MacOS/iTerm2 "$PWD"` 148 | - xterm: `xterm` 149 | 150 | ### Miscellaneous 151 | 152 | This plugin patches `require` so that `require("obsidian")` and other Obsidian modules work in the developer console. It is toggleable as `Expose internal modules` in settings. 153 | 154 | In the developer console, a context variable `$$` is passed into the code, which can be used to dynamically change terminal options. 155 | 156 | The full API is available from [`sources/@types/obsidian-terminal.ts`](sources/%40types/obsidian-terminal.ts). 157 | 158 | ### Troubleshooting 159 | 160 | - Is the plugin useful on mobile? 161 | - Compared to on desktop, it is much less useful. The only use for it for now is opening a developer console on mobile. 162 | - Why do hotkeys not work? 163 | - If the terminal is in focus, all Obsidian hotkeys are disabled so that you can type special characters into the terminal. You can unfocus the terminal by pressing `Ctrl`+`Shift`+`` ` ``, then you can use Obsidian hotkeys again. 164 | 165 | ## Contributing 166 | 167 | Contributions are welcome! 168 | 169 | This project uses [`changesets`](https://github.com/changesets/changesets) to manage the changelog. When creating a pull request, please [add a changeset](https://github.com/changesets/changesets/blob/main/docs/intro-to-using-changesets.md#adding-changesets) describing the changes. Add multiple changesets if your pull request changes several things. End each changeset with `([PR number](PR link) by [author username](author link))`. For example, the newly created file under the directory `.changeset` should look like: 170 | 171 | ```Markdown 172 | --- 173 | "example": patch 174 | --- 175 | 176 | This is an example change. ([GH#1](https://github.com/ghost/example/pull/1) by [@ghost](https://github.com/ghost)) 177 | ``` 178 | 179 | ### Todos 180 | 181 | The todos here, ordered alphabetically, are things planned for the plugin. There are no guarantees that they will be completed. However, we are likely to accept contributions for them. 182 | 183 | - Connect to remote shells. 184 | - Copy terminal tabs. 185 | - Create ad-hoc profile in `Open terminal: Select`. 186 | - Detect sandboxed environment and notify users. 187 | - Edit profile before opening terminal in `Open terminal: Select` when a modifier key is pressed. 188 | - External link confirmation. 189 | - Filter console log by severity in the developer console. 190 | - Fix broken section links in builtin documentations. 191 | - Indicate that the terminal resizer has crashed or is disabled. 192 | - Shared terminal tabs. 193 | - Vim mode switch. 194 | 195 | ### Translating 196 | 197 | Translation files are under [`assets/locales/`](assets/locales/). Each locale has its own directory named with its corresponding __[IETF language tag](https://wikipedia.org/wiki/IETF_language_tag)__. Some translation keys are missing here and instead located at [`obsidian-plugin-library`][plugin library]. 198 | 199 | To contribute translation for an existing locale, modify the files in the corresponding directory. 200 | 201 | For a new locale, create a new directory named with its language tag and copy [`assets/locales/en/translation.json`](assets/locales/en/translation.json) into it. Then, add an entry to [`assets/locales/en/language.json`](assets/locales/en/language.json) in this format: 202 | 203 | ```JSONc 204 | { 205 | // ... 206 | "en": "English", 207 | "(your-language-tag)": "(Native name of your language)", 208 | "uwu": "Uwuish", 209 | // ... 210 | } 211 | ``` 212 | 213 | Sort the list of languages by the alphabetical order of their language tags. Then modify the files in the new directory. There will be errors in [`assets/locales.ts`](assets/locales.ts), which you can ignore and we will fix them for you. You are welcome to fix them yourself if you know TypeScript. 214 | 215 | When translating, keep in mind the following things: 216 | 217 | - Do not translate anything between `{{` and `}}` (`{{example}}`). They are __interpolations__ and will be replaced by localized strings at runtime. 218 | - Do not translate anything between `$t(` and `)` (`$t(example)`). They refer to other localized strings. To find the localized string being referred to, follow the path of the key, which is separated by dots (`.`). For example, the key [`youtu.be./dQw4w9WgXcQ`](https://youtu.be./dQw4w9WgXcQ) refers to: 219 | 220 | ```JSONc 221 | { 222 | // ... 223 | "youtu": { 224 | // ... 225 | "be": { 226 | // ... 227 | "/dQw4w9WgXcQ": "I am 'youtu.be./dQw4w9WgXcQ'!", 228 | // ... 229 | }, 230 | // ... 231 | }, 232 | // ... 233 | } 234 | ``` 235 | 236 | - The keys under `generic` are vocabularies. They can be referred in translation strings by `$t(generic.key)`. Refer to them as much as possible to standardize translations for vocabularies that appear in different places. 237 | - It is okay to move interpolations and references to other localized strings around to make the translation natural. It is also okay to not use some references used in the original translation. However, it is NOT okay to not use all interpolations. 238 | 239 | ## Security 240 | 241 | We hope that there will never be any security vulnerabilities, but unfortunately it does happen. Please [report](#reporting-a-vulnerability) them! 242 | 243 | ### Supported versions 244 | 245 | | Version | Supported | 246 | |-|-| 247 | | latest | ✅ | 248 | | outdated | ❌ | 249 | 250 | ### Reporting a vulnerability 251 | 252 | Please report a vulerability by opening an new issue. We will get back to you as soon as possible. 253 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security 2 | 3 | See [README.md § Security](README.md#security). 4 | -------------------------------------------------------------------------------- /assets/locales.ts: -------------------------------------------------------------------------------- 1 | import { 2 | type AwaitResources, 3 | LibraryLocales, 4 | mergeResources, 5 | syncLocale, 6 | typedKeys, 7 | } from "@polyipseity/obsidian-plugin-library" 8 | import type en from "./locales/en/translation.json" 9 | 10 | export namespace PluginLocales { 11 | export const { 12 | DEFAULT_LANGUAGE, 13 | DEFAULT_NAMESPACE, 14 | FALLBACK_LANGUAGES, 15 | FORMATTERS, 16 | RETURN_NULL, 17 | } = LibraryLocales 18 | const sync = syncLocale() 19 | export const RESOURCES = mergeResources(LibraryLocales.RESOURCES, { 20 | af: { 21 | [DEFAULT_NAMESPACE]: async () => 22 | sync((await import("./locales/af/translation.json")).default), 23 | }, 24 | am: { 25 | [DEFAULT_NAMESPACE]: async () => 26 | sync((await import("./locales/am/translation.json")).default), 27 | }, 28 | ar: { 29 | [DEFAULT_NAMESPACE]: async () => 30 | sync((await import("./locales/ar/translation.json")).default), 31 | }, 32 | be: { 33 | [DEFAULT_NAMESPACE]: async () => 34 | sync((await import("./locales/be/translation.json")).default), 35 | }, 36 | bg: { 37 | [DEFAULT_NAMESPACE]: async () => 38 | sync((await import("./locales/bg/translation.json")).default), 39 | }, 40 | bn: { 41 | [DEFAULT_NAMESPACE]: async () => 42 | sync((await import("./locales/bn/translation.json")).default), 43 | }, 44 | ca: { 45 | [DEFAULT_NAMESPACE]: async () => 46 | sync((await import("./locales/ca/translation.json")).default), 47 | }, 48 | cs: { 49 | [DEFAULT_NAMESPACE]: async () => 50 | sync((await import("./locales/cs/translation.json")).default), 51 | }, 52 | da: { 53 | [DEFAULT_NAMESPACE]: async () => 54 | sync((await import("./locales/da/translation.json")).default), 55 | }, 56 | de: { 57 | [DEFAULT_NAMESPACE]: async () => 58 | sync((await import("./locales/de/translation.json")).default), 59 | }, 60 | el: { 61 | [DEFAULT_NAMESPACE]: async () => 62 | sync((await import("./locales/el/translation.json")).default), 63 | }, 64 | en: { 65 | [DEFAULT_NAMESPACE]: async () => 66 | sync((await import("./locales/en/translation.json")).default), 67 | asset: async () => (await import("./locales/en/asset.json")).default, 68 | language: async () => 69 | (await import("./locales/en/language.json")).default, 70 | }, 71 | eo: { 72 | [DEFAULT_NAMESPACE]: async () => 73 | sync((await import("./locales/eo/translation.json")).default), 74 | }, 75 | es: { 76 | [DEFAULT_NAMESPACE]: async () => 77 | sync((await import("./locales/es/translation.json")).default), 78 | }, 79 | eu: { 80 | [DEFAULT_NAMESPACE]: async () => 81 | sync((await import("./locales/eu/translation.json")).default), 82 | }, 83 | fa: { 84 | [DEFAULT_NAMESPACE]: async () => 85 | sync((await import("./locales/fa/translation.json")).default), 86 | }, 87 | fi: { 88 | [DEFAULT_NAMESPACE]: async () => 89 | sync((await import("./locales/fi/translation.json")).default), 90 | }, 91 | fr: { 92 | [DEFAULT_NAMESPACE]: async () => 93 | sync((await import("./locales/fr/translation.json")).default), 94 | }, 95 | gl: { 96 | [DEFAULT_NAMESPACE]: async () => 97 | sync((await import("./locales/gl/translation.json")).default), 98 | }, 99 | he: { 100 | [DEFAULT_NAMESPACE]: async () => 101 | sync((await import("./locales/he/translation.json")).default), 102 | }, 103 | hi: { 104 | [DEFAULT_NAMESPACE]: async () => 105 | sync((await import("./locales/hi/translation.json")).default), 106 | }, 107 | hu: { 108 | [DEFAULT_NAMESPACE]: async () => 109 | sync((await import("./locales/hu/translation.json")).default), 110 | }, 111 | id: { 112 | [DEFAULT_NAMESPACE]: async () => 113 | sync((await import("./locales/id/translation.json")).default), 114 | }, 115 | it: { 116 | [DEFAULT_NAMESPACE]: async () => 117 | sync((await import("./locales/it/translation.json")).default), 118 | }, 119 | ja: { 120 | [DEFAULT_NAMESPACE]: async () => 121 | sync((await import("./locales/ja/translation.json")).default), 122 | }, 123 | ko: { 124 | [DEFAULT_NAMESPACE]: async () => 125 | sync((await import("./locales/ko/translation.json")).default), 126 | }, 127 | lv: { 128 | [DEFAULT_NAMESPACE]: async () => 129 | sync((await import("./locales/lv/translation.json")).default), 130 | }, 131 | ml: { 132 | [DEFAULT_NAMESPACE]: async () => 133 | sync((await import("./locales/ml/translation.json")).default), 134 | }, 135 | ms: { 136 | [DEFAULT_NAMESPACE]: async () => 137 | sync((await import("./locales/ms/translation.json")).default), 138 | }, 139 | nl: { 140 | [DEFAULT_NAMESPACE]: async () => 141 | sync((await import("./locales/nl/translation.json")).default), 142 | }, 143 | no: { 144 | [DEFAULT_NAMESPACE]: async () => 145 | sync((await import("./locales/no/translation.json")).default), 146 | }, 147 | oc: { 148 | [DEFAULT_NAMESPACE]: async () => 149 | sync((await import("./locales/oc/translation.json")).default), 150 | }, 151 | pl: { 152 | [DEFAULT_NAMESPACE]: async () => 153 | sync((await import("./locales/pl/translation.json")).default), 154 | }, 155 | pt: { 156 | [DEFAULT_NAMESPACE]: async () => 157 | sync((await import("./locales/pt/translation.json")).default), 158 | }, 159 | // eslint-disable-next-line @typescript-eslint/naming-convention 160 | "pt-BR": { 161 | [DEFAULT_NAMESPACE]: async () => 162 | sync((await import("./locales/pt-BR/translation.json")).default), 163 | }, 164 | ro: { 165 | [DEFAULT_NAMESPACE]: async () => 166 | sync((await import("./locales/ro/translation.json")).default), 167 | }, 168 | ru: { 169 | [DEFAULT_NAMESPACE]: async () => 170 | sync((await import("./locales/ru/translation.json")).default), 171 | }, 172 | se: { 173 | [DEFAULT_NAMESPACE]: async () => 174 | sync((await import("./locales/se/translation.json")).default), 175 | }, 176 | sk: { 177 | [DEFAULT_NAMESPACE]: async () => 178 | sync((await import("./locales/sk/translation.json")).default), 179 | }, 180 | sq: { 181 | [DEFAULT_NAMESPACE]: async () => 182 | sync((await import("./locales/sq/translation.json")).default), 183 | }, 184 | sr: { 185 | [DEFAULT_NAMESPACE]: async () => 186 | sync((await import("./locales/sr/translation.json")).default), 187 | }, 188 | ta: { 189 | [DEFAULT_NAMESPACE]: async () => 190 | sync((await import("./locales/ta/translation.json")).default), 191 | }, 192 | te: { 193 | [DEFAULT_NAMESPACE]: async () => 194 | sync((await import("./locales/te/translation.json")).default), 195 | }, 196 | th: { 197 | [DEFAULT_NAMESPACE]: async () => 198 | sync((await import("./locales/th/translation.json")).default), 199 | }, 200 | tr: { 201 | [DEFAULT_NAMESPACE]: async () => 202 | sync((await import("./locales/tr/translation.json")).default), 203 | }, 204 | uk: { 205 | [DEFAULT_NAMESPACE]: async () => 206 | sync((await import("./locales/uk/translation.json")).default), 207 | }, 208 | ur: { 209 | [DEFAULT_NAMESPACE]: async () => 210 | sync((await import("./locales/ur/translation.json")).default), 211 | }, 212 | // eslint-disable-next-line @typescript-eslint/naming-convention 213 | "zh-Hans": { 214 | [DEFAULT_NAMESPACE]: async () => 215 | sync((await import("./locales/zh-Hans/translation.json")).default), 216 | }, 217 | // eslint-disable-next-line @typescript-eslint/naming-convention 218 | "zh-Hant": { 219 | [DEFAULT_NAMESPACE]: async () => 220 | sync((await import("./locales/zh-Hant/translation.json")).default), 221 | }, 222 | }) 223 | export type Resources = 224 | AwaitResources 225 | export type Namespaces = readonly ["translation", "language", "asset"] 226 | export const NAMESPACES = typedKeys()(RESOURCES[DEFAULT_LANGUAGE]) 227 | export type Languages = readonly [ 228 | "af", 229 | "am", 230 | "ar", 231 | "be", 232 | "bg", 233 | "bn", 234 | "ca", 235 | "cs", 236 | "da", 237 | "de", 238 | "el", 239 | "en", 240 | "eo", 241 | "es", 242 | "eu", 243 | "fa", 244 | "fi", 245 | "fr", 246 | "gl", 247 | "he", 248 | "hi", 249 | "hu", 250 | "id", 251 | "it", 252 | "ja", 253 | "ko", 254 | "lv", 255 | "ml", 256 | "ms", 257 | "nl", 258 | "no", 259 | "oc", 260 | "pl", 261 | "pt", 262 | "pt-BR", 263 | "ro", 264 | "ru", 265 | "se", 266 | "sk", 267 | "sq", 268 | "sr", 269 | "ta", 270 | "te", 271 | "th", 272 | "tr", 273 | "uk", 274 | "ur", 275 | "zh-Hans", 276 | "zh-Hant", 277 | ] 278 | export const LANGUAGES = typedKeys> extends Languages[number] ? Languages : never>()(RESOURCES) 281 | } 282 | -------------------------------------------------------------------------------- /assets/locales/af/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "commands": { 3 | "open-developer-console": "$t(generic.open, capitalize) $t(generic.profile-types.developerConsole)", 4 | "open-documentation-changelog": "$t(generic.open, capitalize) $t(generic.documentations.changelog)", 5 | "open-documentation-donate": "$t(generic.open, capitalize) $t(generic.documentations.donate)", 6 | "open-documentation-readme": "$t(generic.open, capitalize) $t(generic.documentations.readme)", 7 | "open-terminal-": "$t(generic.open, capitalize) $t(generic.terminal): $t(generic.profile-types.{{type}}, capitalize)", 8 | "open-terminal-current": "$t(generic.open, capitalize) $t(generic.current-directory) in $t(generic.terminal): $t(generic.profile-types.{{type}}, capitalize)", 9 | "open-terminal-root": "$t(generic.open, capitalize) $t(generic.root-directory) in $t(generic.terminal): $t(generic.profile-types.{{type}}, capitalize)" 10 | }, 11 | "components": { 12 | "profile": { 13 | "data": "$t(generic.data, capitalize)", 14 | "data-edit": "$t(generic.edit, capitalize)", 15 | "external": { 16 | "arguments": "$t(generic.argument_other, capitalize)", 17 | "arguments-description": "$t(generic.list-description, capitalize)", 18 | "arguments-edit": "$t(generic.edit, capitalize)", 19 | "executable": "$t(generic.executable, capitalize)" 20 | }, 21 | "integrated": { 22 | "Python-executable": "$t(generic.Python, capitalize) $t(generic.executable)", 23 | "Python-executable-check": "$t(generic.check, capitalize)", 24 | "Python-executable-checking": "$t(generic.check_gerund, capitalize)", 25 | "Python-executable-description": "Recommend {{version}} or up. Required on $t(generic.platforms.unix) to $t(generic.spawn) $t(generic.profile-types.integrated) $t(generic.terminal). $t(generic.clear, capitalize) $t(generic.text-field) to $t(generic.disable) $t(generic.Python).", 26 | "Python-executable-placeholder": "($t(generic.disable_past, capitalize))", 27 | "arguments": "$t(generic.argument_other, capitalize)", 28 | "arguments-description": "$t(generic.list-description, capitalize)", 29 | "arguments-edit": "$t(generic.edit, capitalize)", 30 | "executable": "$t(generic.executable, capitalize)", 31 | "use-win32-conhost": "$t(generic.use, capitalize) $t(generic.platforms.win32) 'conhost.exe'", 32 | "use-win32-conhost-description": "$t(generic.disable, capitalize) if running 'conhost.exe' does not create a window. No guarantees this will work." 33 | }, 34 | "name": "$t(generic.name, capitalize)", 35 | "platform": "$t(generic.platforms.{{type}}, capitalize)", 36 | "platform-description-": "", 37 | "platform-description-current": "Current $t(generic.platform)", 38 | "preset": "$t(generic.preset, capitalize)", 39 | "preset-placeholder": "($t(generic.custom, capitalize))", 40 | "reset": "$t(generic.reset, capitalize)", 41 | "restore-history": "$t(generic.restore, capitalize) $t(generic.history)", 42 | "success-exit-codes": "Success $t(generic.exit) codes", 43 | "success-exit-codes-description": "$t(generic.list-description, capitalize)", 44 | "success-exit-codes-edit": "$t(generic.edit, capitalize)", 45 | "terminal-options": "$t(generic.terminal-option_other, capitalize)", 46 | "terminal-options-edit": "$t(generic.edit, capitalize)", 47 | "title": "{{name}}", 48 | "type": "$t(generic.type, capitalize)", 49 | "type-options": "$t(generic.profile-types.{{type}}, capitalize)" 50 | }, 51 | "profile-list": { 52 | "descriptor-": "{{info.id}}", 53 | "descriptor-incompatible": "($t(generic.incompatible, capitalize)) $t(components.profile-list.descriptor-)", 54 | "edit": "$t(generic.edit, capitalize)", 55 | "namer-": "{{info.name}}", 56 | "namer-incompatible": "$t(components.profile-list.namer-)", 57 | "preset-placeholder": "$t(components.dropdown.placeholder)", 58 | "title": "$t(generic.profile_other, capitalize)" 59 | }, 60 | "select-profile": { 61 | "item-text-": "$t(profile-name-formats.long)", 62 | "item-text-incompatible": "($t(generic.incompatible, capitalize)) $t(components.select-profile.item-text-)" 63 | }, 64 | "terminal": { 65 | "display-name": "$t(generic.terminal, capitalize): {{name}}", 66 | "edit-modal": { 67 | "profile": "$t(generic.profile, capitalize)", 68 | "profile-edit": "$t(generic.edit, capitalize)", 69 | "profile-name-": "$t(profile-name-formats.short)", 70 | "profile-name-incompatible": "($t(generic.incompatible, capitalize)) $t(components.terminal.edit-modal.profile-name-)", 71 | "profile-placeholder": "$t(components.profile.preset-placeholder)", 72 | "reset": "$t(generic.reset, capitalize)", 73 | "root-directory": "$t(generic.root-directory, capitalize)", 74 | "title": "$t(generic.edit, capitalize) $t(generic.terminal)", 75 | "working-directory": "$t(generic.working-directory, capitalize)", 76 | "working-directory-placeholder": "($t(generic.undefined, capitalize))" 77 | }, 78 | "menus": { 79 | "clear": "$t(generic.clear, capitalize)", 80 | "edit": "$t(generic.edit, capitalize)", 81 | "find": "$t(generic.find, capitalize)", 82 | "restart": "$t(generic.restart, capitalize)", 83 | "save-as-HTML": "$t(generic.save, capitalize) as $t(generic.file-extensions.HTML)" 84 | }, 85 | "name": { 86 | "profile-type": "$t(generic.profile-types.{{type}}, capitalize)" 87 | }, 88 | "restored-history": "\r\n * $t(generic.restore_past, capitalize) $t(generic.history) at {{datetime, datetime(dateStyle: full, timeStyle: full)}}\r\n\r\n", 89 | "unsupported-profile": "Unsupported $t(generic.profile):\r\n{{profile}}\r\n" 90 | }, 91 | "terminal-options": { 92 | "bold-font-weight": "Bold $t(generic.font-weight)", 93 | "description-HTML": "See ITerminalOptions for all $t(generic.option_other).", 94 | "font-family": "$t(generic.font, capitalize) family", 95 | "font-size": "$t(generic.font, capitalize) size", 96 | "font-weight": "$t(generic.font-weight, capitalize)", 97 | "invalid-description": "$t(generic.invalid, capitalize)", 98 | "title": "$t(generic.terminal-option_other, capitalize)", 99 | "undefine": "$t(generic.undefine, capitalize)", 100 | "undefined-placeholder": "($t(generic.undefined, capitalize))" 101 | } 102 | }, 103 | "errors": { 104 | "error-checking-Python": "Error $t(generic.check_gerund) $t(generic.Python)", 105 | "error-killing-pseudoterminal": "Error killing $t(generic.pseudoterminal)", 106 | "error-spawning-resizer": "Error $t(generic.spawn_gerund) $t(generic.terminal-resizer)", 107 | "error-spawning-terminal": "Error $t(generic.spawn_gerund) $t(generic.terminal)", 108 | "no-Python-to-spawn-Unix-pseudoterminal": "No $t(generic.Python) to $t(generic.spawn) $t(generic.platforms.unix) $t(generic.pseudoterminal)", 109 | "not-Python": "Not $t(generic.Python)", 110 | "resizer-disabled": "$t(generic.terminal-resizer, capitalize) $t(generic.disable_past)", 111 | "resizer-exited-unexpectedly": "$t(generic.terminal-resizer, capitalize) $t(generic.exit_past) unexpectedly: {{code}}" 112 | }, 113 | "generic": { 114 | "Python": "Python", 115 | "argument": "$t(generic.argument_one)", 116 | "argument_one": "argument", 117 | "argument_other": "arguments", 118 | "behavior": "behavior", 119 | "check": "check", 120 | "check_gerund": "checking", 121 | "clear": "clear", 122 | "compatible": "compatible", 123 | "context-menu": "context menu", 124 | "current-directory": "current $t(generic.directory)", 125 | "custom": "custom", 126 | "default": "default", 127 | "directory": "directory", 128 | "disable": "disable", 129 | "disable_past": "disabled", 130 | "documentation": "documentation", 131 | "documentations": { 132 | "changelog": "changelog", 133 | "donate": "$t(generic.donate)", 134 | "readme": "readme" 135 | }, 136 | "donate": "donate", 137 | "executable": "executable", 138 | "exist": "exist", 139 | "exist_singular": "exists", 140 | "exit": "exit", 141 | "exit_past": "exited", 142 | "file-extensions": { 143 | "HTML": "HTML" 144 | }, 145 | "focus": "focus", 146 | "focus_past": "focused", 147 | "font": "font", 148 | "font-weight": "$t(generic.font) weight", 149 | "history": "history", 150 | "incompatible": "incompatible", 151 | "instance": "instance", 152 | "instance_gerund": "instancing", 153 | "invalid": "invalid", 154 | "name": "name", 155 | "option": "option", 156 | "option_other": "options", 157 | "pin": "pin", 158 | "platform": "platform", 159 | "platforms": { 160 | "darwin": "\u200bmacOS", 161 | "linux": "Linux", 162 | "unix": "Unix", 163 | "win32": "Microsoft Windows", 164 | "win32-short": "Windows" 165 | }, 166 | "preset": "preset", 167 | "profile": "$t(generic.profile_one)", 168 | "profile-types": { 169 | "": "empty", 170 | "developerConsole": "developer console", 171 | "external": "external", 172 | "integrated": "integrated", 173 | "invalid": "$t(generic.invalid)", 174 | "select": "select" 175 | }, 176 | "profile_one": "profile", 177 | "profile_other": "profiles", 178 | "pseudoterminal": "pseudoterminal", 179 | "renderer": "renderer", 180 | "renderers": { 181 | "canvas": "canvas", 182 | "dom": "DOM", 183 | "webgl": "WebGL" 184 | }, 185 | "restart": "restart", 186 | "restore": "restore", 187 | "restore_past": "restored", 188 | "root-directory": "root $t(generic.directory)", 189 | "save": "save", 190 | "spawn": "spawn", 191 | "spawn_gerund": "spawning", 192 | "split": "split", 193 | "status-bar": "status bar", 194 | "tab": "tab", 195 | "terminal": "terminal", 196 | "terminal-option": "$t(generic.terminal) $t(generic.option)", 197 | "terminal-option_other": "$t(generic.terminal) $t(generic.option_other)", 198 | "terminal-resizer": "$t(generic.terminal) resizer", 199 | "text-field": "text field", 200 | "true": "true", 201 | "type": "type", 202 | "undefine": "undefine", 203 | "undefined": "undefined", 204 | "window": "window", 205 | "working-directory": "working $t(generic.directory)" 206 | }, 207 | "menus": { 208 | "open-terminal": "$t(generic.open, capitalize) in $t(generic.terminal): $t(generic.profile-types.{{type}}, capitalize)" 209 | }, 210 | "name": "$t(generic.terminal, capitalize)", 211 | "notices": { 212 | "Python-status-entry-": "{{name}}: {{version}} (satisfied: {{requirement}})", 213 | "Python-status-entry-unsatisfied": "{{name}}: {{version}} (unsatisfied: {{requirement}})", 214 | "no-default-profile": "No $t(generic.default) $t(generic.profile) for $t(generic.type) '$t(generic.profile-types.{{type}}, capitalize)'", 215 | "spawning-terminal": "$t(generic.spawn_gerund, capitalize) $t(generic.terminal): {{name}}", 216 | "terminal-exited": "$t(generic.terminal, capitalize) $t(generic.exit_past): {{code}}" 217 | }, 218 | "profile-name-formats": { 219 | "long": "\"{{info.name}}\". $t(generic.profile-types.{{info.profile.type}}, capitalize). {{info.id}}.", 220 | "short": "\"{{info.nameOrID}}\". $t(generic.profile-types.{{info.profile.type}}, capitalize)." 221 | }, 222 | "profile-presets": { 223 | "bashIntegrated": "bash: $t(generic.profile-types.integrated, capitalize)", 224 | "cmdExternal": "cmd: $t(generic.profile-types.external, capitalize)", 225 | "cmdIntegrated": "cmd: $t(generic.profile-types.integrated, capitalize)", 226 | "darwinExternalDefault": "$t(generic.platforms.darwin, capitalize) $t(generic.default): $t(generic.profile-types.external, capitalize)", 227 | "darwinIntegratedDefault": "$t(generic.platforms.darwin, capitalize) $t(generic.default): $t(generic.profile-types.integrated, capitalize)", 228 | "dashIntegrated": "dash: $t(generic.profile-types.integrated, capitalize)", 229 | "developerConsole": "$t(generic.profile-types.developerConsole, capitalize)", 230 | "empty": "$t(generic.profile-types., capitalize)", 231 | "gitBashIntegrated": "Git Bash: $t(generic.profile-types.integrated, capitalize)", 232 | "gnomeTerminalExternal": "GNOME $t(generic.terminal, capitalize): $t(generic.profile-types.external, capitalize)", 233 | "iTerm2External": "iTerm2: $t(generic.profile-types.external, capitalize)", 234 | "konsoleExternal": "Konsole: $t(generic.profile-types.external, capitalize)", 235 | "linuxExternalDefault": "$t(generic.platforms.linux, capitalize) $t(generic.default): $t(generic.profile-types.external, capitalize)", 236 | "linuxIntegratedDefault": "$t(generic.platforms.linux, capitalize) $t(generic.default): $t(generic.profile-types.integrated, capitalize)", 237 | "powershellExternal": "powershell: $t(generic.profile-types.external, capitalize)", 238 | "powershellIntegrated": "powershell: $t(generic.profile-types.integrated, capitalize)", 239 | "pwshExternal": "pwsh: $t(generic.profile-types.external, capitalize)", 240 | "pwshIntegrated": "pwsh: $t(generic.profile-types.integrated, capitalize)", 241 | "shIntegrated": "sh: $t(generic.profile-types.integrated, capitalize)", 242 | "terminalMacOSExternal": "$t(generic.terminal, capitalize) ($t(generic.platforms.darwin, capitalize)): $t(generic.profile-types.external, capitalize)", 243 | "win32ExternalDefault": "$t(generic.platforms.win32, capitalize) $t(generic.default): $t(generic.profile-types.external, capitalize)", 244 | "win32IntegratedDefault": "$t(generic.platforms.win32, capitalize) $t(generic.default): $t(generic.profile-types.integrated, capitalize)", 245 | "wslIntegrated": "$t(generic.platforms.win32-short, capitalize) Subsystem for $t(generic.platforms.linux, capitalize): $t(generic.profile-types.integrated, capitalize)", 246 | "wtExternal": "$t(generic.platforms.win32-short, capitalize) $t(generic.terminal, capitalize): $t(generic.profile-types.external, capitalize)", 247 | "xtermExternal": "xterm: $t(generic.profile-types.external, capitalize)", 248 | "zshIntegrated": "zsh: $t(generic.profile-types.integrated, capitalize)" 249 | }, 250 | "ribbons": { 251 | "open-terminal": "$t(generic.open, capitalize) $t(generic.terminal)" 252 | }, 253 | "settings": { 254 | "add-to-command": "Add to $t(generic.command)", 255 | "add-to-context-menu": "Add to $t(generic.context-menu)", 256 | "advanced": "Advanced", 257 | "create-instance-near-existing-ones": "Create $t(generic.instance) near $t(generic.exist_gerund) ones", 258 | "create-instance-near-existing-ones-description": "Overrides '$t(settings.new-instance-behavior)' when $t(generic.instance) $t(generic.exist_singular) if $t(generic.true).", 259 | "documentation": "$t(generic.documentation, capitalize)", 260 | "documentations": { 261 | "changelog": "$t(generic.documentations.changelog, capitalize)", 262 | "donate": "$t(generic.documentations.donate, capitalize)", 263 | "readme": "$t(generic.documentations.readme, capitalize)" 264 | }, 265 | "expose-internal-modules": "Expose internal modules", 266 | "expose-internal-modules-description-HTML": "obsidian, @codemirror/*, @lezer/*…", 267 | "focus-on-new-instance": "$t(generic.focus, capitalize) on new $t(generic.instance)", 268 | "hide-status-bar": "Hide $t(generic.status-bar)", 269 | "hide-status-bar-options": { 270 | "always": "Always", 271 | "focused": "When $t(generic.terminal) is $t(generic.focus_past)", 272 | "never": "Never", 273 | "running": "When $t(generic.terminal) is running" 274 | }, 275 | "instancing": "$t(generic.instance_gerund, capitalize)", 276 | "intercept-logging": "Intercept logging", 277 | "interface": "Interface", 278 | "new-instance-behavior": "New $t(generic.instance) $t(generic.behavior)", 279 | "new-instance-behaviors": { 280 | "newHorizontalSplit": "New horizontal $t(generic.split)", 281 | "newLeftSplit": "New left $t(generic.split)", 282 | "newLeftTab": "New left $t(generic.tab)", 283 | "newRightSplit": "New right $t(generic.split)", 284 | "newRightTab": "New right $t(generic.tab)", 285 | "newTab": "New $t(generic.tab)", 286 | "newVerticalSplit": "New vertical $t(generic.split)", 287 | "newWindow": "New $t(generic.window)", 288 | "replaceTab": "Replace $t(generic.tab)" 289 | }, 290 | "open-changelog-on-update": "$t(generic.open, capitalize) $t(generic.documentations.changelog) on update", 291 | "pin-new-instance": "$t(generic.pin, capitalize) new $t(generic.instance)", 292 | "preferred-renderer": "Preferred $t(generic.renderer)", 293 | "preferred-renderer-options": "$t(generic.renderers.{{type}}, capitalize)", 294 | "profile-list": { 295 | "description": "The first $t(generic.compatible) $t(generic.profile) in the $t(generic.list) is the $t(generic.default) for its $t(generic.terminal) $t(generic.type)." 296 | }, 297 | "profiles": "$t(generic.profile_other, capitalize)", 298 | "profiles-description": "$t(generic.list-description, capitalize)", 299 | "profiles-edit": "$t(generic.edit, capitalize)" 300 | } 301 | } -------------------------------------------------------------------------------- /assets/locales/am/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "commands": { 3 | "open-developer-console": "$t(generic.open, capitalize) $t(generic.profile-types.developerConsole)", 4 | "open-documentation-changelog": "$t(generic.open, capitalize) $t(generic.documentations.changelog)", 5 | "open-documentation-donate": "$t(generic.open, capitalize) $t(generic.documentations.donate)", 6 | "open-documentation-readme": "$t(generic.open, capitalize) $t(generic.documentations.readme)", 7 | "open-terminal-": "$t(generic.open, capitalize) $t(generic.terminal): $t(generic.profile-types.{{type}}, capitalize)", 8 | "open-terminal-current": "$t(generic.open, capitalize) $t(generic.current-directory) in $t(generic.terminal): $t(generic.profile-types.{{type}}, capitalize)", 9 | "open-terminal-root": "$t(generic.open, capitalize) $t(generic.root-directory) in $t(generic.terminal): $t(generic.profile-types.{{type}}, capitalize)" 10 | }, 11 | "components": { 12 | "profile": { 13 | "data": "$t(generic.data, capitalize)", 14 | "data-edit": "$t(generic.edit, capitalize)", 15 | "external": { 16 | "arguments": "$t(generic.argument_other, capitalize)", 17 | "arguments-description": "$t(generic.list-description, capitalize)", 18 | "arguments-edit": "$t(generic.edit, capitalize)", 19 | "executable": "$t(generic.executable, capitalize)" 20 | }, 21 | "integrated": { 22 | "Python-executable": "$t(generic.Python, capitalize) $t(generic.executable)", 23 | "Python-executable-check": "$t(generic.check, capitalize)", 24 | "Python-executable-checking": "$t(generic.check_gerund, capitalize)", 25 | "Python-executable-description": "Recommend {{version}} or up. Required on $t(generic.platforms.unix) to $t(generic.spawn) $t(generic.profile-types.integrated) $t(generic.terminal). $t(generic.clear, capitalize) $t(generic.text-field) to $t(generic.disable) $t(generic.Python).", 26 | "Python-executable-placeholder": "($t(generic.disable_past, capitalize))", 27 | "arguments": "$t(generic.argument_other, capitalize)", 28 | "arguments-description": "$t(generic.list-description, capitalize)", 29 | "arguments-edit": "$t(generic.edit, capitalize)", 30 | "executable": "$t(generic.executable, capitalize)", 31 | "use-win32-conhost": "$t(generic.use, capitalize) $t(generic.platforms.win32) 'conhost.exe'", 32 | "use-win32-conhost-description": "$t(generic.disable, capitalize) if running 'conhost.exe' does not create a window. No guarantees this will work." 33 | }, 34 | "name": "$t(generic.name, capitalize)", 35 | "platform": "$t(generic.platforms.{{type}}, capitalize)", 36 | "platform-description-": "", 37 | "platform-description-current": "Current $t(generic.platform)", 38 | "preset": "$t(generic.preset, capitalize)", 39 | "preset-placeholder": "($t(generic.custom, capitalize))", 40 | "reset": "$t(generic.reset, capitalize)", 41 | "restore-history": "$t(generic.restore, capitalize) $t(generic.history)", 42 | "success-exit-codes": "Success $t(generic.exit) codes", 43 | "success-exit-codes-description": "$t(generic.list-description, capitalize)", 44 | "success-exit-codes-edit": "$t(generic.edit, capitalize)", 45 | "terminal-options": "$t(generic.terminal-option_other, capitalize)", 46 | "terminal-options-edit": "$t(generic.edit, capitalize)", 47 | "title": "{{name}}", 48 | "type": "$t(generic.type, capitalize)", 49 | "type-options": "$t(generic.profile-types.{{type}}, capitalize)" 50 | }, 51 | "profile-list": { 52 | "descriptor-": "{{info.id}}", 53 | "descriptor-incompatible": "($t(generic.incompatible, capitalize)) $t(components.profile-list.descriptor-)", 54 | "edit": "$t(generic.edit, capitalize)", 55 | "namer-": "{{info.name}}", 56 | "namer-incompatible": "$t(components.profile-list.namer-)", 57 | "preset-placeholder": "$t(components.dropdown.placeholder)", 58 | "title": "$t(generic.profile_other, capitalize)" 59 | }, 60 | "select-profile": { 61 | "item-text-": "$t(profile-name-formats.long)", 62 | "item-text-incompatible": "($t(generic.incompatible, capitalize)) $t(components.select-profile.item-text-)" 63 | }, 64 | "terminal": { 65 | "display-name": "$t(generic.terminal, capitalize): {{name}}", 66 | "edit-modal": { 67 | "profile": "$t(generic.profile, capitalize)", 68 | "profile-edit": "$t(generic.edit, capitalize)", 69 | "profile-name-": "$t(profile-name-formats.short)", 70 | "profile-name-incompatible": "($t(generic.incompatible, capitalize)) $t(components.terminal.edit-modal.profile-name-)", 71 | "profile-placeholder": "$t(components.profile.preset-placeholder)", 72 | "reset": "$t(generic.reset, capitalize)", 73 | "root-directory": "$t(generic.root-directory, capitalize)", 74 | "title": "$t(generic.edit, capitalize) $t(generic.terminal)", 75 | "working-directory": "$t(generic.working-directory, capitalize)", 76 | "working-directory-placeholder": "($t(generic.undefined, capitalize))" 77 | }, 78 | "menus": { 79 | "clear": "$t(generic.clear, capitalize)", 80 | "edit": "$t(generic.edit, capitalize)", 81 | "find": "$t(generic.find, capitalize)", 82 | "restart": "$t(generic.restart, capitalize)", 83 | "save-as-HTML": "$t(generic.save, capitalize) as $t(generic.file-extensions.HTML)" 84 | }, 85 | "name": { 86 | "profile-type": "$t(generic.profile-types.{{type}}, capitalize)" 87 | }, 88 | "restored-history": "\r\n * $t(generic.restore_past, capitalize) $t(generic.history) at {{datetime, datetime(dateStyle: full, timeStyle: full)}}\r\n\r\n", 89 | "unsupported-profile": "Unsupported $t(generic.profile):\r\n{{profile}}\r\n" 90 | }, 91 | "terminal-options": { 92 | "bold-font-weight": "Bold $t(generic.font-weight)", 93 | "description-HTML": "See ITerminalOptions for all $t(generic.option_other).", 94 | "font-family": "$t(generic.font, capitalize) family", 95 | "font-size": "$t(generic.font, capitalize) size", 96 | "font-weight": "$t(generic.font-weight, capitalize)", 97 | "invalid-description": "$t(generic.invalid, capitalize)", 98 | "title": "$t(generic.terminal-option_other, capitalize)", 99 | "undefine": "$t(generic.undefine, capitalize)", 100 | "undefined-placeholder": "($t(generic.undefined, capitalize))" 101 | } 102 | }, 103 | "errors": { 104 | "error-checking-Python": "Error $t(generic.check_gerund) $t(generic.Python)", 105 | "error-killing-pseudoterminal": "Error killing $t(generic.pseudoterminal)", 106 | "error-spawning-resizer": "Error $t(generic.spawn_gerund) $t(generic.terminal-resizer)", 107 | "error-spawning-terminal": "Error $t(generic.spawn_gerund) $t(generic.terminal)", 108 | "no-Python-to-spawn-Unix-pseudoterminal": "No $t(generic.Python) to $t(generic.spawn) $t(generic.platforms.unix) $t(generic.pseudoterminal)", 109 | "not-Python": "Not $t(generic.Python)", 110 | "resizer-disabled": "$t(generic.terminal-resizer, capitalize) $t(generic.disable_past)", 111 | "resizer-exited-unexpectedly": "$t(generic.terminal-resizer, capitalize) $t(generic.exit_past) unexpectedly: {{code}}" 112 | }, 113 | "generic": { 114 | "Python": "Python", 115 | "argument": "$t(generic.argument_one)", 116 | "argument_one": "argument", 117 | "argument_other": "arguments", 118 | "behavior": "behavior", 119 | "check": "check", 120 | "check_gerund": "checking", 121 | "clear": "clear", 122 | "compatible": "compatible", 123 | "context-menu": "context menu", 124 | "current-directory": "current $t(generic.directory)", 125 | "custom": "custom", 126 | "default": "default", 127 | "directory": "directory", 128 | "disable": "disable", 129 | "disable_past": "disabled", 130 | "documentation": "documentation", 131 | "documentations": { 132 | "changelog": "changelog", 133 | "donate": "$t(generic.donate)", 134 | "readme": "readme" 135 | }, 136 | "donate": "donate", 137 | "executable": "executable", 138 | "exist": "exist", 139 | "exist_singular": "exists", 140 | "exit": "exit", 141 | "exit_past": "exited", 142 | "file-extensions": { 143 | "HTML": "HTML" 144 | }, 145 | "focus": "focus", 146 | "focus_past": "focused", 147 | "font": "font", 148 | "font-weight": "$t(generic.font) weight", 149 | "history": "history", 150 | "incompatible": "incompatible", 151 | "instance": "instance", 152 | "instance_gerund": "instancing", 153 | "invalid": "invalid", 154 | "name": "name", 155 | "option": "option", 156 | "option_other": "options", 157 | "pin": "pin", 158 | "platform": "platform", 159 | "platforms": { 160 | "darwin": "\u200bmacOS", 161 | "linux": "Linux", 162 | "unix": "Unix", 163 | "win32": "Microsoft Windows", 164 | "win32-short": "Windows" 165 | }, 166 | "preset": "preset", 167 | "profile": "$t(generic.profile_one)", 168 | "profile-types": { 169 | "": "empty", 170 | "developerConsole": "developer console", 171 | "external": "external", 172 | "integrated": "integrated", 173 | "invalid": "$t(generic.invalid)", 174 | "select": "select" 175 | }, 176 | "profile_one": "profile", 177 | "profile_other": "profiles", 178 | "pseudoterminal": "pseudoterminal", 179 | "renderer": "renderer", 180 | "renderers": { 181 | "canvas": "canvas", 182 | "dom": "DOM", 183 | "webgl": "WebGL" 184 | }, 185 | "restart": "restart", 186 | "restore": "restore", 187 | "restore_past": "restored", 188 | "root-directory": "root $t(generic.directory)", 189 | "save": "save", 190 | "spawn": "spawn", 191 | "spawn_gerund": "spawning", 192 | "split": "split", 193 | "status-bar": "status bar", 194 | "tab": "tab", 195 | "terminal": "terminal", 196 | "terminal-option": "$t(generic.terminal) $t(generic.option)", 197 | "terminal-option_other": "$t(generic.terminal) $t(generic.option_other)", 198 | "terminal-resizer": "$t(generic.terminal) resizer", 199 | "text-field": "text field", 200 | "true": "true", 201 | "type": "type", 202 | "undefine": "undefine", 203 | "undefined": "undefined", 204 | "window": "window", 205 | "working-directory": "working $t(generic.directory)" 206 | }, 207 | "menus": { 208 | "open-terminal": "$t(generic.open, capitalize) in $t(generic.terminal): $t(generic.profile-types.{{type}}, capitalize)" 209 | }, 210 | "name": "$t(generic.terminal, capitalize)", 211 | "notices": { 212 | "Python-status-entry-": "{{name}}: {{version}} (satisfied: {{requirement}})", 213 | "Python-status-entry-unsatisfied": "{{name}}: {{version}} (unsatisfied: {{requirement}})", 214 | "no-default-profile": "No $t(generic.default) $t(generic.profile) for $t(generic.type) '$t(generic.profile-types.{{type}}, capitalize)'", 215 | "spawning-terminal": "$t(generic.spawn_gerund, capitalize) $t(generic.terminal): {{name}}", 216 | "terminal-exited": "$t(generic.terminal, capitalize) $t(generic.exit_past): {{code}}" 217 | }, 218 | "profile-name-formats": { 219 | "long": "\"{{info.name}}\". $t(generic.profile-types.{{info.profile.type}}, capitalize). {{info.id}}.", 220 | "short": "\"{{info.nameOrID}}\". $t(generic.profile-types.{{info.profile.type}}, capitalize)." 221 | }, 222 | "profile-presets": { 223 | "bashIntegrated": "bash: $t(generic.profile-types.integrated, capitalize)", 224 | "cmdExternal": "cmd: $t(generic.profile-types.external, capitalize)", 225 | "cmdIntegrated": "cmd: $t(generic.profile-types.integrated, capitalize)", 226 | "darwinExternalDefault": "$t(generic.platforms.darwin, capitalize) $t(generic.default): $t(generic.profile-types.external, capitalize)", 227 | "darwinIntegratedDefault": "$t(generic.platforms.darwin, capitalize) $t(generic.default): $t(generic.profile-types.integrated, capitalize)", 228 | "dashIntegrated": "dash: $t(generic.profile-types.integrated, capitalize)", 229 | "developerConsole": "$t(generic.profile-types.developerConsole, capitalize)", 230 | "empty": "$t(generic.profile-types., capitalize)", 231 | "gitBashIntegrated": "Git Bash: $t(generic.profile-types.integrated, capitalize)", 232 | "gnomeTerminalExternal": "GNOME $t(generic.terminal, capitalize): $t(generic.profile-types.external, capitalize)", 233 | "iTerm2External": "iTerm2: $t(generic.profile-types.external, capitalize)", 234 | "konsoleExternal": "Konsole: $t(generic.profile-types.external, capitalize)", 235 | "linuxExternalDefault": "$t(generic.platforms.linux, capitalize) $t(generic.default): $t(generic.profile-types.external, capitalize)", 236 | "linuxIntegratedDefault": "$t(generic.platforms.linux, capitalize) $t(generic.default): $t(generic.profile-types.integrated, capitalize)", 237 | "powershellExternal": "powershell: $t(generic.profile-types.external, capitalize)", 238 | "powershellIntegrated": "powershell: $t(generic.profile-types.integrated, capitalize)", 239 | "pwshExternal": "pwsh: $t(generic.profile-types.external, capitalize)", 240 | "pwshIntegrated": "pwsh: $t(generic.profile-types.integrated, capitalize)", 241 | "shIntegrated": "sh: $t(generic.profile-types.integrated, capitalize)", 242 | "terminalMacOSExternal": "$t(generic.terminal, capitalize) ($t(generic.platforms.darwin, capitalize)): $t(generic.profile-types.external, capitalize)", 243 | "win32ExternalDefault": "$t(generic.platforms.win32, capitalize) $t(generic.default): $t(generic.profile-types.external, capitalize)", 244 | "win32IntegratedDefault": "$t(generic.platforms.win32, capitalize) $t(generic.default): $t(generic.profile-types.integrated, capitalize)", 245 | "wslIntegrated": "$t(generic.platforms.win32-short, capitalize) Subsystem for $t(generic.platforms.linux, capitalize): $t(generic.profile-types.integrated, capitalize)", 246 | "wtExternal": "$t(generic.platforms.win32-short, capitalize) $t(generic.terminal, capitalize): $t(generic.profile-types.external, capitalize)", 247 | "xtermExternal": "xterm: $t(generic.profile-types.external, capitalize)", 248 | "zshIntegrated": "zsh: $t(generic.profile-types.integrated, capitalize)" 249 | }, 250 | "ribbons": { 251 | "open-terminal": "$t(generic.open, capitalize) $t(generic.terminal)" 252 | }, 253 | "settings": { 254 | "add-to-command": "Add to $t(generic.command)", 255 | "add-to-context-menu": "Add to $t(generic.context-menu)", 256 | "advanced": "Advanced", 257 | "create-instance-near-existing-ones": "Create $t(generic.instance) near $t(generic.exist_gerund) ones", 258 | "create-instance-near-existing-ones-description": "Overrides '$t(settings.new-instance-behavior)' when $t(generic.instance) $t(generic.exist_singular) if $t(generic.true).", 259 | "documentation": "$t(generic.documentation, capitalize)", 260 | "documentations": { 261 | "changelog": "$t(generic.documentations.changelog, capitalize)", 262 | "donate": "$t(generic.documentations.donate, capitalize)", 263 | "readme": "$t(generic.documentations.readme, capitalize)" 264 | }, 265 | "expose-internal-modules": "Expose internal modules", 266 | "expose-internal-modules-description-HTML": "obsidian, @codemirror/*, @lezer/*…", 267 | "focus-on-new-instance": "$t(generic.focus, capitalize) on new $t(generic.instance)", 268 | "hide-status-bar": "Hide $t(generic.status-bar)", 269 | "hide-status-bar-options": { 270 | "always": "Always", 271 | "focused": "When $t(generic.terminal) is $t(generic.focus_past)", 272 | "never": "Never", 273 | "running": "When $t(generic.terminal) is running" 274 | }, 275 | "instancing": "$t(generic.instance_gerund, capitalize)", 276 | "intercept-logging": "Intercept logging", 277 | "interface": "Interface", 278 | "new-instance-behavior": "New $t(generic.instance) $t(generic.behavior)", 279 | "new-instance-behaviors": { 280 | "newHorizontalSplit": "New horizontal $t(generic.split)", 281 | "newLeftSplit": "New left $t(generic.split)", 282 | "newLeftTab": "New left $t(generic.tab)", 283 | "newRightSplit": "New right $t(generic.split)", 284 | "newRightTab": "New right $t(generic.tab)", 285 | "newTab": "New $t(generic.tab)", 286 | "newVerticalSplit": "New vertical $t(generic.split)", 287 | "newWindow": "New $t(generic.window)", 288 | "replaceTab": "Replace $t(generic.tab)" 289 | }, 290 | "open-changelog-on-update": "$t(generic.open, capitalize) $t(generic.documentations.changelog) on update", 291 | "pin-new-instance": "$t(generic.pin, capitalize) new $t(generic.instance)", 292 | "preferred-renderer": "Preferred $t(generic.renderer)", 293 | "preferred-renderer-options": "$t(generic.renderers.{{type}}, capitalize)", 294 | "profile-list": { 295 | "description": "The first $t(generic.compatible) $t(generic.profile) in the $t(generic.list) is the $t(generic.default) for its $t(generic.terminal) $t(generic.type)." 296 | }, 297 | "profiles": "$t(generic.profile_other, capitalize)", 298 | "profiles-description": "$t(generic.list-description, capitalize)", 299 | "profiles-edit": "$t(generic.edit, capitalize)" 300 | } 301 | } -------------------------------------------------------------------------------- /assets/locales/ar/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "commands": { 3 | "open-developer-console": "$t(generic.open, capitalize) $t(generic.profile-types.developerConsole)", 4 | "open-documentation-changelog": "$t(generic.open, capitalize) $t(generic.documentations.changelog)", 5 | "open-documentation-donate": "$t(generic.open, capitalize) $t(generic.documentations.donate)", 6 | "open-documentation-readme": "$t(generic.open, capitalize) $t(generic.documentations.readme)", 7 | "open-terminal-": "$t(generic.open, capitalize) $t(generic.terminal): $t(generic.profile-types.{{type}}, capitalize)", 8 | "open-terminal-current": "$t(generic.open, capitalize) $t(generic.current-directory) in $t(generic.terminal): $t(generic.profile-types.{{type}}, capitalize)", 9 | "open-terminal-root": "$t(generic.open, capitalize) $t(generic.root-directory) in $t(generic.terminal): $t(generic.profile-types.{{type}}, capitalize)" 10 | }, 11 | "components": { 12 | "profile": { 13 | "data": "$t(generic.data, capitalize)", 14 | "data-edit": "$t(generic.edit, capitalize)", 15 | "external": { 16 | "arguments": "$t(generic.argument_other, capitalize)", 17 | "arguments-description": "$t(generic.list-description, capitalize)", 18 | "arguments-edit": "$t(generic.edit, capitalize)", 19 | "executable": "$t(generic.executable, capitalize)" 20 | }, 21 | "integrated": { 22 | "Python-executable": "$t(generic.Python, capitalize) $t(generic.executable)", 23 | "Python-executable-check": "$t(generic.check, capitalize)", 24 | "Python-executable-checking": "$t(generic.check_gerund, capitalize)", 25 | "Python-executable-description": "Recommend {{version}} or up. Required on $t(generic.platforms.unix) to $t(generic.spawn) $t(generic.profile-types.integrated) $t(generic.terminal). $t(generic.clear, capitalize) $t(generic.text-field) to $t(generic.disable) $t(generic.Python).", 26 | "Python-executable-placeholder": "($t(generic.disable_past, capitalize))", 27 | "arguments": "$t(generic.argument_other, capitalize)", 28 | "arguments-description": "$t(generic.list-description, capitalize)", 29 | "arguments-edit": "$t(generic.edit, capitalize)", 30 | "executable": "$t(generic.executable, capitalize)", 31 | "use-win32-conhost": "$t(generic.use, capitalize) $t(generic.platforms.win32) 'conhost.exe'", 32 | "use-win32-conhost-description": "$t(generic.disable, capitalize) if running 'conhost.exe' does not create a window. No guarantees this will work." 33 | }, 34 | "name": "$t(generic.name, capitalize)", 35 | "platform": "$t(generic.platforms.{{type}}, capitalize)", 36 | "platform-description-": "", 37 | "platform-description-current": "Current $t(generic.platform)", 38 | "preset": "$t(generic.preset, capitalize)", 39 | "preset-placeholder": "($t(generic.custom, capitalize))", 40 | "reset": "$t(generic.reset, capitalize)", 41 | "restore-history": "$t(generic.restore, capitalize) $t(generic.history)", 42 | "success-exit-codes": "Success $t(generic.exit) codes", 43 | "success-exit-codes-description": "$t(generic.list-description, capitalize)", 44 | "success-exit-codes-edit": "$t(generic.edit, capitalize)", 45 | "terminal-options": "$t(generic.terminal-option_other, capitalize)", 46 | "terminal-options-edit": "$t(generic.edit, capitalize)", 47 | "title": "{{name}}", 48 | "type": "$t(generic.type, capitalize)", 49 | "type-options": "$t(generic.profile-types.{{type}}, capitalize)" 50 | }, 51 | "profile-list": { 52 | "descriptor-": "{{info.id}}", 53 | "descriptor-incompatible": "($t(generic.incompatible, capitalize)) $t(components.profile-list.descriptor-)", 54 | "edit": "$t(generic.edit, capitalize)", 55 | "namer-": "{{info.name}}", 56 | "namer-incompatible": "$t(components.profile-list.namer-)", 57 | "preset-placeholder": "$t(components.dropdown.placeholder)", 58 | "title": "$t(generic.profile_other, capitalize)" 59 | }, 60 | "select-profile": { 61 | "item-text-": "$t(profile-name-formats.long)", 62 | "item-text-incompatible": "($t(generic.incompatible, capitalize)) $t(components.select-profile.item-text-)" 63 | }, 64 | "terminal": { 65 | "display-name": "$t(generic.terminal, capitalize): {{name}}", 66 | "edit-modal": { 67 | "profile": "$t(generic.profile, capitalize)", 68 | "profile-edit": "$t(generic.edit, capitalize)", 69 | "profile-name-": "$t(profile-name-formats.short)", 70 | "profile-name-incompatible": "($t(generic.incompatible, capitalize)) $t(components.terminal.edit-modal.profile-name-)", 71 | "profile-placeholder": "$t(components.profile.preset-placeholder)", 72 | "reset": "$t(generic.reset, capitalize)", 73 | "root-directory": "$t(generic.root-directory, capitalize)", 74 | "title": "$t(generic.edit, capitalize) $t(generic.terminal)", 75 | "working-directory": "$t(generic.working-directory, capitalize)", 76 | "working-directory-placeholder": "($t(generic.undefined, capitalize))" 77 | }, 78 | "menus": { 79 | "clear": "$t(generic.clear, capitalize)", 80 | "edit": "$t(generic.edit, capitalize)", 81 | "find": "$t(generic.find, capitalize)", 82 | "restart": "$t(generic.restart, capitalize)", 83 | "save-as-HTML": "$t(generic.save, capitalize) as $t(generic.file-extensions.HTML)" 84 | }, 85 | "name": { 86 | "profile-type": "$t(generic.profile-types.{{type}}, capitalize)" 87 | }, 88 | "restored-history": "\r\n * $t(generic.restore_past, capitalize) $t(generic.history) at {{datetime, datetime(dateStyle: full, timeStyle: full)}}\r\n\r\n", 89 | "unsupported-profile": "Unsupported $t(generic.profile):\r\n{{profile}}\r\n" 90 | }, 91 | "terminal-options": { 92 | "bold-font-weight": "Bold $t(generic.font-weight)", 93 | "description-HTML": "See ITerminalOptions for all $t(generic.option_other).", 94 | "font-family": "$t(generic.font, capitalize) family", 95 | "font-size": "$t(generic.font, capitalize) size", 96 | "font-weight": "$t(generic.font-weight, capitalize)", 97 | "invalid-description": "$t(generic.invalid, capitalize)", 98 | "title": "$t(generic.terminal-option_other, capitalize)", 99 | "undefine": "$t(generic.undefine, capitalize)", 100 | "undefined-placeholder": "($t(generic.undefined, capitalize))" 101 | } 102 | }, 103 | "errors": { 104 | "error-checking-Python": "Error $t(generic.check_gerund) $t(generic.Python)", 105 | "error-killing-pseudoterminal": "Error killing $t(generic.pseudoterminal)", 106 | "error-spawning-resizer": "Error $t(generic.spawn_gerund) $t(generic.terminal-resizer)", 107 | "error-spawning-terminal": "Error $t(generic.spawn_gerund) $t(generic.terminal)", 108 | "no-Python-to-spawn-Unix-pseudoterminal": "No $t(generic.Python) to $t(generic.spawn) $t(generic.platforms.unix) $t(generic.pseudoterminal)", 109 | "not-Python": "Not $t(generic.Python)", 110 | "resizer-disabled": "$t(generic.terminal-resizer, capitalize) $t(generic.disable_past)", 111 | "resizer-exited-unexpectedly": "$t(generic.terminal-resizer, capitalize) $t(generic.exit_past) unexpectedly: {{code}}" 112 | }, 113 | "generic": { 114 | "Python": "Python", 115 | "argument": "$t(generic.argument_one)", 116 | "argument_one": "argument", 117 | "argument_other": "arguments", 118 | "behavior": "behavior", 119 | "check": "check", 120 | "check_gerund": "checking", 121 | "clear": "clear", 122 | "compatible": "compatible", 123 | "context-menu": "context menu", 124 | "current-directory": "current $t(generic.directory)", 125 | "custom": "custom", 126 | "default": "default", 127 | "directory": "directory", 128 | "disable": "disable", 129 | "disable_past": "disabled", 130 | "documentation": "documentation", 131 | "documentations": { 132 | "changelog": "changelog", 133 | "donate": "$t(generic.donate)", 134 | "readme": "readme" 135 | }, 136 | "donate": "donate", 137 | "executable": "executable", 138 | "exist": "exist", 139 | "exist_singular": "exists", 140 | "exit": "exit", 141 | "exit_past": "exited", 142 | "file-extensions": { 143 | "HTML": "HTML" 144 | }, 145 | "focus": "focus", 146 | "focus_past": "focused", 147 | "font": "font", 148 | "font-weight": "$t(generic.font) weight", 149 | "history": "history", 150 | "incompatible": "incompatible", 151 | "instance": "instance", 152 | "instance_gerund": "instancing", 153 | "invalid": "invalid", 154 | "name": "name", 155 | "option": "option", 156 | "option_other": "options", 157 | "pin": "pin", 158 | "platform": "platform", 159 | "platforms": { 160 | "darwin": "\u200bmacOS", 161 | "linux": "Linux", 162 | "unix": "Unix", 163 | "win32": "Microsoft Windows", 164 | "win32-short": "Windows" 165 | }, 166 | "preset": "preset", 167 | "profile": "$t(generic.profile_one)", 168 | "profile-types": { 169 | "": "empty", 170 | "developerConsole": "developer console", 171 | "external": "external", 172 | "integrated": "integrated", 173 | "invalid": "$t(generic.invalid)", 174 | "select": "select" 175 | }, 176 | "profile_one": "profile", 177 | "profile_other": "profiles", 178 | "pseudoterminal": "pseudoterminal", 179 | "renderer": "renderer", 180 | "renderers": { 181 | "canvas": "canvas", 182 | "dom": "DOM", 183 | "webgl": "WebGL" 184 | }, 185 | "restart": "restart", 186 | "restore": "restore", 187 | "restore_past": "restored", 188 | "root-directory": "root $t(generic.directory)", 189 | "save": "save", 190 | "spawn": "spawn", 191 | "spawn_gerund": "spawning", 192 | "split": "split", 193 | "status-bar": "status bar", 194 | "tab": "tab", 195 | "terminal": "terminal", 196 | "terminal-option": "$t(generic.terminal) $t(generic.option)", 197 | "terminal-option_other": "$t(generic.terminal) $t(generic.option_other)", 198 | "terminal-resizer": "$t(generic.terminal) resizer", 199 | "text-field": "text field", 200 | "true": "true", 201 | "type": "type", 202 | "undefine": "undefine", 203 | "undefined": "undefined", 204 | "window": "window", 205 | "working-directory": "working $t(generic.directory)" 206 | }, 207 | "menus": { 208 | "open-terminal": "$t(generic.open, capitalize) in $t(generic.terminal): $t(generic.profile-types.{{type}}, capitalize)" 209 | }, 210 | "name": "$t(generic.terminal, capitalize)", 211 | "notices": { 212 | "Python-status-entry-": "{{name}}: {{version}} (satisfied: {{requirement}})", 213 | "Python-status-entry-unsatisfied": "{{name}}: {{version}} (unsatisfied: {{requirement}})", 214 | "no-default-profile": "No $t(generic.default) $t(generic.profile) for $t(generic.type) '$t(generic.profile-types.{{type}}, capitalize)'", 215 | "spawning-terminal": "$t(generic.spawn_gerund, capitalize) $t(generic.terminal): {{name}}", 216 | "terminal-exited": "$t(generic.terminal, capitalize) $t(generic.exit_past): {{code}}" 217 | }, 218 | "profile-name-formats": { 219 | "long": "\"{{info.name}}\". $t(generic.profile-types.{{info.profile.type}}, capitalize). {{info.id}}.", 220 | "short": "\"{{info.nameOrID}}\". $t(generic.profile-types.{{info.profile.type}}, capitalize)." 221 | }, 222 | "profile-presets": { 223 | "bashIntegrated": "bash: $t(generic.profile-types.integrated, capitalize)", 224 | "cmdExternal": "cmd: $t(generic.profile-types.external, capitalize)", 225 | "cmdIntegrated": "cmd: $t(generic.profile-types.integrated, capitalize)", 226 | "darwinExternalDefault": "$t(generic.platforms.darwin, capitalize) $t(generic.default): $t(generic.profile-types.external, capitalize)", 227 | "darwinIntegratedDefault": "$t(generic.platforms.darwin, capitalize) $t(generic.default): $t(generic.profile-types.integrated, capitalize)", 228 | "dashIntegrated": "dash: $t(generic.profile-types.integrated, capitalize)", 229 | "developerConsole": "$t(generic.profile-types.developerConsole, capitalize)", 230 | "empty": "$t(generic.profile-types., capitalize)", 231 | "gitBashIntegrated": "Git Bash: $t(generic.profile-types.integrated, capitalize)", 232 | "gnomeTerminalExternal": "GNOME $t(generic.terminal, capitalize): $t(generic.profile-types.external, capitalize)", 233 | "iTerm2External": "iTerm2: $t(generic.profile-types.external, capitalize)", 234 | "konsoleExternal": "Konsole: $t(generic.profile-types.external, capitalize)", 235 | "linuxExternalDefault": "$t(generic.platforms.linux, capitalize) $t(generic.default): $t(generic.profile-types.external, capitalize)", 236 | "linuxIntegratedDefault": "$t(generic.platforms.linux, capitalize) $t(generic.default): $t(generic.profile-types.integrated, capitalize)", 237 | "powershellExternal": "powershell: $t(generic.profile-types.external, capitalize)", 238 | "powershellIntegrated": "powershell: $t(generic.profile-types.integrated, capitalize)", 239 | "pwshExternal": "pwsh: $t(generic.profile-types.external, capitalize)", 240 | "pwshIntegrated": "pwsh: $t(generic.profile-types.integrated, capitalize)", 241 | "shIntegrated": "sh: $t(generic.profile-types.integrated, capitalize)", 242 | "terminalMacOSExternal": "$t(generic.terminal, capitalize) ($t(generic.platforms.darwin, capitalize)): $t(generic.profile-types.external, capitalize)", 243 | "win32ExternalDefault": "$t(generic.platforms.win32, capitalize) $t(generic.default): $t(generic.profile-types.external, capitalize)", 244 | "win32IntegratedDefault": "$t(generic.platforms.win32, capitalize) $t(generic.default): $t(generic.profile-types.integrated, capitalize)", 245 | "wslIntegrated": "$t(generic.platforms.win32-short, capitalize) Subsystem for $t(generic.platforms.linux, capitalize): $t(generic.profile-types.integrated, capitalize)", 246 | "wtExternal": "$t(generic.platforms.win32-short, capitalize) $t(generic.terminal, capitalize): $t(generic.profile-types.external, capitalize)", 247 | "xtermExternal": "xterm: $t(generic.profile-types.external, capitalize)", 248 | "zshIntegrated": "zsh: $t(generic.profile-types.integrated, capitalize)" 249 | }, 250 | "ribbons": { 251 | "open-terminal": "$t(generic.open, capitalize) $t(generic.terminal)" 252 | }, 253 | "settings": { 254 | "add-to-command": "Add to $t(generic.command)", 255 | "add-to-context-menu": "Add to $t(generic.context-menu)", 256 | "advanced": "Advanced", 257 | "create-instance-near-existing-ones": "Create $t(generic.instance) near $t(generic.exist_gerund) ones", 258 | "create-instance-near-existing-ones-description": "Overrides '$t(settings.new-instance-behavior)' when $t(generic.instance) $t(generic.exist_singular) if $t(generic.true).", 259 | "documentation": "$t(generic.documentation, capitalize)", 260 | "documentations": { 261 | "changelog": "$t(generic.documentations.changelog, capitalize)", 262 | "donate": "$t(generic.documentations.donate, capitalize)", 263 | "readme": "$t(generic.documentations.readme, capitalize)" 264 | }, 265 | "expose-internal-modules": "Expose internal modules", 266 | "expose-internal-modules-description-HTML": "obsidian, @codemirror/*, @lezer/*…", 267 | "focus-on-new-instance": "$t(generic.focus, capitalize) on new $t(generic.instance)", 268 | "hide-status-bar": "Hide $t(generic.status-bar)", 269 | "hide-status-bar-options": { 270 | "always": "Always", 271 | "focused": "When $t(generic.terminal) is $t(generic.focus_past)", 272 | "never": "Never", 273 | "running": "When $t(generic.terminal) is running" 274 | }, 275 | "instancing": "$t(generic.instance_gerund, capitalize)", 276 | "intercept-logging": "Intercept logging", 277 | "interface": "Interface", 278 | "new-instance-behavior": "New $t(generic.instance) $t(generic.behavior)", 279 | "new-instance-behaviors": { 280 | "newHorizontalSplit": "New horizontal $t(generic.split)", 281 | "newLeftSplit": "New left $t(generic.split)", 282 | "newLeftTab": "New left $t(generic.tab)", 283 | "newRightSplit": "New right $t(generic.split)", 284 | "newRightTab": "New right $t(generic.tab)", 285 | "newTab": "New $t(generic.tab)", 286 | "newVerticalSplit": "New vertical $t(generic.split)", 287 | "newWindow": "New $t(generic.window)", 288 | "replaceTab": "Replace $t(generic.tab)" 289 | }, 290 | "open-changelog-on-update": "$t(generic.open, capitalize) $t(generic.documentations.changelog) on update", 291 | "pin-new-instance": "$t(generic.pin, capitalize) new $t(generic.instance)", 292 | "preferred-renderer": "Preferred $t(generic.renderer)", 293 | "preferred-renderer-options": "$t(generic.renderers.{{type}}, capitalize)", 294 | "profile-list": { 295 | "description": "The first $t(generic.compatible) $t(generic.profile) in the $t(generic.list) is the $t(generic.default) for its $t(generic.terminal) $t(generic.type)." 296 | }, 297 | "profiles": "$t(generic.profile_other, capitalize)", 298 | "profiles-description": "$t(generic.list-description, capitalize)", 299 | "profiles-edit": "$t(generic.edit, capitalize)" 300 | } 301 | } -------------------------------------------------------------------------------- /assets/locales/en/asset.json: -------------------------------------------------------------------------------- 1 | { 2 | "commands": { 3 | "open-developer-console-icon": "$t(asset:generic.profile-types.developerConsole-icon)", 4 | "open-documentation-changelog-icon": "$t(asset:generic.documentations.changelog-icon)", 5 | "open-documentation-donate-icon": "$t(asset:generic.documentations.donate-icon)", 6 | "open-documentation-readme-icon": "$t(asset:generic.documentations.readme-icon)", 7 | "open-terminal--icon": "command", 8 | "open-terminal-current-icon": "$t(asset:generic.terminal-file-icon)", 9 | "open-terminal-root-icon": "$t(asset:generic.terminal-icon)" 10 | }, 11 | "components": { 12 | "profile": { 13 | "data-icon": "$t(asset:generic.data-icon)", 14 | "external": { 15 | "arguments-edit-icon": "$t(asset:generic.edit-list-icon)", 16 | "arguments-icon": "$t(asset:components.profile.generic.arguments-icon)", 17 | "executable-icon": "$t(asset:components.profile.generic.executable-icon)" 18 | }, 19 | "generic": { 20 | "arguments-icon": "form-input", 21 | "executable-icon": "$t(asset:generic.terminal-file-icon)" 22 | }, 23 | "integrated": { 24 | "Python-executable-check-icon": "file-check", 25 | "Python-executable-checking-icon": "$t(asset:generic.load-icon)", 26 | "Python-executable-icon": "file-cog", 27 | "arguments-edit-icon": "$t(asset:generic.edit-list-icon)", 28 | "arguments-icon": "$t(asset:components.profile.generic.arguments-icon)", 29 | "executable-icon": "$t(asset:components.profile.generic.executable-icon)", 30 | "use-win32-conhost-icon": "code" 31 | }, 32 | "name-icon": "text-cursor-input", 33 | "platform-icon": "$t(asset:generic.platforms.{{type}}-icon)", 34 | "preset-icon": "$t(asset:generic.profile-icon)", 35 | "restore-history-icon": "history", 36 | "success-exit-codes-edit-icon": "$t(asset:generic.edit-list-icon)", 37 | "success-exit-codes-icon": "log-out", 38 | "terminal-options-edit-icon": "$t(asset:generic.edit-icon)", 39 | "terminal-options-icon": "$t(asset:generic.terminal-icon)", 40 | "type-icon": "type" 41 | }, 42 | "profile-list": { 43 | "edit-icon": "$t(asset:generic.edit-icon)" 44 | }, 45 | "terminal": { 46 | "edit-modal": { 47 | "profile-edit-icon": "$t(asset:generic.edit-icon)", 48 | "profile-icon": "$t(asset:generic.profile-icon)", 49 | "root-directory-icon": "$t(asset:generic.root-directory-icon)", 50 | "working-directory-icon": "folder" 51 | }, 52 | "icon": "$t(asset:generic.terminal-icon)", 53 | "menus": { 54 | "clear-icon": "eraser", 55 | "edit-icon": "$t(asset:generic.edit-icon)", 56 | "find-icon": "search", 57 | "restart-icon": "reset", 58 | "save-as-HTML-icon": "save" 59 | } 60 | }, 61 | "terminal-options": { 62 | "bold-font-weight-icon": "$t(asset:components.terminal-options.font-weight-icon)", 63 | "font-family-icon": "type", 64 | "font-size-icon": "scaling", 65 | "font-weight-icon": "dumbbell", 66 | "undefine-icon": "x" 67 | } 68 | }, 69 | "generic": { 70 | "documentations": { 71 | "changelog-icon": "file-text", 72 | "donate-icon": "heart", 73 | "readme-icon": "file-question" 74 | }, 75 | "platforms": { 76 | "darwin-icon": "terminal:macos", 77 | "linux-icon": "terminal:linux", 78 | "win32-icon": "grid-2x2" 79 | }, 80 | "profile-icon": "$t(asset:generic.terminal-alt-icon)", 81 | "profile-types": { 82 | "-icon": "square", 83 | "developerConsole-icon": "curly-braces", 84 | "external-icon": "$t(asset:generic.terminal-alt-icon)", 85 | "integrated-icon": "$t(asset:generic.terminal-icon)", 86 | "invalid-icon": "x-circle", 87 | "select-icon": "text-cursor-input" 88 | }, 89 | "root-directory-icon": "folder-tree", 90 | "terminal-alt-icon": "terminal-square", 91 | "terminal-file-icon": "file-terminal", 92 | "terminal-icon": "terminal" 93 | }, 94 | "menus": { 95 | "open-terminal-icon": "$t(asset:generic.profile-types.{{type}}-icon)" 96 | }, 97 | "ribbons": { 98 | "open-terminal-icon": "$t(asset:generic.terminal-alt-icon)", 99 | "open-terminal-id": "Open terminal" 100 | }, 101 | "settings": { 102 | "add-to-command-icon": "$t(asset:generic.terminal-icon)", 103 | "add-to-context-menu-icon": "menu", 104 | "create-instance-near-existing-ones-icon": "plus", 105 | "documentations": { 106 | "changelog-icon": "$t(asset:generic.documentations.changelog-icon)", 107 | "donate-icon": "$t(asset:generic.documentations.donate-icon)", 108 | "readme-icon": "$t(asset:generic.documentations.readme-icon)" 109 | }, 110 | "expose-internal-modules-icon": "puzzle", 111 | "focus-on-new-instance-icon": "focus", 112 | "hide-status-bar-icon": "eye-off", 113 | "intercept-logging-icon": "scroll-text", 114 | "new-instance-behavior-icon": "plus", 115 | "open-changelog-on-update-icon": "$t(asset:generic.documentations.changelog-icon)", 116 | "pin-new-instance-icon": "pin", 117 | "preferred-renderer-icon": "brush", 118 | "profiles-edit-icon": "$t(asset:generic.edit-list-icon)", 119 | "profiles-icon": "$t(asset:generic.profile-icon)" 120 | } 121 | } -------------------------------------------------------------------------------- /assets/locales/en/language.json: -------------------------------------------------------------------------------- 1 | { 2 | "af": "Afrikaans 🚧", 3 | "am": "አማርኛ 🚧", 4 | "ar": "العربية 🚧", 5 | "be": "беларуская мова 🚧", 6 | "bg": "български език 🚧", 7 | "bn": "বাংলা 🚧", 8 | "ca": "català 🚧", 9 | "cs": "čeština 🚧", 10 | "da": "Dansk 🚧", 11 | "de": "Deutsch 🚧", 12 | "el": "Ελληνικά 🚧", 13 | "en": "English", 14 | "eo": "Esperanto 🚧", 15 | "es": "Español 🚧", 16 | "eu": "Euskara 🚧", 17 | "fa": "فارسی 🚧", 18 | "fi": "suomi 🚧", 19 | "fr": "français 🚧", 20 | "gl": "Galego 🚧", 21 | "he": "עברית 🚧", 22 | "hi": "हिन्दी 🚧", 23 | "hu": "Magyar nyelv 🚧", 24 | "id": "Bahasa Indonesia 🚧", 25 | "it": "Italiano 🚧", 26 | "ja": "日本語 🚧", 27 | "ko": "한국어 🚧", 28 | "lv": "Latviešu valoda 🚧", 29 | "ml": "മലയാളം 🚧", 30 | "ms": "Bahasa Melayu 🚧", 31 | "nl": "Nederlands 🚧", 32 | "no": "Norsk 🚧", 33 | "oc": "Occitan 🚧", 34 | "pl": "język polski 🚧", 35 | "pt": "Português 🚧", 36 | "pt-BR": "Portugues do Brasil 🚧", 37 | "ro": "Română 🚧", 38 | "ru": "Русский 🚧", 39 | "se": "Svenska 🚧", 40 | "sk": "Slovenčina 🚧", 41 | "sq": "Shqip 🚧", 42 | "sr": "српски језик 🚧", 43 | "ta": "தமிழ் 🚧", 44 | "te": "తెలుగు 🚧", 45 | "th": "ไทย 🚧", 46 | "tr": "Türkçe 🚧", 47 | "uk": "Українська 🚧", 48 | "ur": "اردو 🚧", 49 | "zh-Hans": "简体中文", 50 | "zh-Hant": "繁體中文" 51 | } -------------------------------------------------------------------------------- /assets/locales/zh-Hans/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "commands": { 3 | "open-developer-console": "$t(generic.open)$t(generic.profile-types.developerConsole)", 4 | "open-documentation-changelog": "$t(generic.open)$t(generic.documentations.changelog)", 5 | "open-documentation-donate": "$t(generic.open)$t(generic.documentations.donate)", 6 | "open-documentation-readme": "$t(generic.open)$t(generic.documentations.readme)", 7 | "open-terminal-": "$t(generic.open)$t(generic.terminal):$t(generic.profile-types.{{type}})", 8 | "open-terminal-current": "在$t(generic.terminal)$t(generic.open)$t(generic.current-directory):$t(generic.profile-types.{{type}})", 9 | "open-terminal-root": "在$t(generic.terminal)$t(generic.open)$t(generic.root-directory):$t(generic.profile-types.{{type}})" 10 | }, 11 | "components": { 12 | "profile": { 13 | "data": "$t(generic.data)", 14 | "data-edit": "$t(generic.edit)", 15 | "external": { 16 | "arguments": "$t(generic.argument)", 17 | "arguments-description": "$t(generic.list-description)", 18 | "arguments-edit": "$t(generic.edit)", 19 | "executable": "$t(generic.executable)" 20 | }, 21 | "integrated": { 22 | "Python-executable": "$t(generic.Python)$t(generic.executable)", 23 | "Python-executable-check": "$t(generic.check)", 24 | "Python-executable-checking": "$t(generic.check)中", 25 | "Python-executable-description": "推荐{{version}}或更高版本。于$t(generic.platforms.unix)$t(generic.spawn)$t(generic.profile-types.integrated)$t(generic.terminal)所需要的。$t(generic.clear)$t(generic.text-field)以$t(generic.disable)$t(generic.Python)。", 26 | "Python-executable-placeholder": "(已$t(generic.disable))", 27 | "arguments": "$t(generic.argument)", 28 | "arguments-description": "$t(generic.list-description)", 29 | "arguments-edit": "$t(generic.edit)", 30 | "executable": "$t(generic.executable)", 31 | "use-win32-conhost": "$t(generic.use)$t(generic.platforms.win32)「conhost.exe」", 32 | "use-win32-conhost-description": "如果执行「conhost.exe」没有创建视窗,请$t(generic.disable)。无保证这会起作用。" 33 | }, 34 | "name": "$t(generic.name)", 35 | "platform": "$t(generic.platforms.{{type}})", 36 | "platform-description-": "", 37 | "platform-description-current": "目前$t(generic.platform)", 38 | "preset": "$t(generic.preset)", 39 | "preset-placeholder": "($t(generic.custom))", 40 | "reset": "$t(generic.reset)", 41 | "restore-history": "$t(generic.restore)$t(generic.history)", 42 | "success-exit-codes": "成功$t(generic.exit)代码", 43 | "success-exit-codes-description": "$t(generic.list-description)", 44 | "success-exit-codes-edit": "$t(generic.edit)", 45 | "terminal-options": "$t(generic.terminal-option)", 46 | "terminal-options-edit": "$t(generic.edit)", 47 | "title": "{{name}}", 48 | "type": "$t(generic.type)", 49 | "type-options": "$t(generic.profile-types.{{type}})" 50 | }, 51 | "profile-list": { 52 | "descriptor-": "{{info.id}}", 53 | "descriptor-incompatible": "($t(generic.incompatible))$t(components.profile-list.descriptor-)", 54 | "edit": "$t(generic.edit)", 55 | "namer-": "{{info.name}}", 56 | "namer-incompatible": "$t(components.profile-list.namer-)", 57 | "preset-placeholder": "$t(components.dropdown.placeholder)", 58 | "title": "$t(generic.profile)" 59 | }, 60 | "select-profile": { 61 | "item-text-": "$t(profile-name-formats.long)", 62 | "item-text-incompatible": "($t(generic.incompatible))$t(components.select-profile.item-text-)" 63 | }, 64 | "terminal": { 65 | "display-name": "$t(generic.terminal):{{name}}", 66 | "edit-modal": { 67 | "profile": "$t(generic.profile)", 68 | "profile-edit": "$t(generic.edit)", 69 | "profile-name-": "$t(profile-name-formats.short)", 70 | "profile-name-incompatible": "($t(generic.incompatible))$t(components.terminal.edit-modal.profile-name-)", 71 | "profile-placeholder": "$t(components.profile.preset-placeholder)", 72 | "reset": "$t(generic.reset)", 73 | "root-directory": "$t(generic.root-directory)", 74 | "title": "$t(generic.edit)$t(generic.terminal)", 75 | "working-directory": "$t(generic.working-directory)", 76 | "working-directory-placeholder": "($t(generic.undefined))" 77 | }, 78 | "menus": { 79 | "clear": "$t(generic.clear)", 80 | "edit": "$t(generic.edit)", 81 | "find": "$t(generic.find)", 82 | "restart": "$t(generic.restart)", 83 | "save-as-HTML": "$t(generic.save)为$t(generic.file-extensions.HTML)" 84 | }, 85 | "name": { 86 | "profile-type": "$t(generic.profile-types.{{type}})" 87 | }, 88 | "restored-history": "\r\n * 已$t(generic.restore)于{{datetime, datetime(dateStyle: full, timeStyle: full)}}的$t(generic.history)\r\n\r\n", 89 | "unsupported-profile": "不支持的$t(generic.profile):\r\n{{profile}}\r\n" 90 | }, 91 | "terminal-options": { 92 | "bold-font-weight": "粗体$t(generic.font-weight)", 93 | "description-HTML": "请参阅ITerminalOptions了解所有$t(generic.option)。", 94 | "font-family": "$t(generic.font)系列", 95 | "font-size": "$t(generic.font)大小", 96 | "font-weight": "$t(generic.font-weight)", 97 | "invalid-description": "$t(generic.invalid)", 98 | "title": "$t(generic.terminal-option)", 99 | "undefine": "$t(generic.undefine)", 100 | "undefined-placeholder": "($t(generic.undefined))" 101 | } 102 | }, 103 | "errors": { 104 | "error-checking-Python": "$t(generic.check)$t(generic.Python)时出错", 105 | "error-killing-pseudoterminal": "终止$t(generic.pseudoterminal)时出错", 106 | "error-spawning-resizer": "$t(generic.spawn)$t(generic.terminal-resizer)时出错", 107 | "error-spawning-terminal": "$t(generic.spawn)$t(generic.terminal)时出错", 108 | "no-Python-to-spawn-Unix-pseudoterminal": "没有$t(generic.Python)来$t(generic.spawn)$t(generic.platforms.unix)$t(generic.pseudoterminal)", 109 | "not-Python": "不是$t(generic.Python)", 110 | "resizer-disabled": "$t(generic.terminal-resizer)已$t(generic.disable)", 111 | "resizer-exited-unexpectedly": "$t(generic.terminal-resizer)意外$t(generic.exit):{{code}}" 112 | }, 113 | "generic": { 114 | "Python": "Python", 115 | "argument": "参数", 116 | "behavior": "行为", 117 | "check": "检查", 118 | "clear": "清除", 119 | "compatible": "兼容", 120 | "context-menu": "右键选单", 121 | "current-directory": "目前$t(generic.directory)", 122 | "custom": "自定义", 123 | "default": "默认", 124 | "directory": "目录", 125 | "disable": "禁用", 126 | "documentation": "文档", 127 | "documentations": { 128 | "changelog": "更改日志", 129 | "donate": "$t(generic.donate)", 130 | "readme": "自述文件" 131 | }, 132 | "donate": "捐赠", 133 | "executable": "可执行文件", 134 | "exist": "存在", 135 | "exit": "退出", 136 | "file-extensions": { 137 | "HTML": "超文本标记语言" 138 | }, 139 | "focus": "聚焦", 140 | "font": "字体", 141 | "font-weight": "$t(generic.font)粗细", 142 | "history": "历史", 143 | "incompatible": "不$t(generic.compatible)", 144 | "instance": "个体", 145 | "invalid": "无效", 146 | "name": "名称", 147 | "option": "选项", 148 | "pin": "锁定", 149 | "platform": "平台", 150 | "platforms": { 151 | "darwin": "\u200bmacOS", 152 | "linux": "Linux", 153 | "unix": "Unix", 154 | "win32": "Microsoft Windows", 155 | "win32-short": "Windows" 156 | }, 157 | "preset": "预设", 158 | "profile": "配置", 159 | "profile-types": { 160 | "": "空白", 161 | "developerConsole": "开发者控制台", 162 | "external": "外部", 163 | "integrated": "整合式", 164 | "invalid": "$t(generic.invalid)", 165 | "select": "选择" 166 | }, 167 | "pseudoterminal": "伪终端", 168 | "renderer": "渲染器", 169 | "renderers": { 170 | "canvas": "画布", 171 | "dom": "文件物件模型", 172 | "webgl": "WebGL" 173 | }, 174 | "restart": "重启", 175 | "restore": "还原", 176 | "root-directory": "根$t(generic.directory)", 177 | "save": "储存", 178 | "spawn": "啟动", 179 | "split": "分屏", 180 | "status-bar": "状态栏", 181 | "tab": "标签页", 182 | "terminal": "终端", 183 | "terminal-option": "$t(generic.terminal)$t(generic.option)", 184 | "terminal-resizer": "$t(generic.terminal)缩放迸程", 185 | "text-field": "文本框", 186 | "true": "真", 187 | "type": "类型", 188 | "undefine": "取消定义", 189 | "undefined": "未定义", 190 | "window": "窗口", 191 | "working-directory": "工作$t(generic.directory)" 192 | }, 193 | "menus": { 194 | "open-terminal": "在$t(generic.terminal)$t(generic.open):$t(generic.profile-types.{{type}})" 195 | }, 196 | "name": "$t(generic.terminal)", 197 | "notices": { 198 | "Python-status-entry-": "{{name}}:{{version}}(满足:{{requirement}})", 199 | "Python-status-entry-unsatisfied": "{{name}}:{{version}}(未满足:{{requirement}})", 200 | "no-default-profile": "没有$t(generic.type)「$t(generic.profile-types.{{type}})」的$t(generic.default)$t(generic.profile)", 201 | "spawning-terminal": "$t(generic.terminal)$t(generic.spawn)中:{{name}}", 202 | "terminal-exited": "$t(generic.terminal)已$t(generic.exit):{{code}}" 203 | }, 204 | "profile-name-formats": { 205 | "long": "\"{{info.name}}\". $t(generic.profile-types.{{info.profile.type}}). {{info.id}}.", 206 | "short": "\"{{info.nameOrID}}\". $t(generic.profile-types.{{info.profile.type}})." 207 | }, 208 | "profile-presets": { 209 | "bashIntegrated": "bash:$t(generic.profile-types.integrated)", 210 | "cmdExternal": "cmd:$t(generic.profile-types.external)", 211 | "cmdIntegrated": "cmd:$t(generic.profile-types.integrated)", 212 | "darwinExternalDefault": "$t(generic.platforms.darwin)$t(generic.default):$t(generic.profile-types.external)", 213 | "darwinIntegratedDefault": "$t(generic.platforms.darwin)$t(generic.default):$t(generic.profile-types.integrated)", 214 | "dashIntegrated": "dash:$t(generic.profile-types.integrated)", 215 | "developerConsole": "$t(generic.profile-types.developerConsole)", 216 | "empty": "$t(generic.profile-types.)", 217 | "gitBashIntegrated": "Git Bash:$t(generic.profile-types.integrated)", 218 | "gnomeTerminalExternal": "GNOME$t(generic.terminal):$t(generic.profile-types.external)", 219 | "iTerm2External": "iTerm2:$t(generic.profile-types.external, capitalize)", 220 | "konsoleExternal": "Konsole:$t(generic.profile-types.external)", 221 | "linuxExternalDefault": "$t(generic.platforms.linux)$t(generic.default):$t(generic.profile-types.external)", 222 | "linuxIntegratedDefault": "$t(generic.platforms.linux)$t(generic.default):$t(generic.profile-types.integrated)", 223 | "powershellExternal": "powershell:$t(generic.profile-types.external)", 224 | "powershellIntegrated": "powershell:$t(generic.profile-types.integrated)", 225 | "pwshExternal": "pwsh:$t(generic.profile-types.external)", 226 | "pwshIntegrated": "pwsh:$t(generic.profile-types.integrated)", 227 | "shIntegrated": "sh:$t(generic.profile-types.integrated)", 228 | "terminalMacOSExternal": "$t(generic.terminal)($t(generic.platforms.darwin)):$t(generic.profile-types.external)", 229 | "win32ExternalDefault": "$t(generic.platforms.win32)$t(generic.default):$t(generic.profile-types.external)", 230 | "win32IntegratedDefault": "$t(generic.platforms.win32)$t(generic.default):$t(generic.profile-types.integrated)", 231 | "wslIntegrated": "适用于$t(generic.platforms.linux)的$t(generic.platforms.win32-short)子系统:$t(generic.profile-types.integrated)", 232 | "wtExternal": "$t(generic.platforms.win32-short)$t(generic.terminal):$t(generic.profile-types.external)", 233 | "xtermExternal": "xterm:$t(generic.profile-types.external)", 234 | "zshIntegrated": "zsh:$t(generic.profile-types.integrated)" 235 | }, 236 | "ribbons": { 237 | "open-terminal": "$t(generic.open)$t(generic.terminal)" 238 | }, 239 | "settings": { 240 | "add-to-command": "加入至$t(generic.command)", 241 | "add-to-context-menu": "加入至$t(generic.context-menu)", 242 | "advanced": "高级", 243 | "create-instance-near-existing-ones": "在$t(generic.exist)$t(generic.instance)附近创建$t(generic.instance)", 244 | "create-instance-near-existing-ones-description": "如果为$t(generic.true),则在$t(generic.instance)$t(generic.exist)时覆盖「$t(settings.new-instance-behavior)」。", 245 | "documentation": "$t(generic.documentation)", 246 | "documentations": { 247 | "changelog": "$t(generic.documentations.changelog)", 248 | "donate": "$t(generic.documentations.donate)", 249 | "readme": "$t(generic.documentations.readme)" 250 | }, 251 | "expose-internal-modules": "公开内部模块", 252 | "expose-internal-modules-description-HTML": "obsidian@codemirror/*@lezer/*……", 253 | "focus-on-new-instance": "$t(generic.focus)新$t(generic.instance)", 254 | "hide-status-bar": "隐藏$t(generic.status-bar)", 255 | "hide-status-bar-options": { 256 | "always": "总是", 257 | "focused": "当$t(generic.focus)$t(generic.terminal)时", 258 | "never": "从不", 259 | "running": "当执行$t(generic.terminal)时" 260 | }, 261 | "instancing": "$t(generic.instance)", 262 | "intercept-logging": "拦截记录", 263 | "interface": "界面", 264 | "new-instance-behavior": "新$t(generic.instance)$t(generic.behavior)", 265 | "new-instance-behaviors": { 266 | "newHorizontalSplit": "新水平$t(generic.split)", 267 | "newLeftSplit": "新左侧$t(generic.split)", 268 | "newLeftTab": "新左侧$t(generic.tab)", 269 | "newRightSplit": "新右侧$t(generic.split)", 270 | "newRightTab": "新右侧$t(generic.tab)", 271 | "newTab": "新$t(generic.tab)", 272 | "newVerticalSplit": "新垂直$t(generic.split)", 273 | "newWindow": "新$t(generic.window)", 274 | "replaceTab": "替换$t(generic.tab)" 275 | }, 276 | "open-changelog-on-update": "更新时$t(generic.open)$t(generic.documentations.changelog)", 277 | "pin-new-instance": "$t(generic.pin)新$t(generic.instance)", 278 | "preferred-renderer": "首选$t(generic.renderer)", 279 | "preferred-renderer-options": "$t(generic.renderers.{{type}})", 280 | "profile-list": { 281 | "description": "$t(generic.list)中第一个$t(generic.compatible)的$t(generic.profile)是其$t(generic.terminal)$t(generic.type)的$t(generic.default)。" 282 | }, 283 | "profiles": "$t(generic.profile)", 284 | "profiles-description": "$t(generic.list-description)", 285 | "profiles-edit": "$t(generic.edit)" 286 | } 287 | } -------------------------------------------------------------------------------- /assets/locales/zh-Hant/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "commands": { 3 | "open-developer-console": "$t(generic.open)$t(generic.profile-types.developerConsole)", 4 | "open-documentation-changelog": "$t(generic.open)$t(generic.documentations.changelog)", 5 | "open-documentation-donate": "$t(generic.open)$t(generic.documentations.donate)", 6 | "open-documentation-readme": "$t(generic.open)$t(generic.documentations.readme)", 7 | "open-terminal-": "$t(generic.open)$t(generic.terminal):$t(generic.profile-types.{{type}})", 8 | "open-terminal-current": "在$t(generic.terminal)$t(generic.open)$t(generic.current-directory):$t(generic.profile-types.{{type}})", 9 | "open-terminal-root": "在$t(generic.terminal)$t(generic.open)$t(generic.root-directory):$t(generic.profile-types.{{type}})" 10 | }, 11 | "components": { 12 | "profile": { 13 | "data": "$t(generic.data)", 14 | "data-edit": "$t(generic.edit)", 15 | "external": { 16 | "arguments": "$t(generic.argument)", 17 | "arguments-description": "$t(generic.list-description)", 18 | "arguments-edit": "$t(generic.edit)", 19 | "executable": "$t(generic.executable)" 20 | }, 21 | "integrated": { 22 | "Python-executable": "$t(generic.Python)$t(generic.executable)", 23 | "Python-executable-check": "$t(generic.check)", 24 | "Python-executable-checking": "$t(generic.check)中", 25 | "Python-executable-description": "推薦{{version}}或更高版本。於$t(generic.platforms.unix)$t(generic.spawn)$t(generic.profile-types.integrated)$t(generic.terminal)所需要的。$t(generic.clear)$t(generic.text-field)以$t(generic.disable)$t(generic.Python)。", 26 | "Python-executable-placeholder": "(已$t(generic.disable))", 27 | "arguments": "$t(generic.argument)", 28 | "arguments-description": "$t(generic.list-description)", 29 | "arguments-edit": "$t(generic.edit)", 30 | "executable": "$t(generic.executable)", 31 | "use-win32-conhost": "$t(generic.use)$t(generic.platforms.win32)「conhost.exe」", 32 | "use-win32-conhost-description": "如果執行「conhost.exe」沒有創建視窗,請$t(generic.disable)。無保證這會起作用。" 33 | }, 34 | "name": "$t(generic.name)", 35 | "platform": "$t(generic.platforms.{{type}})", 36 | "platform-description-": "", 37 | "platform-description-current": "目前$t(generic.platform)", 38 | "preset": "$t(generic.preset)", 39 | "preset-placeholder": "($t(generic.custom))", 40 | "reset": "$t(generic.reset)", 41 | "restore-history": "$t(generic.restore)$t(generic.history)", 42 | "success-exit-codes": "成功$t(generic.exit)代碼", 43 | "success-exit-codes-description": "$t(generic.list-description)", 44 | "success-exit-codes-edit": "$t(generic.edit)", 45 | "terminal-options": "$t(generic.terminal-option)", 46 | "terminal-options-edit": "$t(generic.edit)", 47 | "title": "{{name}}", 48 | "type": "$t(generic.type)", 49 | "type-options": "$t(generic.profile-types.{{type}})" 50 | }, 51 | "profile-list": { 52 | "descriptor-": "{{info.id}}", 53 | "descriptor-incompatible": "($t(generic.incompatible))$t(components.profile-list.descriptor-)", 54 | "edit": "$t(generic.edit)", 55 | "namer-": "{{info.name}}", 56 | "namer-incompatible": "$t(components.profile-list.namer-)", 57 | "preset-placeholder": "$t(components.dropdown.placeholder)", 58 | "title": "$t(generic.profile)" 59 | }, 60 | "select-profile": { 61 | "item-text-": "$t(profile-name-formats.long)", 62 | "item-text-incompatible": "($t(generic.incompatible))$t(components.select-profile.item-text-)" 63 | }, 64 | "terminal": { 65 | "display-name": "$t(generic.terminal):{{name}}", 66 | "edit-modal": { 67 | "profile": "$t(generic.profile)", 68 | "profile-edit": "$t(generic.edit)", 69 | "profile-name-": "$t(profile-name-formats.short)", 70 | "profile-name-incompatible": "($t(generic.incompatible))$t(components.terminal.edit-modal.profile-name-)", 71 | "profile-placeholder": "$t(components.profile.preset-placeholder)", 72 | "reset": "$t(generic.reset)", 73 | "root-directory": "$t(generic.root-directory)", 74 | "title": "$t(generic.edit)$t(generic.terminal)", 75 | "working-directory": "$t(generic.working-directory)", 76 | "working-directory-placeholder": "($t(generic.undefined))" 77 | }, 78 | "menus": { 79 | "clear": "$t(generic.clear)", 80 | "edit": "$t(generic.edit)", 81 | "find": "$t(generic.find)", 82 | "restart": "$t(generic.restart)", 83 | "save-as-HTML": "$t(generic.save)為$t(generic.file-extensions.HTML)" 84 | }, 85 | "name": { 86 | "profile-type": "$t(generic.profile-types.{{type}})" 87 | }, 88 | "restored-history": "\r\n * 已$t(generic.restore)於{{datetime, datetime(dateStyle: full, timeStyle: full)}}的$t(generic.history)\r\n\r\n", 89 | "unsupported-profile": "不支持的$t(generic.profile):\r\n{{profile}}\r\n" 90 | }, 91 | "terminal-options": { 92 | "bold-font-weight": "粗體$t(generic.font-weight)", 93 | "description-HTML": "請參閱ITerminalOptions了解所有$t(generic.option)。", 94 | "font-family": "$t(generic.font)系列", 95 | "font-size": "$t(generic.font)大小", 96 | "font-weight": "$t(generic.font-weight)", 97 | "invalid-description": "$t(generic.invalid)", 98 | "title": "$t(generic.terminal-option)", 99 | "undefine": "$t(generic.undefine)", 100 | "undefined-placeholder": "($t(generic.undefined))" 101 | } 102 | }, 103 | "errors": { 104 | "error-checking-Python": "$t(generic.check)$t(generic.Python)時出錯", 105 | "error-killing-pseudoterminal": "終止$t(generic.pseudoterminal)時出錯", 106 | "error-spawning-resizer": "$t(generic.spawn)$t(generic.terminal-resizer)時出錯", 107 | "error-spawning-terminal": "$t(generic.spawn)$t(generic.terminal)時出錯", 108 | "no-Python-to-spawn-Unix-pseudoterminal": "沒有$t(generic.Python)來$t(generic.spawn)$t(generic.platforms.unix)$t(generic.pseudoterminal)", 109 | "not-Python": "不是$t(generic.Python)", 110 | "resizer-disabled": "$t(generic.terminal-resizer)已$t(generic.disable)", 111 | "resizer-exited-unexpectedly": "$t(generic.terminal-resizer)意外$t(generic.exit):{{code}}" 112 | }, 113 | "generic": { 114 | "Python": "Python", 115 | "argument": "參數", 116 | "behavior": "行為", 117 | "check": "檢查", 118 | "clear": "清除", 119 | "compatible": "兼容", 120 | "context-menu": "右鍵選單", 121 | "current-directory": "目前$t(generic.directory)", 122 | "custom": "自定義", 123 | "default": "默認", 124 | "directory": "目錄", 125 | "disable": "禁用", 126 | "documentation": "文檔", 127 | "documentations": { 128 | "changelog": "變更日誌", 129 | "donate": "$t(generic.donate)", 130 | "readme": "自述文件" 131 | }, 132 | "donate": "贊助", 133 | "executable": "執行檔", 134 | "exist": "存在", 135 | "exit": "退出", 136 | "file-extensions": { 137 | "HTML": "超文件標示語言" 138 | }, 139 | "focus": "聚焦", 140 | "font": "字體", 141 | "font-weight": "$t(generic.font)粗細", 142 | "history": "歷史", 143 | "incompatible": "不$t(generic.compatible)", 144 | "instance": "個體", 145 | "invalid": "無效", 146 | "name": "名稱", 147 | "option": "選項", 148 | "pin": "釘選", 149 | "platform": "平台", 150 | "platforms": { 151 | "darwin": "\u200bmacOS", 152 | "linux": "Linux", 153 | "unix": "Unix", 154 | "win32": "Microsoft Windows", 155 | "win32-short": "Windows" 156 | }, 157 | "preset": "預設", 158 | "profile": "配置", 159 | "profile-types": { 160 | "": "空白", 161 | "developerConsole": "開發者控制台", 162 | "external": "外部", 163 | "integrated": "整合式", 164 | "invalid": "$t(generic.invalid)", 165 | "select": "選擇" 166 | }, 167 | "pseudoterminal": "偽終端", 168 | "renderer": "渲染器", 169 | "renderers": { 170 | "canvas": "畫布", 171 | "dom": "文件物件模型", 172 | "webgl": "WebGL" 173 | }, 174 | "restart": "重啟", 175 | "restore": "還原", 176 | "root-directory": "根$t(generic.directory)", 177 | "save": "儲存", 178 | "spawn": "啟動", 179 | "split": "分割", 180 | "status-bar": "狀態欄", 181 | "tab": "分頁", 182 | "terminal": "終端", 183 | "terminal-option": "$t(generic.terminal)$t(generic.option)", 184 | "terminal-resizer": "$t(generic.terminal)縮放程式", 185 | "text-field": "文字域", 186 | "true": "真", 187 | "type": "類型", 188 | "undefine": "取消定義", 189 | "undefined": "未定義", 190 | "window": "視窗", 191 | "working-directory": "工作$t(generic.directory)" 192 | }, 193 | "menus": { 194 | "open-terminal": "在$t(generic.terminal)$t(generic.open):$t(generic.profile-types.{{type}})" 195 | }, 196 | "name": "$t(generic.terminal)", 197 | "notices": { 198 | "Python-status-entry-": "{{name}}:{{version}}(滿足:{{requirement}})", 199 | "Python-status-entry-unsatisfied": "{{name}}:{{version}}(未滿足:{{requirement}})", 200 | "no-default-profile": "沒有$t(generic.type)「$t(generic.profile-types.{{type}})」的$t(generic.default)$t(generic.profile)", 201 | "spawning-terminal": "$t(generic.terminal)$t(generic.spawn)中:{{name}}", 202 | "terminal-exited": "$t(generic.terminal)已$t(generic.exit):{{code}}" 203 | }, 204 | "profile-name-formats": { 205 | "long": "\"{{info.name}}\". $t(generic.profile-types.{{info.profile.type}}). {{info.id}}.", 206 | "short": "\"{{info.nameOrID}}\". $t(generic.profile-types.{{info.profile.type}})." 207 | }, 208 | "profile-presets": { 209 | "bashIntegrated": "bash:$t(generic.profile-types.integrated)", 210 | "cmdExternal": "cmd:$t(generic.profile-types.external)", 211 | "cmdIntegrated": "cmd:$t(generic.profile-types.integrated)", 212 | "darwinExternalDefault": "$t(generic.platforms.darwin)$t(generic.default):$t(generic.profile-types.external)", 213 | "darwinIntegratedDefault": "$t(generic.platforms.darwin)$t(generic.default):$t(generic.profile-types.integrated)", 214 | "dashIntegrated": "dash:$t(generic.profile-types.integrated)", 215 | "developerConsole": "$t(generic.profile-types.developerConsole)", 216 | "empty": "$t(generic.profile-types.)", 217 | "gitBashIntegrated": "Git Bash:$t(generic.profile-types.integrated)", 218 | "gnomeTerminalExternal": "GNOME$t(generic.terminal):$t(generic.profile-types.external)", 219 | "iTerm2External": "iTerm2:$t(generic.profile-types.external, capitalize)", 220 | "konsoleExternal": "Konsole:$t(generic.profile-types.external)", 221 | "linuxExternalDefault": "$t(generic.platforms.linux)$t(generic.default):$t(generic.profile-types.external)", 222 | "linuxIntegratedDefault": "$t(generic.platforms.linux)$t(generic.default):$t(generic.profile-types.integrated)", 223 | "powershellExternal": "powershell:$t(generic.profile-types.external)", 224 | "powershellIntegrated": "powershell:$t(generic.profile-types.integrated)", 225 | "pwshExternal": "pwsh:$t(generic.profile-types.external)", 226 | "pwshIntegrated": "pwsh:$t(generic.profile-types.integrated)", 227 | "shIntegrated": "sh:$t(generic.profile-types.integrated)", 228 | "terminalMacOSExternal": "$t(generic.terminal)($t(generic.platforms.darwin)):$t(generic.profile-types.external)", 229 | "win32ExternalDefault": "$t(generic.platforms.win32)$t(generic.default):$t(generic.profile-types.external)", 230 | "win32IntegratedDefault": "$t(generic.platforms.win32)$t(generic.default):$t(generic.profile-types.integrated)", 231 | "wslIntegrated": "適用於$t(generic.platforms.linux)的$t(generic.platforms.win32-short)子系統:$t(generic.profile-types.integrated)", 232 | "wtExternal": "$t(generic.platforms.win32-short)$t(generic.terminal):$t(generic.profile-types.external)", 233 | "xtermExternal": "xterm:$t(generic.profile-types.external)", 234 | "zshIntegrated": "zsh:$t(generic.profile-types.integrated)" 235 | }, 236 | "ribbons": { 237 | "open-terminal": "$t(generic.open)$t(generic.terminal)" 238 | }, 239 | "settings": { 240 | "add-to-command": "加入至$t(generic.command)", 241 | "add-to-context-menu": "加入至$t(generic.context-menu)", 242 | "advanced": "高級", 243 | "create-instance-near-existing-ones": "在$t(generic.exist)$t(generic.instance)附近創建$t(generic.instance)", 244 | "create-instance-near-existing-ones-description": "如果為$t(generic.true),則在$t(generic.instance)$t(generic.exist)時覆蓋「$t(settings.new-instance-behavior)」。", 245 | "documentation": "$t(generic.documentation)", 246 | "documentations": { 247 | "changelog": "$t(generic.documentations.changelog)", 248 | "donate": "$t(generic.documentations.donate)", 249 | "readme": "$t(generic.documentations.readme)" 250 | }, 251 | "expose-internal-modules": "公開內部模塊", 252 | "expose-internal-modules-description-HTML": "obsidian@codemirror/*@lezer/*……", 253 | "focus-on-new-instance": "$t(generic.focus)新$t(generic.instance)", 254 | "hide-status-bar": "隱藏$t(generic.status-bar)", 255 | "hide-status-bar-options": { 256 | "always": "總是", 257 | "focused": "當$t(generic.focus)$t(generic.terminal)時", 258 | "never": "從不", 259 | "running": "當執行$t(generic.terminal)時" 260 | }, 261 | "instancing": "$t(generic.instance)", 262 | "intercept-logging": "攔截記錄", 263 | "interface": "界面", 264 | "new-instance-behavior": "新$t(generic.instance)$t(generic.behavior)", 265 | "new-instance-behaviors": { 266 | "newHorizontalSplit": "新水平$t(generic.split)", 267 | "newLeftSplit": "新左側$t(generic.split)", 268 | "newLeftTab": "新左側$t(generic.tab)", 269 | "newRightSplit": "新右側$t(generic.split)", 270 | "newRightTab": "新右側$t(generic.tab)", 271 | "newTab": "新$t(generic.tab)", 272 | "newVerticalSplit": "新垂直$t(generic.split)", 273 | "newWindow": "新$t(generic.window)", 274 | "replaceTab": "替換$t(generic.tab)" 275 | }, 276 | "open-changelog-on-update": "更新時$t(generic.open)$t(generic.documentations.changelog)", 277 | "pin-new-instance": "$t(generic.pin)新$t(generic.instance)", 278 | "preferred-renderer": "首選$t(generic.renderer)", 279 | "preferred-renderer-options": "$t(generic.renderers.{{type}})", 280 | "profile-list": { 281 | "description": "$t(generic.list)中第一個$t(generic.compatible)的$t(generic.profile)是其$t(generic.terminal)$t(generic.type)的$t(generic.default)。" 282 | }, 283 | "profiles": "$t(generic.profile)", 284 | "profiles-description": "$t(generic.list-description)", 285 | "profiles-edit": "$t(generic.edit)" 286 | } 287 | } -------------------------------------------------------------------------------- /assets/trailer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polyipseity/obsidian-terminal/3d5b5c782c9a958a7d9adb10eedf3bfb25fa070b/assets/trailer.png -------------------------------------------------------------------------------- /build/build.mjs: -------------------------------------------------------------------------------- 1 | import { analyzeMetafile, context, formatMessages } from "esbuild" 2 | import { PATHS } from "./util.mjs" 3 | import { argv } from "node:process" 4 | import builtinModules from "builtin-modules" 5 | import esbuildCompress from "esbuild-compress" 6 | import esbuildPluginGlobals from "esbuild-plugin-globals" 7 | import esbuildPluginTextReplace from "esbuild-plugin-text-replace" 8 | import { isEmpty } from "lodash-es" 9 | import { writeFile } from "node:fs/promises" 10 | 11 | const ARGV_PRODUCTION = 2, 12 | COMMENT = "// repository: https://github.com/polyipseity/obsidian-terminal", 13 | DEV = argv[ARGV_PRODUCTION] === "dev", 14 | BUILD = await context({ 15 | alias: {}, 16 | banner: { js: COMMENT }, 17 | bundle: true, 18 | color: true, 19 | drop: [], 20 | entryPoints: ["src/main.ts", "src/styles.css"], 21 | external: [ 22 | "@codemirror/*", 23 | "@lezer/*", 24 | "electron", 25 | "node:*", 26 | "obsidian", 27 | ...builtinModules, 28 | ], 29 | footer: { js: COMMENT }, 30 | format: "cjs", 31 | inject: ["@polyipseity/obsidian-plugin-library/inject"], 32 | jsx: "transform", 33 | legalComments: "inline", 34 | loader: {}, 35 | logLevel: "info", 36 | logLimit: 0, 37 | metafile: true, 38 | minify: !DEV, 39 | outdir: PATHS.outDir, 40 | platform: "browser", 41 | plugins: [ 42 | esbuildPluginGlobals({ 43 | // Cannot use `i18next` because it is too outdated to have formatters 44 | moment: "moment", 45 | }), 46 | esbuildCompress({ 47 | compressors: [ 48 | { 49 | filter: /\.json$/, // eslint-disable-line require-unicode-regexp 50 | loader: "json", 51 | }, 52 | { 53 | filter: /\.(?:md|py)$/, // eslint-disable-line require-unicode-regexp 54 | lazy: true, 55 | loader: "text", 56 | }, 57 | ], 58 | }), 59 | esbuildPluginTextReplace({ 60 | include: /obsidian-plugin-library.*\.js$/, // eslint-disable-line require-unicode-regexp 61 | pattern: [ 62 | [ 63 | /\/\/(?[@#]) sourceMappingURL=/gu, 64 | "//$1 sourceMappingURL= ", 65 | ], 66 | ], 67 | }), 68 | ], 69 | sourcemap: DEV && "inline", 70 | sourcesContent: true, 71 | target: "ES2018", 72 | treeShaking: true, 73 | }) 74 | 75 | async function esbuild() { 76 | if (DEV) { 77 | await BUILD.watch({}) 78 | } else { 79 | try { 80 | // Await https://github.com/evanw/esbuild/issues/2886 81 | const { errors, warnings, metafile } = await BUILD.rebuild() 82 | await Promise.all([ 83 | (async () => { 84 | if (metafile) { 85 | console.log(await analyzeMetafile(metafile, { 86 | color: true, 87 | verbose: true, 88 | })) 89 | } 90 | for await (const logging of [ 91 | { 92 | data: warnings, 93 | kind: "warning", 94 | log: console.warn.bind(console), 95 | }, 96 | { 97 | data: errors, 98 | kind: "error", 99 | log: console.error.bind(console), 100 | }, 101 | ] 102 | .filter(({ data }) => !isEmpty(data)) 103 | .map(async ({ data, kind, log }) => { 104 | const message = (await formatMessages(data, { 105 | color: true, 106 | kind, 107 | })).join("\n") 108 | return () => log(message) 109 | })) { 110 | logging() 111 | } 112 | })(), 113 | ...metafile 114 | ? [ 115 | writeFile( 116 | PATHS.metafile, 117 | JSON.stringify(metafile, null, "\t"), 118 | { encoding: "utf-8" }, 119 | ), 120 | ] 121 | : [], 122 | ]) 123 | } finally { 124 | await BUILD.dispose() 125 | } 126 | } 127 | } 128 | await esbuild() 129 | -------------------------------------------------------------------------------- /build/obsidian-install.mjs: -------------------------------------------------------------------------------- 1 | import { PATHS, PLUGIN_ID } from "./util.mjs" 2 | import { copyFile, mkdir } from "node:fs/promises" 3 | import { argv } from "node:process" 4 | 5 | const ARGV_DESTINATION = 2, 6 | DESTINATION_PREFIX = `${PATHS.obsidianPlugins}/${await PLUGIN_ID}`, 7 | DESTINATION = `${argv[ARGV_DESTINATION] ?? "."}/${DESTINATION_PREFIX}` 8 | 9 | await mkdir(DESTINATION, { recursive: true }) 10 | await Promise.all([PATHS.manifest, PATHS.main, PATHS.styles] 11 | .map(file => copyFile(file, `${DESTINATION}/${file}`))) 12 | -------------------------------------------------------------------------------- /build/util.mjs: -------------------------------------------------------------------------------- 1 | import PLazy from "p-lazy" 2 | import { execFile } from "node:child_process" 3 | import { promisify } from "node:util" 4 | import { readFile } from "node:fs/promises" 5 | 6 | const execFileP = promisify(execFile), 7 | OUTDIR = "." 8 | 9 | export const 10 | PATHS = Object.freeze({ 11 | main: `${OUTDIR}/main.js`, 12 | manifest: "manifest.json", 13 | manifestBeta: "manifest-beta.json", 14 | metafile: "metafile.json", 15 | obsidianPlugins: ".obsidian/plugins", 16 | outDir: OUTDIR, 17 | "package": "package.json", 18 | packageLock: "package-lock.json", 19 | styles: `${OUTDIR}/styles.css`, 20 | versions: "versions.json", 21 | }), 22 | PLUGIN_ID = PLazy.from(async () => 23 | JSON.parse(await readFile(PATHS.manifest, { encoding: "utf-8" })).id) 24 | 25 | export async function execute(...args) { 26 | const process = execFileP(...args), 27 | { stdout, stderr } = await process 28 | if (stdout) { 29 | console.log(stdout) 30 | } 31 | if (stderr) { 32 | console.error(stderr) 33 | } 34 | const { exitCode } = process.child 35 | if (exitCode !== 0) { 36 | throw new Error(String(exitCode)) 37 | } 38 | return stdout 39 | } 40 | -------------------------------------------------------------------------------- /build/version-post.mjs: -------------------------------------------------------------------------------- 1 | import { PATHS, execute } from "./util.mjs" 2 | import { readFile, writeFile } from "node:fs/promises" 3 | 4 | const 5 | TRIM_END_FILES = Object.freeze([ 6 | PATHS.package, 7 | PATHS.packageLock, 8 | ]), 9 | [{ tag, tagMessage }] = await Promise.all([ 10 | (async () => { 11 | const tag0 = (await execute( 12 | "git", 13 | ["tag", "--points-at"], 14 | { encoding: "utf-8" }, 15 | )).split("\n", 1)[0].trim() 16 | return { 17 | tag: tag0, 18 | tagMessage: (await execute( 19 | "git", 20 | [ 21 | "tag", 22 | "--list", 23 | "--format=%(contents:subject)\n%(contents:body)", 24 | tag0, 25 | ], 26 | { encoding: "utf-8" }, 27 | )).trim(), 28 | } 29 | })(), 30 | (async () => { 31 | await Promise.all(TRIM_END_FILES 32 | .map(async file => writeFile( 33 | file, 34 | (await readFile(file, { encoding: "utf-8" })).trimEnd(), 35 | { encoding: "utf-8" }, 36 | ))) 37 | await execute( 38 | "git", 39 | ["add", ...TRIM_END_FILES], 40 | { encoding: "utf-8" }, 41 | ) 42 | })(), 43 | ]) 44 | await execute( 45 | "git", 46 | ["commit", "--amend", "--no-edit", "--gpg-sign"], 47 | { encoding: "utf-8" }, 48 | ) 49 | await execute( 50 | "git", 51 | ["tag", "--sign", "--force", `--message=${tagMessage}`, tag], 52 | { encoding: "utf-8" }, 53 | ) 54 | -------------------------------------------------------------------------------- /build/version.mjs: -------------------------------------------------------------------------------- 1 | import { PATHS, execute } from "./util.mjs" 2 | import { readFile, writeFile } from "node:fs/promises" 3 | 4 | const 5 | MANIFEST_MAP = Object.freeze({ 6 | author: ({ author }) => author, 7 | description: ({ description }) => description, 8 | fundingUrl: ({ funding }) => funding 9 | ? Object.fromEntries(funding.map(({ type, url }) => [type, url])) 10 | : null, 11 | version: ({ version }) => version, 12 | }), 13 | BETA_MANIFEST = Object.freeze({ version: "latest" }), 14 | aPackage = readFile(PATHS.package, "utf-8").then(data => JSON.parse(data)), 15 | aVersions = readFile(PATHS.versions, "utf-8").then(data => JSON.parse(data)) 16 | 17 | await Promise.all([ 18 | (async () => { 19 | const pack = await aPackage, 20 | manifest = { 21 | ...Object.fromEntries(Object.entries(MANIFEST_MAP) 22 | .map(([key, value]) => [key, value(pack)]) 23 | .filter(([, value]) => value)), 24 | ...pack.obsidian, 25 | } 26 | await Promise.all([ 27 | writeFile( 28 | PATHS.manifest, 29 | JSON.stringify(manifest, null, "\t"), 30 | { encoding: "utf-8" }, 31 | ), 32 | writeFile( 33 | PATHS.manifestBeta, 34 | JSON.stringify({ ...manifest, ...BETA_MANIFEST }, null, "\t"), 35 | { encoding: "utf-8" }, 36 | ), 37 | ]) 38 | })(), 39 | (async () => { 40 | const [pack, versions] = await Promise.all([aPackage, aVersions]) 41 | versions[MANIFEST_MAP.version(pack)] = pack.obsidian.minAppVersion 42 | await writeFile( 43 | PATHS.versions, 44 | JSON.stringify(versions, null, "\t"), 45 | { encoding: "utf-8" }, 46 | ) 47 | })(), 48 | ]) 49 | await execute( 50 | "git", 51 | ["add", PATHS.manifest, PATHS.manifestBeta, PATHS.versions], 52 | { encoding: "utf-8" }, 53 | ) 54 | -------------------------------------------------------------------------------- /manifest-beta.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "polyipseity", 3 | "description": "Integrate consoles, shells, and terminals inside Obsidian.", 4 | "fundingUrl": { 5 | "Buy Me a Coffee": "https://buymeacoffee.com/polyipseity", 6 | "GitHub Sponsors": "https://github.com/sponsors/polyipseity" 7 | }, 8 | "version": "latest", 9 | "authorUrl": "https://github.com/polyipseity", 10 | "id": "terminal", 11 | "isDesktopOnly": false, 12 | "minAppVersion": "1.4.11", 13 | "name": "Terminal" 14 | } -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "polyipseity", 3 | "description": "Integrate consoles, shells, and terminals inside Obsidian.", 4 | "fundingUrl": { 5 | "Buy Me a Coffee": "https://buymeacoffee.com/polyipseity", 6 | "GitHub Sponsors": "https://github.com/sponsors/polyipseity" 7 | }, 8 | "version": "3.16.0", 9 | "authorUrl": "https://github.com/polyipseity", 10 | "id": "terminal", 11 | "isDesktopOnly": false, 12 | "minAppVersion": "1.4.11", 13 | "name": "Terminal" 14 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "polyipseity", 3 | "bugs": { 4 | "url": "https://github.com/polyipseity/PLACEHOLDER" 5 | }, 6 | "dependencies": { 7 | "@polyipseity/obsidian-plugin-library": "^1.28.0", 8 | "@xterm/addon-canvas": "^0.7.0", 9 | "@xterm/addon-fit": "^0.10.0", 10 | "@xterm/addon-ligatures": "^0.9.0", 11 | "@xterm/addon-search": "^0.15.0", 12 | "@xterm/addon-serialize": "^0.13.0", 13 | "@xterm/addon-unicode11": "^0.8.0", 14 | "@xterm/addon-web-links": "^0.11.0", 15 | "@xterm/addon-webgl": "^0.18.0", 16 | "@xterm/xterm": "^5.5.0", 17 | "acorn": "^8.14.1", 18 | "ansi-escape-sequences": "^6.2.4", 19 | "async-lock": "^1.4.1", 20 | "browser-util-inspect": "^0.2.0", 21 | "espree": "^10.3.0", 22 | "i18next": "^24.2.3", 23 | "immutable": "^5.0.3", 24 | "lodash-es": "^4.17.21", 25 | "monkey-around": "^3.0.0", 26 | "obsidian": "~1.4.11", 27 | "semver": "^7.7.1", 28 | "simple-icons": "^14.11.0", 29 | "source-map": "^0.7.4", 30 | "svelte": "^5.25.2", 31 | "tmp-promise": "^3.0.3", 32 | "ts-essentials": "^10.0.4" 33 | }, 34 | "description": "Integrate consoles, shells, and terminals.", 35 | "devDependencies": { 36 | "@changesets/cli": "^2.28.1", 37 | "@eslint/compat": "^1.2.7", 38 | "@eslint/eslintrc": "^3.3.1", 39 | "@eslint/js": "^9.23.0", 40 | "@polyipseity/obsidian": "~1.4.11", 41 | "@tsconfig/node16": "^16.1.3", 42 | "@tsconfig/recommended": "^1.0.8", 43 | "@tsconfig/strictest": "^2.0.5", 44 | "@types/ansi-escape-sequences": "^4.0.4", 45 | "@types/async-lock": "^1.4.2", 46 | "@types/browser-util-inspect": "^0.2.4", 47 | "@types/lodash-es": "^4.17.12", 48 | "@types/node": "^22.13.11", 49 | "@types/semver": "^7.5.8", 50 | "@typescript-eslint/eslint-plugin": "^8.27.0", 51 | "@typescript-eslint/parser": "^8.27.0", 52 | "builtin-modules": "^5.0.0", 53 | "esbuild": "^0.25.1", 54 | "esbuild-compress": "^2.0.1", 55 | "esbuild-plugin-globals": "^0.2.0", 56 | "esbuild-plugin-text-replace": "^1.3.0", 57 | "eslint": "^9.23.0", 58 | "eslint-import-resolver-typescript": "^4.2.2", 59 | "eslint-plugin-import": "^2.31.0", 60 | "eslint-plugin-markdownlint": "^0.6.0", 61 | "globals": "^16.0.0", 62 | "p-lazy": "^5.0.0", 63 | "tslib": "^2.8.1", 64 | "typescript": "^5.8.2" 65 | }, 66 | "files": [ 67 | "main.js", 68 | "manifest.json", 69 | "requirements.txt", 70 | "styles.css" 71 | ], 72 | "funding": [ 73 | { 74 | "type": "Buy Me a Coffee", 75 | "url": "https://buymeacoffee.com/polyipseity" 76 | }, 77 | { 78 | "type": "GitHub Sponsors", 79 | "url": "https://github.com/sponsors/polyipseity" 80 | } 81 | ], 82 | "homepage": "https://github.com/polyipseity/PLACEHOLDER#readme", 83 | "keywords": [ 84 | "console", 85 | "console-emulator", 86 | "obsidian", 87 | "obsidian-plugin", 88 | "plugin", 89 | "shell", 90 | "shell-emulator", 91 | "terminal", 92 | "terminal-emulator" 93 | ], 94 | "license": "AGPL-3.0-or-later", 95 | "main": "main.js", 96 | "name": "obsidian-terminal", 97 | "obsidian": { 98 | "authorUrl": "https://github.com/polyipseity", 99 | "id": "terminal", 100 | "isDesktopOnly": false, 101 | "minAppVersion": "1.4.11", 102 | "name": "Terminal" 103 | }, 104 | "overrides": {}, 105 | "pnpm": { 106 | "overrides": {} 107 | }, 108 | "private": true, 109 | "repository": { 110 | "type": "git", 111 | "url": "git+https://github.com/polyipseity/PLACEHOLDER.git" 112 | }, 113 | "scripts": { 114 | "build": "npm run check && npm run build:force", 115 | "build:force": "node build/build.mjs", 116 | "check": "tsc --noEmit && eslint --cache .", 117 | "dev": "npm run build:force -- dev", 118 | "fix": "eslint --fix --cache .", 119 | "obsidian:install": "npm run build && node build/obsidian-install.mjs", 120 | "obsidian:install:force": "npm run build:force && node build/obsidian-install.mjs", 121 | "postversion": "node build/version-post.mjs", 122 | "version": "node build/version.mjs" 123 | }, 124 | "sideEffects": false, 125 | "style": "styles.css", 126 | "type": "module", 127 | "version": "3.16.0", 128 | "workspaces": [ 129 | ".", 130 | "build/*" 131 | ] 132 | } -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - '.' 3 | - build/* 4 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | --index-url https://pypi.org/simple/ 2 | 3 | # Update `README.md`, `magic.ts`, and `requirements.txt` together. 4 | psutil>=5.9.5 5 | pywinctl>=0.0.50 6 | typing_extensions>=4.7.1 7 | -------------------------------------------------------------------------------- /src/@types/_index.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.md" { 2 | const value: PromiseLike 3 | export default value 4 | } 5 | declare module "*.py" { 6 | const value: PromiseLike 7 | export default value 8 | } 9 | -------------------------------------------------------------------------------- /src/@types/_obsidian-terminal.d.ts: -------------------------------------------------------------------------------- 1 | declare module "obsidian-terminal" { } 2 | -------------------------------------------------------------------------------- /src/@types/electron.ts: -------------------------------------------------------------------------------- 1 | declare global { 2 | interface File { 3 | readonly path?: string | undefined 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/@types/i18next.ts: -------------------------------------------------------------------------------- 1 | declare module "i18next" { 2 | interface CustomTypeOptions { 3 | readonly defaultNS: typeof PluginLocales.DEFAULT_NAMESPACE 4 | readonly resources: PluginLocales.Resources 5 | readonly returnNull: typeof PluginLocales.RETURN_NULL 6 | } 7 | } 8 | import type { } from "i18next" 9 | import type { PluginLocales } from "../../assets/locales.js" 10 | -------------------------------------------------------------------------------- /src/@types/index.ts: -------------------------------------------------------------------------------- 1 | declare module "*.md" { } 2 | declare module "*.py" { } 3 | import type { } from "*.md" 4 | import type { } from "*.py" 5 | -------------------------------------------------------------------------------- /src/@types/obsidian-terminal.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Public API for `obsidian-terminal`. 3 | */ 4 | declare module "obsidian-terminal" { 5 | 6 | /** 7 | * Type of `$$` in the developer console. 8 | */ 9 | interface DeveloperConsoleContext { 10 | 11 | /** 12 | * Depth to expanded nested objects up to. 13 | * 14 | * @default 0 15 | */ 16 | depth: number 17 | 18 | /** 19 | * Terminals connected to the developer console. 20 | */ 21 | readonly terminals: readonly Terminal[] 22 | } 23 | } 24 | import type { } from "obsidian-terminal" 25 | import type { Terminal } from "@xterm/xterm" 26 | -------------------------------------------------------------------------------- /src/documentations.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DocumentationMarkdownView, 3 | StorageSettingsManager, 4 | addCommand, 5 | anyToError, 6 | deepFreeze, 7 | printError, 8 | revealPrivate, 9 | toJSONOrString, 10 | typedKeys, 11 | } from "@polyipseity/obsidian-plugin-library" 12 | import { DOMClasses2 } from "./magic.js" 13 | import type { TerminalPlugin } from "./main.js" 14 | import changelogMd from "../CHANGELOG.md" 15 | import readmeMd from "../README.md" 16 | import semverLt from "semver/functions/lt.js" 17 | 18 | export const DOCUMENTATIONS = deepFreeze({ 19 | async changelog(view: DocumentationMarkdownView.Registered, active: boolean) { 20 | await view.open(active, { 21 | data: await changelogMd, 22 | displayTextI18nKey: "translation:generic.documentations.changelog", 23 | iconI18nKey: "asset:generic.documentations.changelog-icon", 24 | }) 25 | }, 26 | donate(view: DocumentationMarkdownView.Registered) { 27 | const { context, context: { app, manifest } } = view 28 | revealPrivate(context, [app], app0 => { 29 | const { setting: { settingTabs } } = app0 30 | for (const tab of settingTabs) { 31 | const { id, containerEl: { ownerDocument } } = tab 32 | if (id !== "community-plugins") { continue } 33 | const div = ownerDocument.createElement("div") 34 | tab.renderInstalledPlugin(manifest, div) 35 | const element = div.querySelector( 36 | `.${DOMClasses2.SVG_ICON}.${DOMClasses2.LUCIDE_HEART}`, 37 | )?.parentElement 38 | if (!element) { throw new Error(toJSONOrString(div)) } 39 | element.click() 40 | return 41 | } 42 | throw new Error(toJSONOrString(settingTabs)) 43 | }, error => { throw error }) 44 | }, 45 | async readme(view: DocumentationMarkdownView.Registered, active: boolean) { 46 | await view.open(active, { 47 | data: await readmeMd, 48 | displayTextI18nKey: "translation:generic.documentations.readme", 49 | iconI18nKey: "asset:generic.documentations.readme-icon", 50 | }) 51 | }, 52 | }) 53 | export type DocumentationKeys = readonly ["changelog", "donate", "readme"] 54 | export const DOCUMENTATION_KEYS = typedKeys()(DOCUMENTATIONS) 55 | 56 | class Loaded0 { 57 | public constructor( 58 | public readonly context: TerminalPlugin, 59 | public readonly docMdView: DocumentationMarkdownView.Registered, 60 | ) { } 61 | 62 | public open(key: DocumentationKeys[number], active = true): void { 63 | const { 64 | context, 65 | context: { version, language: { value: i18n }, localSettings }, 66 | docMdView, 67 | } = this; 68 | (async (): Promise => { 69 | try { 70 | await DOCUMENTATIONS[key](docMdView, active) 71 | if (key === "changelog" && version !== null) { 72 | localSettings.mutate(lsm => { 73 | lsm.lastReadChangelogVersion = version 74 | }).then(async () => localSettings.write()) 75 | .catch((error: unknown) => { self.console.error(error) }) 76 | } 77 | } catch (error) { 78 | printError( 79 | anyToError(error), 80 | () => i18n.t("errors.error-opening-documentation"), 81 | context, 82 | ) 83 | } 84 | })() 85 | } 86 | } 87 | export function loadDocumentations( 88 | context: TerminalPlugin, 89 | readme = false, 90 | ): loadDocumentations.Loaded { 91 | const 92 | { 93 | version, 94 | language: { value: i18n }, 95 | localSettings, 96 | settings, 97 | } = context, 98 | ret = new Loaded0( 99 | context, 100 | DocumentationMarkdownView.register(context), 101 | ) 102 | for (const doc of DOCUMENTATION_KEYS) { 103 | addCommand(context, () => i18n.t(`commands.open-documentation-${doc}`), { 104 | callback() { ret.open(doc) }, 105 | icon: i18n.t(`asset:commands.open-documentation-${doc}-icon`), 106 | id: `open-documentation.${doc}`, 107 | }) 108 | } 109 | if (readme) { ret.open("readme", false) } 110 | if (version !== null && 111 | settings.value.openChangelogOnUpdate && 112 | !StorageSettingsManager.hasFailed(localSettings.value) && 113 | semverLt(localSettings.value.lastReadChangelogVersion, version)) { 114 | ret.open("changelog", false) 115 | } 116 | return ret 117 | } 118 | export namespace loadDocumentations { 119 | export type Loaded = Loaded0 120 | } 121 | -------------------------------------------------------------------------------- /src/get-package-version.py: -------------------------------------------------------------------------------- 1 | from importlib.metadata import PackageNotFoundError as _Pkg404Err, version as _ver 2 | from sys import argv as _argv 3 | from typing import Sequence as _Seq 4 | 5 | 6 | def main(argv: _Seq[str]): 7 | pkg = argv[1] if len(argv) > 1 else "" 8 | try: 9 | ver = _ver(pkg) 10 | except (ValueError, _Pkg404Err): 11 | ver = "" 12 | print(f"{pkg} {ver}") 13 | 14 | 15 | if __name__ == "__main__": 16 | main(_argv) 17 | -------------------------------------------------------------------------------- /src/icons.ts: -------------------------------------------------------------------------------- 1 | import { 2 | UnnamespacedID, 3 | registerIcon, 4 | } from "@polyipseity/obsidian-plugin-library" 5 | import { siLinux, siMacos } from "simple-icons" 6 | import type { TerminalPlugin } from "./main.js" 7 | 8 | export function loadIcons(context: TerminalPlugin): void { 9 | for (const [key, value] of Object.entries({ 10 | linux: siLinux, 11 | macos: siMacos, 12 | })) { 13 | registerIcon( 14 | context, 15 | new UnnamespacedID(key).namespaced(context), 16 | value.svg, 17 | ) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/import.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-require-imports */ 2 | import { 3 | deepFreeze, 4 | typedKeys, 5 | } from "@polyipseity/obsidian-plugin-library" 6 | 7 | // Needed for bundler 8 | const BUNDLE0 = deepFreeze({ 9 | // eslint-disable-next-line @typescript-eslint/naming-convention 10 | "@xterm/addon-canvas": (): unknown => require("@xterm/addon-canvas"), 11 | // eslint-disable-next-line @typescript-eslint/naming-convention 12 | "@xterm/addon-fit": (): unknown => require("@xterm/addon-fit"), 13 | // eslint-disable-next-line @typescript-eslint/naming-convention 14 | "@xterm/addon-ligatures": (): unknown => require("@xterm/addon-ligatures"), 15 | // eslint-disable-next-line @typescript-eslint/naming-convention 16 | "@xterm/addon-search": (): unknown => require("@xterm/addon-search"), 17 | // eslint-disable-next-line @typescript-eslint/naming-convention 18 | "@xterm/addon-serialize": (): unknown => require("@xterm/addon-serialize"), 19 | // eslint-disable-next-line @typescript-eslint/naming-convention 20 | "@xterm/addon-unicode11": (): unknown => require("@xterm/addon-unicode11"), 21 | // eslint-disable-next-line @typescript-eslint/naming-convention 22 | "@xterm/addon-web-links": (): unknown => require("@xterm/addon-web-links"), 23 | // eslint-disable-next-line @typescript-eslint/naming-convention 24 | "@xterm/addon-webgl": (): unknown => require("@xterm/addon-webgl"), 25 | // eslint-disable-next-line @typescript-eslint/naming-convention 26 | "@xterm/xterm": (): unknown => require("@xterm/xterm"), 27 | // eslint-disable-next-line @typescript-eslint/naming-convention 28 | "tmp-promise": (): unknown => require("tmp-promise"), 29 | }) 30 | export const 31 | // Needed for bundler 32 | BUNDLE = new Map(Object.entries(BUNDLE0)), 33 | MODULES = typedKeys()(BUNDLE0) 45 | -------------------------------------------------------------------------------- /src/magic.ts: -------------------------------------------------------------------------------- 1 | import { Platform, deepFreeze } from "@polyipseity/obsidian-plugin-library" 2 | import { SemVer } from "semver" 3 | 4 | export const 5 | CHECK_EXECUTABLE_WAIT = 5, 6 | DEFAULT_ENCODING = "utf-8", 7 | DEFAULT_PYTHON_EXECUTABLE = "python3", 8 | DEFAULT_PYTHONIOENCODING = `${DEFAULT_ENCODING}:backslashreplace`, 9 | EXIT_SUCCESS = 0, 10 | DEFAULT_SUCCESS_EXIT_CODES = deepFreeze([ 11 | EXIT_SUCCESS.toString(), 12 | "SIGINT", 13 | "SIGTERM", 14 | ]), 15 | MAX_HISTORY = 1024, 16 | MAX_LOCK_PENDING = Infinity, 17 | PLUGIN_UNLOAD_DELAY = 10, 18 | PYTHON_REQUIREMENTS = deepFreeze({ 19 | // Update `README.md`, `magic.ts`, and `requirements.txt` together. 20 | // eslint-disable-next-line @typescript-eslint/naming-convention 21 | Python: { platforms: Platform.DESKTOP, version: new SemVer("3.10.0") }, 22 | psutil: { platforms: ["win32"], version: new SemVer("5.9.5") }, 23 | pywinctl: { platforms: ["win32"], version: new SemVer("0.0.50") }, 24 | // eslint-disable-next-line @typescript-eslint/naming-convention, camelcase 25 | typing_extensions: { platforms: ["win32"], version: new SemVer("4.7.1") }, 26 | }) satisfies Readonly>, 30 | TERMINAL_EMULATOR_RESIZE_WAIT = 0.1, 31 | TERMINAL_EXIT_CLEANUP_WAIT = 5, 32 | TERMINAL_PTY_RESIZE_WAIT = 0.5, 33 | TERMINAL_RESIZER_WATCHDOG_WAIT = 0.5, 34 | WINDOWS_CMD_PATH = "C:\\Windows\\System32\\cmd.exe", 35 | WINDOWS_CONHOST_PATH = "C:\\Windows\\System32\\conhost.exe" 36 | 37 | export namespace DOMClasses2 { 38 | export const 39 | LUCIDE_HEART = "lucide-heart", 40 | SVG_ICON = "svg-icon" 41 | export namespace Namespaced { 42 | export const 43 | TERMINAL = "terminal" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { type App, Plugin, type PluginManifest } from "obsidian" 2 | import { EarlyPatchManager, loadPatch } from "./patch.js" 3 | import { 4 | LanguageManager, 5 | type PluginContext, 6 | SI_PREFIX_SCALE, 7 | SettingsManager, 8 | StatusBarHider, 9 | StorageSettingsManager, 10 | createI18n, 11 | semVerString, 12 | } from "@polyipseity/obsidian-plugin-library" 13 | import { LocalSettings, Settings } from "./settings-data.js" 14 | import { MAX_HISTORY, PLUGIN_UNLOAD_DELAY } from "./magic.js" 15 | import { DeveloperConsolePseudoterminal } from "./terminal/pseudoterminal.js" 16 | import { PluginLocales } from "../assets/locales.js" 17 | import { isNil } from "lodash-es" 18 | import { loadDocumentations } from "./documentations.js" 19 | import { loadIcons } from "./icons.js" 20 | import { loadSettings } from "./settings.js" 21 | import { loadTerminal } from "./terminal/load.js" 22 | 23 | export class TerminalPlugin 24 | extends Plugin 25 | implements PluginContext { 26 | public readonly version 27 | public readonly language: LanguageManager 28 | public readonly localSettings: StorageSettingsManager 29 | public readonly settings: SettingsManager 30 | public readonly developerConsolePTY = 31 | new DeveloperConsolePseudoterminal.Manager(this) 32 | 33 | public readonly earlyPatch 34 | public readonly statusBarHider = new StatusBarHider(this) 35 | 36 | public constructor(app: App, manifest: PluginManifest) { 37 | const earlyPatch = new EarlyPatchManager(app, { maxHistory: MAX_HISTORY }) 38 | earlyPatch.load() 39 | super(app, manifest) 40 | this.earlyPatch = earlyPatch 41 | try { 42 | this.version = semVerString(manifest.version) 43 | } catch (error) { 44 | self.console.warn(error) 45 | this.version = null 46 | } 47 | this.language = new LanguageManager( 48 | this, 49 | async () => createI18n( 50 | PluginLocales.RESOURCES, 51 | PluginLocales.FORMATTERS, 52 | { 53 | defaultNS: PluginLocales.DEFAULT_NAMESPACE, 54 | fallbackLng: PluginLocales.FALLBACK_LANGUAGES, 55 | returnNull: PluginLocales.RETURN_NULL, 56 | }, 57 | ), 58 | ) 59 | this.localSettings = new StorageSettingsManager(this, LocalSettings.fix) 60 | this.settings = new SettingsManager(this, Settings.fix) 61 | } 62 | 63 | public displayName(unlocalized = false): string { 64 | return unlocalized 65 | ? this.language.value.t("name", { 66 | interpolation: { escapeValue: false }, 67 | lng: PluginLocales.DEFAULT_LANGUAGE, 68 | }) 69 | : this.language.value.t("name") 70 | } 71 | 72 | public override onload(): void { 73 | (async (): Promise => { 74 | try { 75 | const loaded: unknown = await this.loadData(), 76 | { 77 | developerConsolePTY, 78 | earlyPatch, 79 | language, 80 | localSettings, 81 | statusBarHider, 82 | settings, 83 | } = this, 84 | earlyChildren = [earlyPatch, language, localSettings, settings], 85 | // Placeholder to resolve merge conflicts more easily 86 | children = [ 87 | developerConsolePTY, 88 | statusBarHider, 89 | ] 90 | for (const child of earlyChildren) { child.unload() } 91 | for (const child of earlyChildren) { 92 | // Delay unloading as there are unload tasks that cannot be awaited 93 | this.register(() => { 94 | const id = self.setTimeout(() => { 95 | child.unload() 96 | }, PLUGIN_UNLOAD_DELAY * SI_PREFIX_SCALE) 97 | child.register(() => { self.clearTimeout(id) }) 98 | }) 99 | child.load() 100 | } 101 | await Promise.all(earlyChildren.map(async child => child.onLoaded)) 102 | for (const child of children) { this.addChild(child) } 103 | await Promise.all([ 104 | Promise.resolve().then(() => { 105 | settings.onMutate( 106 | settings0 => settings0.interceptLogging, 107 | cur => { this.earlyPatch.value.enableLoggingPatch(cur) }, 108 | ) 109 | this.earlyPatch.value 110 | .enableLoggingPatch(settings.value.interceptLogging) 111 | }), 112 | Promise.resolve().then(() => { loadPatch(this) }), 113 | Promise.resolve().then(() => { loadIcons(this) }), 114 | Promise.resolve().then(() => { 115 | loadSettings(this, loadDocumentations(this, isNil(loaded))) 116 | }), 117 | Promise.resolve().then(() => { loadTerminal(this) }), 118 | Promise.resolve().then(() => { 119 | this.register(settings.onMutate( 120 | settings0 => settings0.hideStatusBar, 121 | () => { statusBarHider.update() }, 122 | )) 123 | statusBarHider.hide(() => 124 | settings.value.hideStatusBar === "always") 125 | }), 126 | ]) 127 | } catch (error) { 128 | self.console.error(error) 129 | } 130 | })() 131 | } 132 | } 133 | // Needed for loading 134 | export default TerminalPlugin 135 | -------------------------------------------------------------------------------- /src/patch.ts: -------------------------------------------------------------------------------- 1 | import { 2 | EventEmitterLite, 3 | Functions, 4 | ResourceComponent, 5 | aroundIdentityFactory, 6 | deepFreeze, 7 | dynamicRequireSync, 8 | patchWindows, 9 | } from "@polyipseity/obsidian-plugin-library" 10 | import type { App } from "obsidian" 11 | import type { TerminalPlugin } from "./main.js" 12 | import { around } from "monkey-around" 13 | import { noop } from "lodash-es" 14 | 15 | export class Log { 16 | public readonly logger = new EventEmitterLite() 17 | readonly #history: Log.Event[] = [] 18 | 19 | public constructor(protected readonly maxHistory = NaN) { 20 | this.logger.listen(event => { 21 | const his = this.#history 22 | his.push(event) 23 | his.splice(0, his.length - maxHistory) 24 | }) 25 | } 26 | 27 | public get history(): readonly Log.Event[] { 28 | return this.#history 29 | } 30 | } 31 | export namespace Log { 32 | export type Event = { readonly type: Event.Type } & ( 33 | { 34 | readonly type: "debug" | "error" | "info" | "warn" 35 | readonly data: readonly unknown[] 36 | } | { 37 | readonly type: "unhandledRejection" 38 | readonly data: PromiseRejectionEvent 39 | } | { 40 | readonly type: "windowError" 41 | readonly data: ErrorEvent 42 | } 43 | ) 44 | export namespace Event { 45 | export const TYPES = deepFreeze([ 46 | "info", 47 | "error", 48 | "warn", 49 | "debug", 50 | "windowError", 51 | "unhandledRejection", 52 | ]) 53 | export type Type = typeof TYPES[number] 54 | export type Typed = Event & { readonly type: T } 55 | } 56 | } 57 | 58 | function patchLoggingConsole(console: Console, log: Log): () => void { 59 | function consolePatch( 60 | type: T, 61 | proto: typeof console[T], 62 | ): typeof proto { 63 | let recursive = false 64 | return function fn( 65 | this: typeof console, 66 | ...args: Parameters 67 | ): void { 68 | if (recursive) { return } 69 | recursive = true 70 | try { 71 | try { 72 | log.logger.emit({ data: args, type }) 73 | .catch(noop satisfies () => unknown as () => unknown) 74 | } catch (error) { 75 | this.error(error) 76 | } finally { 77 | proto.apply(this, args) 78 | } 79 | } finally { 80 | recursive = false 81 | } 82 | } 83 | } 84 | return around(console, { 85 | debug(next) { return consolePatch("debug", next) }, 86 | error(next) { return consolePatch("error", next) }, 87 | log(next) { return consolePatch("info", next) }, 88 | warn(next) { return consolePatch("warn", next) }, 89 | }) 90 | } 91 | 92 | function patchLoggingWindow(self0: Window, log: Log): () => void { 93 | const 94 | onWindowError = (error: ErrorEvent): void => { 95 | log.logger.emit({ 96 | data: error, 97 | type: "windowError", 98 | }).catch(noop satisfies () => unknown as () => unknown) 99 | }, 100 | onUnhandledRejection = (error: PromiseRejectionEvent): void => { 101 | log.logger.emit({ 102 | data: error, 103 | type: "unhandledRejection", 104 | }).catch(noop satisfies () => unknown as () => unknown) 105 | }, 106 | ret = new Functions( 107 | { async: false, settled: true }, 108 | () => { 109 | self0.removeEventListener("error", onWindowError, { capture: true }) 110 | }, 111 | () => { 112 | self0.removeEventListener( 113 | "unhandledrejection", 114 | onUnhandledRejection, 115 | { capture: true }, 116 | ) 117 | }, 118 | ) 119 | try { 120 | self0.addEventListener("error", onWindowError, { 121 | capture: true, 122 | passive: true, 123 | }) 124 | self0.addEventListener("unhandledrejection", onUnhandledRejection, { 125 | capture: true, 126 | passive: true, 127 | }) 128 | return () => { ret.call() } 129 | } catch (error) { 130 | ret.call() 131 | throw error 132 | } 133 | } 134 | 135 | function patchLogging( 136 | self0: Window & typeof globalThis, 137 | log: Log, 138 | ): () => void { 139 | const ret = new Functions({ async: false, settled: true }) 140 | try { 141 | ret.push(patchLoggingConsole(self0.console, log)) 142 | ret.push(patchLoggingWindow(self0, log)) 143 | return () => { ret.call() } 144 | } catch (error) { 145 | ret.call() 146 | throw error 147 | } 148 | } 149 | 150 | export interface EarlyPatch { 151 | readonly log: Log 152 | readonly enableLoggingPatch: (enable: boolean) => void 153 | } 154 | function earlyPatch(app: App, options?: { 155 | readonly maxHistory?: number | undefined 156 | }): EarlyPatch & { readonly unpatch: () => void } { 157 | const unpatch = new Functions({ async: false, settled: true }) 158 | try { 159 | const { workspace } = app, 160 | log = new Log(options?.maxHistory) 161 | let loggingPatch: (() => void) | null = null 162 | 163 | unpatch.push(() => { if (loggingPatch) { loggingPatch() } }) 164 | loggingPatch = patchWindows(workspace, self0 => patchLogging(self0, log)) 165 | 166 | return Object.freeze({ 167 | enableLoggingPatch(enable: boolean) { 168 | if (enable) { 169 | if (loggingPatch) { return } 170 | loggingPatch = 171 | patchWindows(workspace, self0 => patchLogging(self0, this.log)) 172 | return 173 | } 174 | if (!loggingPatch) { return } 175 | try { 176 | loggingPatch() 177 | } finally { 178 | loggingPatch = null 179 | } 180 | }, 181 | log, 182 | unpatch() { unpatch.call() }, 183 | }) 184 | } catch (error) { 185 | unpatch.call() 186 | throw error 187 | } 188 | } 189 | 190 | export class EarlyPatchManager extends ResourceComponent { 191 | #loaded = false 192 | 193 | public constructor( 194 | protected readonly app: App, 195 | protected readonly options?: Parameters[1], 196 | ) { super() } 197 | 198 | public override load(): void { 199 | if (this.#loaded) { return } 200 | super.load() 201 | this.register(() => { this.#loaded = false }) 202 | this.#loaded = true 203 | } 204 | 205 | protected override load0(): EarlyPatch { 206 | const ret = earlyPatch(this.app, this.options) 207 | this.register(ret.unpatch) 208 | return ret 209 | } 210 | } 211 | 212 | function patchRequire( 213 | context: TerminalPlugin, 214 | self0: typeof globalThis, 215 | ): () => void { 216 | const { settings } = context 217 | return around(self0, { 218 | require(next) { 219 | // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion 220 | return function fn( 221 | this: typeof self0 | undefined, 222 | ...args: Parameters 223 | ): ReturnType { 224 | try { 225 | return next.apply(this, args) 226 | } catch (error) { 227 | if (!settings.value.exposeInternalModules) { throw error } 228 | /* @__PURE__ */ self0.console.debug(error) 229 | return dynamicRequireSync(new Map(), ...args) 230 | } 231 | } as NodeJS.Require 232 | }, 233 | toString: aroundIdentityFactory(), 234 | }) 235 | } 236 | 237 | export function loadPatch(context: TerminalPlugin): void { 238 | const { app: { workspace } } = context 239 | context.register(patchWindows(workspace, self0 => 240 | patchRequire(context, self0))) 241 | } 242 | -------------------------------------------------------------------------------- /src/settings.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AdvancedSettingTab, 3 | cloneAsWritable, 4 | closeSetting, 5 | createChildElement, 6 | createDocumentFragment, 7 | linkSetting, 8 | registerSettingsCommands, 9 | resetButton, 10 | setTextToEnum, 11 | } from "@polyipseity/obsidian-plugin-library" 12 | import { ProfileListModal } from "./modals.js" 13 | import { Settings } from "./settings-data.js" 14 | import type { TerminalPlugin } from "./main.js" 15 | import type { loadDocumentations } from "./documentations.js" 16 | import semverLt from "semver/functions/lt.js" 17 | import { size } from "lodash-es" 18 | 19 | export class SettingTab extends AdvancedSettingTab { 20 | public constructor( 21 | protected override readonly context: TerminalPlugin, 22 | protected readonly docs: loadDocumentations.Loaded, 23 | ) { super(context) } 24 | 25 | protected override onLoad(): void { 26 | super.onLoad() 27 | const { 28 | containerEl, 29 | context, 30 | context: { language: { value: i18n }, localSettings, settings, version }, 31 | docs, 32 | ui, 33 | } = this 34 | this.newDescriptionWidget() 35 | this.newLanguageWidget( 36 | Settings.DEFAULTABLE_LANGUAGES, 37 | language => language 38 | ? i18n.t(`language:${language}`) 39 | : i18n.t("settings.language-default"), 40 | Settings.DEFAULT, 41 | ) 42 | ui.newSetting(containerEl, setting => { 43 | setting 44 | .setName(i18n.t("settings.documentation")) 45 | .addButton(button => button 46 | .setIcon(i18n.t("asset:settings.documentations.donate-icon")) 47 | .setTooltip(i18n.t("settings.documentations.donate")) 48 | .setCta() 49 | .onClick(() => { docs.open("donate") })) 50 | .addButton(button => button 51 | .setIcon(i18n.t("asset:settings.documentations.readme-icon")) 52 | .setTooltip(i18n.t("settings.documentations.readme")) 53 | .setCta() 54 | .onClick(() => { 55 | docs.open("readme") 56 | closeSetting(containerEl) 57 | })) 58 | .addButton(button => { 59 | button 60 | .setIcon(i18n.t("asset:settings.documentations.changelog-icon")) 61 | .setTooltip(i18n.t("settings.documentations.changelog")) 62 | .onClick(() => { 63 | docs.open("changelog") 64 | closeSetting(containerEl) 65 | }) 66 | if (version === null || 67 | semverLt(localSettings.value.lastReadChangelogVersion, version)) { 68 | button.setCta() 69 | } 70 | }) 71 | }) 72 | this.newAllSettingsWidget( 73 | Settings.DEFAULT, 74 | Settings.fix, 75 | ) 76 | ui.newSetting(containerEl, setting => { 77 | setting 78 | .setName(i18n.t("settings.add-to-command")) 79 | .addToggle(linkSetting( 80 | () => settings.value.addToCommand, 81 | async value => settings 82 | .mutate(settingsM => { settingsM.addToCommand = value }), 83 | () => { this.postMutate() }, 84 | )) 85 | .addExtraButton(resetButton( 86 | i18n.t("asset:settings.add-to-command-icon"), 87 | i18n.t("settings.reset"), 88 | async () => settings.mutate(settingsM => { 89 | settingsM.addToCommand = Settings.DEFAULT.addToCommand 90 | }), 91 | () => { this.postMutate() }, 92 | )) 93 | }) 94 | .newSetting(containerEl, setting => { 95 | setting 96 | .setName(i18n.t("settings.add-to-context-menu")) 97 | .addToggle(linkSetting( 98 | () => settings.value.addToContextMenu, 99 | async value => settings.mutate(settingsM => { 100 | settingsM.addToContextMenu = value 101 | }), 102 | () => { this.postMutate() }, 103 | )) 104 | .addExtraButton(resetButton( 105 | i18n.t("asset:settings.add-to-context-menu-icon"), 106 | i18n.t("settings.reset"), 107 | async () => settings.mutate(settingsM => { 108 | settingsM.addToContextMenu = Settings.DEFAULT.addToContextMenu 109 | }), 110 | () => { this.postMutate() }, 111 | )) 112 | }) 113 | .newSetting(containerEl, setting => { 114 | setting 115 | .setName(i18n.t("settings.profiles")) 116 | .setDesc(i18n.t("settings.profiles-description", { 117 | count: size(settings.value.profiles), 118 | interpolation: { escapeValue: false }, 119 | })) 120 | .addButton(button => button 121 | .setIcon(i18n.t("asset:settings.profiles-edit-icon")) 122 | .setTooltip(i18n.t("settings.profiles-edit")) 123 | .onClick(() => { 124 | new ProfileListModal( 125 | context, 126 | Object.entries(settings.value.profiles), 127 | { 128 | callback: async (data): Promise => { 129 | await settings.mutate(settingsM => { 130 | settingsM.profiles = Object.fromEntries(data) 131 | }) 132 | this.postMutate() 133 | }, 134 | description: (): string => 135 | i18n.t("settings.profile-list.description"), 136 | }, 137 | ).open() 138 | })) 139 | .addExtraButton(resetButton( 140 | i18n.t("asset:settings.profiles-icon"), 141 | i18n.t("settings.reset"), 142 | async () => settings.mutate(settingsM => { 143 | settingsM.profiles = cloneAsWritable(Settings.DEFAULT.profiles) 144 | }), 145 | () => { this.postMutate() }, 146 | )) 147 | }) 148 | this.newSectionWidget(() => i18n.t("settings.instancing")) 149 | ui 150 | .newSetting(containerEl, setting => { 151 | setting 152 | .setName(i18n.t("settings.new-instance-behavior")) 153 | .addDropdown(linkSetting( 154 | (): string => settings.value.newInstanceBehavior, 155 | setTextToEnum( 156 | Settings.NEW_INSTANCE_BEHAVIORS, 157 | async value => settings.mutate(settingsM => { 158 | settingsM.newInstanceBehavior = value 159 | }), 160 | ), 161 | () => { this.postMutate() }, 162 | { 163 | pre: dropdown => { 164 | dropdown 165 | .addOptions(Object.fromEntries(Settings.NEW_INSTANCE_BEHAVIORS 166 | .map(value => [ 167 | value, 168 | i18n.t(`settings.new-instance-behaviors.${value}`), 169 | ]))) 170 | }, 171 | }, 172 | )) 173 | .addExtraButton(resetButton( 174 | i18n.t("asset:settings.new-instance-behavior-icon"), 175 | i18n.t("settings.reset"), 176 | async () => settings.mutate(settingsM => { 177 | settingsM.newInstanceBehavior = 178 | Settings.DEFAULT.newInstanceBehavior 179 | }), 180 | () => { this.postMutate() }, 181 | )) 182 | }) 183 | .newSetting(containerEl, setting => { 184 | setting 185 | .setName(i18n.t("settings.create-instance-near-existing-ones")) 186 | .setDesc(i18n 187 | .t("settings.create-instance-near-existing-ones-description")) 188 | .addToggle(linkSetting( 189 | () => settings.value.createInstanceNearExistingOnes, 190 | async value => settings.mutate(settingsM => { 191 | settingsM.createInstanceNearExistingOnes = value 192 | }), 193 | () => { this.postMutate() }, 194 | )) 195 | .addExtraButton(resetButton( 196 | i18n.t("asset:settings.create-instance-near-existing-ones-icon"), 197 | i18n.t("settings.reset"), 198 | async () => settings.mutate(settingsM => { 199 | settingsM.createInstanceNearExistingOnes = 200 | Settings.DEFAULT.createInstanceNearExistingOnes 201 | }), 202 | () => { this.postMutate() }, 203 | )) 204 | }) 205 | .newSetting(containerEl, setting => { 206 | setting 207 | .setName(i18n.t("settings.focus-on-new-instance")) 208 | .addToggle(linkSetting( 209 | () => settings.value.focusOnNewInstance, 210 | async value => settings.mutate(settingsM => { 211 | settingsM.focusOnNewInstance = value 212 | }), 213 | () => { this.postMutate() }, 214 | )) 215 | .addExtraButton(resetButton( 216 | i18n.t("asset:settings.focus-on-new-instance-icon"), 217 | i18n.t("settings.reset"), 218 | async () => settings.mutate(settingsM => { 219 | settingsM.focusOnNewInstance = Settings.DEFAULT.focusOnNewInstance 220 | }), 221 | () => { this.postMutate() }, 222 | )) 223 | }) 224 | .newSetting(containerEl, setting => { 225 | setting 226 | .setName(i18n.t("settings.pin-new-instance")) 227 | .addToggle(linkSetting( 228 | () => settings.value.pinNewInstance, 229 | async value => settings.mutate(settingsM => { 230 | settingsM.pinNewInstance = value 231 | }), 232 | () => { this.postMutate() }, 233 | )) 234 | .addExtraButton(resetButton( 235 | i18n.t("asset:settings.pin-new-instance-icon"), 236 | i18n.t("settings.reset"), 237 | async () => settings.mutate(settingsM => { 238 | settingsM.pinNewInstance = Settings.DEFAULT.pinNewInstance 239 | }), 240 | () => { this.postMutate() }, 241 | )) 242 | }) 243 | this.newSectionWidget(() => i18n.t("settings.interface")) 244 | ui 245 | .newSetting(containerEl, setting => { 246 | setting 247 | .setName(i18n.t("settings.open-changelog-on-update")) 248 | .addToggle(linkSetting( 249 | () => settings.value.openChangelogOnUpdate, 250 | async value => settings.mutate(settingsM => { 251 | settingsM.openChangelogOnUpdate = value 252 | }), 253 | () => { this.postMutate() }, 254 | )) 255 | .addExtraButton(resetButton( 256 | i18n.t("asset:settings.open-changelog-on-update-icon"), 257 | i18n.t("settings.reset"), 258 | async () => settings.mutate(settingsM => { 259 | settingsM.openChangelogOnUpdate = 260 | Settings.DEFAULT.openChangelogOnUpdate 261 | }), 262 | () => { this.postMutate() }, 263 | )) 264 | }).newSetting(containerEl, setting => { 265 | setting 266 | .setName(i18n.t("settings.hide-status-bar")) 267 | .addDropdown(linkSetting( 268 | (): string => settings.value.hideStatusBar, 269 | setTextToEnum( 270 | Settings.HIDE_STATUS_BAR_OPTIONS, 271 | async value => settings.mutate(settingsM => { 272 | settingsM.hideStatusBar = value 273 | }), 274 | ), 275 | () => { this.postMutate() }, 276 | { 277 | pre: dropdown => { 278 | dropdown 279 | .addOptions(Object 280 | .fromEntries(Settings.HIDE_STATUS_BAR_OPTIONS 281 | .map(value => [ 282 | value, 283 | i18n.t(`settings.hide-status-bar-options.${value}`), 284 | ]))) 285 | }, 286 | }, 287 | )) 288 | .addExtraButton(resetButton( 289 | i18n.t("asset:settings.hide-status-bar-icon"), 290 | i18n.t("settings.reset"), 291 | async () => settings.mutate(settingsM => { 292 | settingsM.hideStatusBar = Settings.DEFAULT.hideStatusBar 293 | }), 294 | () => { this.postMutate() }, 295 | )) 296 | }) 297 | this.newNoticeTimeoutWidget(Settings.DEFAULT) 298 | this.newSectionWidget(() => i18n.t("settings.advanced")) 299 | ui 300 | .newSetting(containerEl, setting => { 301 | const { settingEl } = setting 302 | setting 303 | .setName(i18n.t("settings.expose-internal-modules")) 304 | .setDesc(createDocumentFragment(settingEl.ownerDocument, frag => { 305 | createChildElement(frag, "span", ele => { 306 | ele.innerHTML = i18n 307 | .t("settings.expose-internal-modules-description-HTML") 308 | }) 309 | })) 310 | .addToggle(linkSetting( 311 | () => settings.value.exposeInternalModules, 312 | async value => settings.mutate(settingsM => { 313 | settingsM.exposeInternalModules = value 314 | }), 315 | () => { this.postMutate() }, 316 | )) 317 | .addExtraButton(resetButton( 318 | i18n.t("asset:settings.expose-internal-modules-icon"), 319 | i18n.t("settings.reset"), 320 | async () => settings.mutate(settingsM => { 321 | settingsM.exposeInternalModules = 322 | Settings.DEFAULT.exposeInternalModules 323 | }), 324 | () => { this.postMutate() }, 325 | )) 326 | }) 327 | .newSetting(containerEl, setting => { 328 | setting 329 | .setName(i18n.t("settings.intercept-logging")) 330 | .addToggle(linkSetting( 331 | () => settings.value.interceptLogging, 332 | async value => settings.mutate(settingsM => { 333 | settingsM.interceptLogging = value 334 | }), 335 | () => { this.postMutate() }, 336 | )) 337 | .addExtraButton(resetButton( 338 | i18n.t("asset:settings.intercept-logging-icon"), 339 | i18n.t("settings.reset"), 340 | async () => settings.mutate(settingsM => { 341 | settingsM.interceptLogging = 342 | Settings.DEFAULT.interceptLogging 343 | }), 344 | () => { this.postMutate() }, 345 | )) 346 | }) 347 | .newSetting(containerEl, setting => { 348 | setting 349 | .setName(i18n.t("settings.preferred-renderer")) 350 | .addDropdown(linkSetting( 351 | (): string => settings.value.preferredRenderer, 352 | setTextToEnum( 353 | Settings.PREFERRED_RENDERER_OPTIONS, 354 | async value => settings.mutate(settingsM => { 355 | settingsM.preferredRenderer = value 356 | }), 357 | ), 358 | () => { this.postMutate() }, 359 | { 360 | pre: dropdown => { 361 | dropdown 362 | .addOptions(Object 363 | .fromEntries(Settings.PREFERRED_RENDERER_OPTIONS 364 | .map(type => [ 365 | type, 366 | i18n.t("settings.preferred-renderer-options", { 367 | interpolation: { escapeValue: false }, 368 | type, 369 | }), 370 | ]))) 371 | }, 372 | }, 373 | )) 374 | .addExtraButton(resetButton( 375 | i18n.t("asset:settings.preferred-renderer-icon"), 376 | i18n.t("settings.reset"), 377 | async () => settings.mutate(settingsM => { 378 | settingsM.preferredRenderer = Settings.DEFAULT.preferredRenderer 379 | }), 380 | () => { this.postMutate() }, 381 | )) 382 | }) 383 | } 384 | 385 | protected override snapshot0(): Partial { 386 | return Settings.persistent(this.context.settings.value) 387 | } 388 | } 389 | 390 | export function loadSettings( 391 | context: TerminalPlugin, 392 | docs: loadDocumentations.Loaded, 393 | ): void { 394 | context.addSettingTab(new SettingTab(context, docs)) 395 | registerSettingsCommands(context) 396 | } 397 | -------------------------------------------------------------------------------- /src/styles.css: -------------------------------------------------------------------------------- 1 | @import "./terminal/emulator.css"; 2 | @import "./terminal/view.css"; 3 | @import "@polyipseity/obsidian-plugin-library/style"; -------------------------------------------------------------------------------- /src/terminal/emulator-addons.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Functions, 3 | activeSelf, 4 | consumeEvent, 5 | deepFreeze, 6 | isNonNil, 7 | replaceAllRegex, 8 | } from "@polyipseity/obsidian-plugin-library" 9 | import type { ITerminalAddon, Terminal } from "@xterm/xterm" 10 | import type { CanvasAddon } from "@xterm/addon-canvas" 11 | import type { WebglAddon } from "@xterm/addon-webgl" 12 | 13 | export class DisposerAddon extends Functions implements ITerminalAddon { 14 | public constructor(...args: readonly (() => void)[]) { 15 | super({ async: false, settled: true }, ...args) 16 | } 17 | 18 | // eslint-disable-next-line @typescript-eslint/class-methods-use-this 19 | public activate(_terminal: Terminal): void { 20 | // Noop 21 | } 22 | 23 | public dispose(): void { 24 | this.call() 25 | } 26 | } 27 | 28 | export class DragAndDropAddon implements ITerminalAddon { 29 | readonly #disposer = new DisposerAddon() 30 | 31 | public constructor(protected readonly element: HTMLElement) { } 32 | 33 | public activate(terminal: Terminal): void { 34 | const { element } = this, 35 | drop = (event: DragEvent): void => { 36 | terminal.paste(Array.from(event.dataTransfer?.files ?? []) 37 | .map(file => file.path) 38 | .filter(isNonNil) 39 | .map(path => path.replace(replaceAllRegex("\""), "\\\"")) 40 | .map(path => path.includes(" ") ? `"${path}"` : path) 41 | .join(" ")) 42 | consumeEvent(event) 43 | }, 44 | dragover = consumeEvent 45 | this.#disposer.push( 46 | () => { element.removeEventListener("dragover", dragover) }, 47 | () => { element.removeEventListener("drop", drop) }, 48 | ) 49 | element.addEventListener("drop", drop) 50 | element.addEventListener("dragover", dragover) 51 | } 52 | 53 | public dispose(): void { 54 | this.#disposer.dispose() 55 | } 56 | } 57 | 58 | export class RendererAddon implements ITerminalAddon { 59 | public renderer: CanvasAddon | WebglAddon | null = null 60 | #terminal: Terminal | null = null 61 | 62 | public constructor( 63 | protected readonly canvasSupplier: () => CanvasAddon, 64 | protected readonly webglSupplier: () => WebglAddon, 65 | ) { } 66 | 67 | public use(renderer: RendererAddon.RendererOption): void { 68 | const term = this.#terminal 69 | if (!term) { return } 70 | const { element } = term 71 | this.renderer?.dispose() 72 | switch (renderer) { 73 | case "dom": 74 | this.renderer = null 75 | break 76 | case "canvas": 77 | try { 78 | const renderer0 = this.canvasSupplier() 79 | term.loadAddon(this.renderer = renderer0) 80 | break 81 | } catch (error) { 82 | activeSelf(element).console.warn(error) 83 | this.use("dom") 84 | } 85 | break 86 | case "webgl": { 87 | try { 88 | const renderer0 = this.webglSupplier(), 89 | contextLoss = renderer0.onContextLoss(() => { 90 | try { 91 | this.use("webgl") 92 | } finally { 93 | contextLoss.dispose() 94 | } 95 | }) 96 | term.loadAddon(this.renderer = renderer0) 97 | } catch (error) { 98 | activeSelf(element).console.warn(error) 99 | this.use("canvas") 100 | } 101 | break 102 | } 103 | // No default 104 | } 105 | } 106 | 107 | public activate(terminal: Terminal): void { 108 | this.#terminal = terminal 109 | } 110 | 111 | public dispose(): void { 112 | this.renderer?.dispose() 113 | this.#terminal = null 114 | } 115 | } 116 | export namespace RendererAddon { 117 | export const RENDERER_OPTIONS = deepFreeze(["dom", "canvas", "webgl"]) 118 | export type RendererOption = typeof RENDERER_OPTIONS[number] 119 | } 120 | -------------------------------------------------------------------------------- /src/terminal/emulator.css: -------------------------------------------------------------------------------- 1 | @import "@xterm/xterm/css/xterm.css"; -------------------------------------------------------------------------------- /src/terminal/emulator.ts: -------------------------------------------------------------------------------- 1 | import { 2 | type Fixed, 3 | SI_PREFIX_SCALE, 4 | activeSelf, 5 | asyncDebounce, 6 | deepFreeze, 7 | dynamicRequire, 8 | dynamicRequireLazy, 9 | fixTyped, 10 | importable, 11 | launderUnchecked, 12 | markFixed, 13 | } from "@polyipseity/obsidian-plugin-library" 14 | import type { 15 | ITerminalInitOnlyOptions, 16 | ITerminalOptions, 17 | Terminal, 18 | } from "@xterm/xterm" 19 | import { 20 | TERMINAL_EMULATOR_RESIZE_WAIT, 21 | TERMINAL_PTY_RESIZE_WAIT, 22 | } from "../magic.js" 23 | import { noop, throttle } from "lodash-es" 24 | import type { AsyncOrSync } from "ts-essentials" 25 | import { BUNDLE } from "../import.js" 26 | import type { ChildProcessByStdio } from "node:child_process" 27 | import type { Pseudoterminal } from "./pseudoterminal.js" 28 | import { spawnPromise } from "../util.js" 29 | import { writePromise } from "./util.js" 30 | 31 | const 32 | childProcess = 33 | dynamicRequire( 34 | BUNDLE, "node:child_process"), 35 | xterm = 36 | dynamicRequireLazy( 37 | BUNDLE, "@xterm/xterm"), 38 | xtermAddonFit = 39 | dynamicRequireLazy( 40 | BUNDLE, "@xterm/addon-fit"), 41 | xtermAddonSerialize = 42 | dynamicRequireLazy( 43 | BUNDLE, "@xterm/addon-serialize") 44 | 45 | export const SUPPORTS_EXTERNAL_TERMINAL_EMULATOR = 46 | importable(BUNDLE, "node:child_process") 47 | export async function spawnExternalTerminalEmulator( 48 | executable: string, 49 | args?: readonly string[], 50 | cwd?: string, 51 | ): Promise> { 52 | const childProcess2 = await childProcess, 53 | ret = await spawnPromise(() => 54 | childProcess2.spawn(executable, args ?? [], { 55 | cwd, 56 | detached: true, 57 | shell: true, 58 | stdio: ["ignore", "ignore", "ignore"], 59 | })) 60 | try { ret.unref() } catch (error) { self.console.warn(error) } 61 | return ret 62 | } 63 | 64 | export class XtermTerminalEmulator { 65 | public static readonly type = "xterm-256color" 66 | public readonly terminal 67 | public readonly addons 68 | public readonly pseudoterminal 69 | protected readonly resizeEmulator = asyncDebounce(throttle(( 70 | resolve: (value: AsyncOrSync) => void, 71 | reject: (reason?: unknown) => void, 72 | columns: number, 73 | rows: number, 74 | ) => { 75 | try { 76 | this.terminal.resize(columns, rows) 77 | resolve() 78 | } catch (error) { 79 | reject(error) 80 | } 81 | }, TERMINAL_EMULATOR_RESIZE_WAIT * SI_PREFIX_SCALE)) 82 | 83 | protected readonly resizePTY = asyncDebounce(throttle(( 84 | resolve: (value: AsyncOrSync) => void, 85 | _reject: (reason?: unknown) => void, 86 | columns: number, 87 | rows: number, 88 | mustResizePseudoterminal: boolean, 89 | ) => { 90 | resolve((async (): Promise => { 91 | try { 92 | const pty = await this.pseudoterminal 93 | if (pty.resize) { 94 | await pty.resize(columns, rows) 95 | } 96 | } catch (error) { 97 | if (mustResizePseudoterminal) { throw error } 98 | /* @__PURE__ */ activeSelf(this.terminal.element).console.debug(error) 99 | } 100 | })()) 101 | }, TERMINAL_PTY_RESIZE_WAIT * SI_PREFIX_SCALE)) 102 | 103 | #running = true 104 | 105 | public constructor( 106 | protected readonly element: HTMLElement, 107 | pseudoterminal: ( 108 | terminal: Terminal, 109 | addons: XtermTerminalEmulator["addons"], 110 | ) => AsyncOrSync, 111 | state?: XtermTerminalEmulator.State, 112 | options?: ITerminalInitOnlyOptions & ITerminalOptions, 113 | addons?: A, 114 | ) { 115 | this.terminal = new xterm.Terminal(options) 116 | const { terminal } = this 117 | terminal.open(element) 118 | // eslint-disable-next-line prefer-object-spread 119 | const addons0 = Object.assign({ 120 | fit: new xtermAddonFit.FitAddon(), 121 | serialize: new xtermAddonSerialize.SerializeAddon(), 122 | }, addons) 123 | for (const addon of Object.values(addons0)) { 124 | terminal.loadAddon(addon) 125 | } 126 | this.addons = addons0 127 | let write = Promise.resolve() 128 | if (state) { 129 | terminal.resize(state.columns, state.rows) 130 | write = writePromise(terminal, state.data) 131 | } 132 | this.pseudoterminal = write.then(async () => { 133 | const pty0 = await pseudoterminal(terminal, addons0) 134 | await pty0.pipe(terminal) 135 | return pty0 136 | }) 137 | this.pseudoterminal.then(async pty0 => pty0.onExit) 138 | .catch(noop satisfies () => unknown as () => unknown) 139 | .finally(() => { this.#running = false }) 140 | } 141 | 142 | public async close(mustClosePseudoterminal = true): Promise { 143 | try { 144 | if (this.#running) { 145 | await (await this.pseudoterminal).kill() 146 | } 147 | } catch (error) { 148 | if (mustClosePseudoterminal) { throw error } 149 | /* @__PURE__ */ activeSelf(this.terminal.element).console.debug(error) 150 | } 151 | this.terminal.dispose() 152 | } 153 | 154 | public async resize(mustResizePseudoterminal = true): Promise { 155 | const { addons, resizeEmulator, resizePTY } = this, 156 | { fit } = addons, 157 | dim = fit.proposeDimensions() 158 | if (dim) { 159 | const { cols, rows } = dim 160 | if (isFinite(cols) && isFinite(rows)) { 161 | await Promise.all([ 162 | resizeEmulator(cols, rows), 163 | resizePTY(cols, rows, mustResizePseudoterminal), 164 | ]) 165 | } 166 | } 167 | } 168 | 169 | public reopen(): void { 170 | const { element, terminal } = this 171 | terminal.element?.remove() 172 | terminal.open(element) 173 | } 174 | 175 | public serialize(): XtermTerminalEmulator.State { 176 | return deepFreeze({ 177 | columns: this.terminal.cols, 178 | data: this.addons.serialize.serialize({ 179 | excludeAltBuffer: true, 180 | excludeModes: true, 181 | }), 182 | rows: this.terminal.rows, 183 | }) 184 | } 185 | } 186 | export namespace XtermTerminalEmulator { 187 | export interface State { 188 | readonly columns: number 189 | readonly rows: number 190 | readonly data: string 191 | } 192 | export namespace State { 193 | export const DEFAULT: State = deepFreeze({ 194 | columns: 1, 195 | data: "", 196 | rows: 1, 197 | }) 198 | export function fix(self0: unknown): Fixed { 199 | const unc = launderUnchecked(self0) 200 | return markFixed(self0, { 201 | columns: fixTyped(DEFAULT, unc, "columns", ["number"]), 202 | data: fixTyped(DEFAULT, unc, "data", ["string"]), 203 | rows: fixTyped(DEFAULT, unc, "rows", ["number"]), 204 | }) 205 | } 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /src/terminal/load.ts: -------------------------------------------------------------------------------- 1 | import { 2 | FileSystemAdapter, 3 | MarkdownView, 4 | type MenuItem, 5 | TFolder, 6 | } from "obsidian" 7 | import { 8 | Platform, 9 | addCommand, 10 | addRibbonIcon, 11 | deepFreeze, 12 | isNonNil, 13 | notice2, 14 | } from "@polyipseity/obsidian-plugin-library" 15 | import { SelectProfileModal, spawnTerminal } from "./spawn.js" 16 | import { PROFILE_PROPERTIES } from "./profile-properties.js" 17 | import { Settings } from "../settings-data.js" 18 | import type { TerminalPlugin } from "../main.js" 19 | import { TerminalView } from "./view.js" 20 | import { isEmpty } from "lodash-es" 21 | 22 | export function loadTerminal(context: TerminalPlugin): void { 23 | context.registerView( 24 | TerminalView.type.namespaced(context), 25 | leaf => new TerminalView(context, leaf), 26 | ) 27 | const 28 | PROFILE_TYPES = deepFreeze(([ 29 | "select", 30 | "integrated", 31 | "external", 32 | ] satisfies readonly (keyof typeof PROFILE_PROPERTIES | "select")[]) 33 | .filter(type => type === "select" || PROFILE_PROPERTIES[type].available)), 34 | CWD_TYPES = deepFreeze(["", "root", "current"]), 35 | EXCLUDED_TYPES = deepFreeze([ 36 | { cwd: "", profile: "integrated" }, 37 | { cwd: "", profile: "external" }, 38 | ]), 39 | { 40 | app: { vault, workspace }, 41 | language: { value: i18n }, 42 | settings, 43 | } = context, 44 | defaultProfile = 45 | (type: Settings.Profile.Type): Settings.Profile | null => { 46 | const ret = Settings.Profile.defaultOfType( 47 | type, 48 | settings.value.profiles, 49 | Platform.CURRENT, 50 | ) 51 | if (!ret) { 52 | notice2( 53 | () => i18n.t("notices.no-default-profile", { 54 | interpolation: { escapeValue: false }, 55 | type, 56 | }), 57 | settings.value.errorNoticeTimeout, 58 | context, 59 | ) 60 | } 61 | return ret 62 | }, 63 | adapter = vault.adapter instanceof FileSystemAdapter 64 | ? vault.adapter 65 | : null, 66 | contextMenu = ( 67 | type: Settings.Profile.Type | "select", 68 | cwd?: TFolder, 69 | ): ((item: MenuItem) => void) | null => { 70 | const cwd0 = cwd 71 | ? adapter ? adapter.getFullPath(cwd.path) : null 72 | : cwd 73 | if (cwd0 === null) { return null } 74 | return (item: MenuItem) => { 75 | item 76 | .setTitle(i18n.t("menus.open-terminal", { 77 | interpolation: { escapeValue: false }, 78 | type, 79 | })) 80 | .setIcon(i18n.t("asset:menus.open-terminal-icon", { 81 | interpolation: { escapeValue: false }, 82 | type, 83 | })) 84 | .onClick(() => { 85 | if (type === "select") { 86 | new SelectProfileModal(context, cwd0).open() 87 | return 88 | } 89 | const profile = defaultProfile(type) 90 | if (!profile) { return } 91 | spawnTerminal( 92 | context, 93 | profile, 94 | cwd0, 95 | ) 96 | }) 97 | } 98 | }, 99 | command = ( 100 | type: Settings.Profile.Type | "select", 101 | cwd: typeof CWD_TYPES[number], 102 | ) => (checking: boolean): boolean => { 103 | // eslint-disable-next-line @typescript-eslint/consistent-return 104 | const cwd0 = ((): string | null | undefined => { 105 | if (!cwd) { return void 0 } 106 | if (!adapter) { return null } 107 | switch (cwd) { 108 | case "root": 109 | return adapter.getBasePath() 110 | case "current": { 111 | const active = workspace.getActiveFile() 112 | if (active?.parent) { 113 | return adapter.getFullPath(active.parent.path) 114 | } 115 | return null 116 | } 117 | // No default 118 | } 119 | })() 120 | if (cwd0 === null) { return false } 121 | if (!checking) { 122 | if (type === "select") { 123 | new SelectProfileModal(context, cwd0).open() 124 | return true 125 | } 126 | const profile = defaultProfile(type) 127 | if (profile) { spawnTerminal(context, profile, cwd0) } 128 | } 129 | return true 130 | } 131 | 132 | addRibbonIcon( 133 | context, 134 | i18n.t("asset:ribbons.open-terminal-id"), 135 | i18n.t("asset:ribbons.open-terminal-icon"), 136 | () => i18n.t("ribbons.open-terminal"), 137 | () => { new SelectProfileModal(context, adapter?.getBasePath()).open() }, 138 | ) 139 | context.registerEvent(workspace.on("file-menu", (menu, file) => { 140 | if (!settings.value.addToContextMenu) { 141 | return 142 | } 143 | const folder = file instanceof TFolder ? file : file.parent 144 | if (!folder) { return } 145 | menu.addSeparator() 146 | const items = PROFILE_TYPES 147 | .map(type => contextMenu(type, folder)) 148 | .filter(isNonNil) 149 | if (!isEmpty(items)) { 150 | menu.addSeparator() 151 | items.forEach(item => menu.addItem(item)) 152 | } 153 | })) 154 | context.registerEvent(workspace.on( 155 | "editor-menu", 156 | (menu, _0, info) => { 157 | const { file } = info 158 | if (!settings.value.addToContextMenu || 159 | info instanceof MarkdownView || 160 | !file?.parent) { 161 | return 162 | } 163 | const { parent } = file 164 | menu.addSeparator() 165 | const items = PROFILE_TYPES 166 | .map(type => contextMenu(type, parent)) 167 | .filter(isNonNil) 168 | if (!isEmpty(items)) { 169 | menu.addSeparator() 170 | items.forEach(item => menu.addItem(item)) 171 | } 172 | }, 173 | )) 174 | // Always register command for interop with other plugins 175 | addCommand(context, () => i18n.t("commands.open-developer-console"), { 176 | checkCallback(checking) { 177 | if (!settings.value.addToCommand) { return false } 178 | if (!checking) { 179 | const prof = defaultProfile("developerConsole") 180 | if (prof) { spawnTerminal(context, prof) } 181 | } 182 | return true 183 | }, 184 | icon: i18n.t("asset:commands.open-developer-console-icon"), 185 | id: "open-terminal.developerConsole", 186 | }) 187 | for (const type of PROFILE_TYPES) { 188 | for (const cwd of CWD_TYPES) { 189 | if (EXCLUDED_TYPES.some(({ cwd: cwd0, profile }) => 190 | cwd0 === cwd && profile === type)) { 191 | continue 192 | } 193 | addCommand( 194 | context, 195 | () => i18n.t(`commands.open-terminal-${cwd}`, { 196 | interpolation: { escapeValue: false }, 197 | type, 198 | }), 199 | { 200 | checkCallback(checking) { 201 | if (!settings.value.addToCommand) { return false } 202 | return command(type, cwd)(checking) 203 | }, 204 | icon: i18n.t(`asset:commands.open-terminal-${cwd}-icon`), 205 | id: `open-terminal.${type}.${cwd}`, 206 | }, 207 | ) 208 | } 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /src/terminal/profile-presets.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DEFAULT_PYTHON_EXECUTABLE, 3 | DEFAULT_SUCCESS_EXIT_CODES, 4 | WINDOWS_CMD_PATH, 5 | } from "../magic.js" 6 | import type { 7 | ILinkHandler, 8 | ILogger, 9 | ITheme, 10 | IWindowOptions, 11 | IWindowsPty, 12 | } from "@xterm/xterm" 13 | import { 14 | activeSelf, 15 | deepFreeze, 16 | openExternal, 17 | typedKeys, 18 | } from "@polyipseity/obsidian-plugin-library" 19 | import type { Pseudoterminal } from "./pseudoterminal.js" 20 | import type { Settings } from "../settings-data.js" 21 | 22 | export const 23 | DEFAULT_LINK_HANDLER: ILinkHandler = deepFreeze({ 24 | activate(event, text, _2) { openExternal(activeSelf(event), text) }, 25 | }), 26 | DEFAULT_LOGGER: ILogger = deepFreeze({ 27 | debug(message, ...args: readonly unknown[]) { 28 | self.console.debug(message, ...args) 29 | }, 30 | error(message, ...args: readonly unknown[]) { 31 | self.console.error(message, ...args) 32 | }, 33 | info(message, ...args: readonly unknown[]) { 34 | self.console.info(message, ...args) 35 | }, 36 | trace(message, ...args: readonly unknown[]) { 37 | self.console.trace(message, ...args) 38 | }, 39 | warn(message, ...args: readonly unknown[]) { 40 | self.console.warn(message, ...args) 41 | }, 42 | }), 43 | DEFAULT_TERMINAL_OPTIONS: Settings.Profile.TerminalOptions = 44 | deepFreeze({ documentOverride: null }), 45 | DEFAULT_THEME: ITheme = deepFreeze({}), 46 | DEFAULT_WINDOW_OPTIONS: IWindowOptions = deepFreeze({}), 47 | DEFAULT_WINDOWS_PTY: IWindowsPty = deepFreeze({}) 48 | 49 | export interface ProfilePresets0 { 50 | readonly empty: Settings.Profile.Empty 51 | readonly developerConsole: Settings.Profile.DeveloperConsole 52 | 53 | readonly cmdExternal: Settings.Profile.External 54 | readonly gnomeTerminalExternal: Settings.Profile.External 55 | readonly iTerm2External: Settings.Profile.External 56 | readonly konsoleExternal: Settings.Profile.External 57 | readonly powershellExternal: Settings.Profile.External 58 | readonly pwshExternal: Settings.Profile.External 59 | readonly terminalMacOSExternal: Settings.Profile.External 60 | readonly wtExternal: Settings.Profile.External 61 | readonly xtermExternal: Settings.Profile.External 62 | 63 | readonly bashIntegrated: Settings.Profile.Integrated 64 | readonly cmdIntegrated: Settings.Profile.Integrated 65 | readonly dashIntegrated: Settings.Profile.Integrated 66 | readonly gitBashIntegrated: Settings.Profile.Integrated 67 | readonly powershellIntegrated: Settings.Profile.Integrated 68 | readonly pwshIntegrated: Settings.Profile.Integrated 69 | readonly shIntegrated: Settings.Profile.Integrated 70 | readonly wslIntegrated: Settings.Profile.Integrated 71 | readonly zshIntegrated: Settings.Profile.Integrated 72 | } 73 | type ExternalDefaults = Readonly< 74 | Record<`${Pseudoterminal.SupportedPlatforms[number] 75 | }ExternalDefault`, Settings.Profile.External> 76 | > 77 | type IntegratedDefaults = Readonly< 78 | Record<`${Pseudoterminal.SupportedPlatforms[number] 79 | }IntegratedDefault`, Settings.Profile.Integrated> 80 | > 81 | export interface ProfilePresets 82 | extends ProfilePresets0, ExternalDefaults, IntegratedDefaults { } 83 | const PROFILE_PRESETS0 = deepFreeze({ 84 | bashIntegrated: { 85 | args: [], 86 | executable: "/bin/bash", 87 | name: "", 88 | platforms: { darwin: true, linux: true }, 89 | pythonExecutable: DEFAULT_PYTHON_EXECUTABLE, 90 | restoreHistory: true, 91 | successExitCodes: DEFAULT_SUCCESS_EXIT_CODES, 92 | terminalOptions: DEFAULT_TERMINAL_OPTIONS, 93 | type: "integrated", 94 | useWin32Conhost: true, 95 | }, 96 | cmdExternal: { 97 | args: [], 98 | executable: WINDOWS_CMD_PATH, 99 | name: "", 100 | platforms: { win32: true }, 101 | restoreHistory: true, 102 | successExitCodes: DEFAULT_SUCCESS_EXIT_CODES, 103 | terminalOptions: DEFAULT_TERMINAL_OPTIONS, 104 | type: "external", 105 | }, 106 | cmdIntegrated: { 107 | args: [], 108 | executable: WINDOWS_CMD_PATH, 109 | name: "", 110 | platforms: { win32: true }, 111 | pythonExecutable: DEFAULT_PYTHON_EXECUTABLE, 112 | restoreHistory: true, 113 | successExitCodes: DEFAULT_SUCCESS_EXIT_CODES, 114 | terminalOptions: DEFAULT_TERMINAL_OPTIONS, 115 | type: "integrated", 116 | useWin32Conhost: true, 117 | }, 118 | dashIntegrated: { 119 | args: [], 120 | executable: "/bin/dash", 121 | name: "", 122 | platforms: { darwin: true, linux: true }, 123 | pythonExecutable: DEFAULT_PYTHON_EXECUTABLE, 124 | restoreHistory: true, 125 | successExitCodes: DEFAULT_SUCCESS_EXIT_CODES, 126 | terminalOptions: DEFAULT_TERMINAL_OPTIONS, 127 | type: "integrated", 128 | useWin32Conhost: true, 129 | }, 130 | developerConsole: { 131 | name: "", 132 | restoreHistory: true, 133 | successExitCodes: DEFAULT_SUCCESS_EXIT_CODES, 134 | terminalOptions: DEFAULT_TERMINAL_OPTIONS, 135 | type: "developerConsole", 136 | }, 137 | empty: { 138 | name: "", 139 | restoreHistory: true, 140 | successExitCodes: DEFAULT_SUCCESS_EXIT_CODES, 141 | terminalOptions: DEFAULT_TERMINAL_OPTIONS, 142 | type: "", 143 | }, 144 | gitBashIntegrated: { 145 | args: [], 146 | executable: "C:\\Program Files\\Git\\bin\\bash.exe", 147 | name: "", 148 | platforms: { win32: true }, 149 | pythonExecutable: DEFAULT_PYTHON_EXECUTABLE, 150 | restoreHistory: true, 151 | successExitCodes: DEFAULT_SUCCESS_EXIT_CODES, 152 | terminalOptions: DEFAULT_TERMINAL_OPTIONS, 153 | type: "integrated", 154 | useWin32Conhost: true, 155 | }, 156 | gnomeTerminalExternal: { 157 | args: [], 158 | executable: "gnome-terminal", 159 | name: "", 160 | platforms: { linux: true }, 161 | restoreHistory: true, 162 | successExitCodes: DEFAULT_SUCCESS_EXIT_CODES, 163 | terminalOptions: DEFAULT_TERMINAL_OPTIONS, 164 | type: "external", 165 | }, 166 | iTerm2External: { 167 | args: ["\"$PWD\""], 168 | executable: 169 | "/Applications/iTerm.app/Contents/MacOS/iTerm2", 170 | name: "", 171 | platforms: { darwin: true }, 172 | restoreHistory: true, 173 | successExitCodes: DEFAULT_SUCCESS_EXIT_CODES, 174 | terminalOptions: DEFAULT_TERMINAL_OPTIONS, 175 | type: "external", 176 | }, 177 | konsoleExternal: { 178 | args: [], 179 | executable: "konsole", 180 | name: "", 181 | platforms: { linux: true }, 182 | restoreHistory: true, 183 | successExitCodes: DEFAULT_SUCCESS_EXIT_CODES, 184 | terminalOptions: DEFAULT_TERMINAL_OPTIONS, 185 | type: "external", 186 | }, 187 | powershellExternal: { 188 | args: [], 189 | executable: "powershell", 190 | name: "", 191 | platforms: { win32: true }, 192 | restoreHistory: true, 193 | successExitCodes: DEFAULT_SUCCESS_EXIT_CODES, 194 | terminalOptions: DEFAULT_TERMINAL_OPTIONS, 195 | type: "external", 196 | }, 197 | powershellIntegrated: { 198 | args: [], 199 | executable: "powershell", 200 | name: "", 201 | platforms: { win32: true }, 202 | pythonExecutable: DEFAULT_PYTHON_EXECUTABLE, 203 | restoreHistory: true, 204 | successExitCodes: DEFAULT_SUCCESS_EXIT_CODES, 205 | terminalOptions: DEFAULT_TERMINAL_OPTIONS, 206 | type: "integrated", 207 | useWin32Conhost: true, 208 | }, 209 | pwshExternal: { 210 | args: [], 211 | executable: "pwsh", 212 | name: "", 213 | platforms: { darwin: true, linux: true, win32: true }, 214 | restoreHistory: true, 215 | successExitCodes: DEFAULT_SUCCESS_EXIT_CODES, 216 | terminalOptions: DEFAULT_TERMINAL_OPTIONS, 217 | type: "external", 218 | }, 219 | pwshIntegrated: { 220 | args: [], 221 | executable: "pwsh", 222 | name: "", 223 | platforms: { darwin: true, linux: true, win32: true }, 224 | pythonExecutable: DEFAULT_PYTHON_EXECUTABLE, 225 | restoreHistory: true, 226 | successExitCodes: DEFAULT_SUCCESS_EXIT_CODES, 227 | terminalOptions: DEFAULT_TERMINAL_OPTIONS, 228 | type: "integrated", 229 | useWin32Conhost: true, 230 | }, 231 | shIntegrated: { 232 | args: [], 233 | executable: "/bin/sh", 234 | name: "", 235 | platforms: { darwin: true, linux: true }, 236 | pythonExecutable: DEFAULT_PYTHON_EXECUTABLE, 237 | restoreHistory: true, 238 | successExitCodes: DEFAULT_SUCCESS_EXIT_CODES, 239 | terminalOptions: DEFAULT_TERMINAL_OPTIONS, 240 | type: "integrated", 241 | useWin32Conhost: true, 242 | }, 243 | terminalMacOSExternal: { 244 | args: ["\"$PWD\""], 245 | executable: 246 | "/System/Applications/Utilities/Terminal.app/Contents/macOS/Terminal", 247 | name: "", 248 | platforms: { darwin: true }, 249 | restoreHistory: true, 250 | successExitCodes: DEFAULT_SUCCESS_EXIT_CODES, 251 | terminalOptions: DEFAULT_TERMINAL_OPTIONS, 252 | type: "external", 253 | }, 254 | wslIntegrated: { 255 | args: [], 256 | executable: "C:\\Windows\\System32\\wsl.exe", 257 | name: "", 258 | platforms: { win32: true }, 259 | pythonExecutable: DEFAULT_PYTHON_EXECUTABLE, 260 | restoreHistory: true, 261 | successExitCodes: DEFAULT_SUCCESS_EXIT_CODES, 262 | terminalOptions: DEFAULT_TERMINAL_OPTIONS, 263 | type: "integrated", 264 | useWin32Conhost: true, 265 | }, 266 | wtExternal: { 267 | args: [], 268 | executable: "wt", 269 | name: "", 270 | platforms: { win32: true }, 271 | restoreHistory: true, 272 | successExitCodes: DEFAULT_SUCCESS_EXIT_CODES, 273 | terminalOptions: DEFAULT_TERMINAL_OPTIONS, 274 | type: "external", 275 | }, 276 | xtermExternal: { 277 | args: [], 278 | executable: "xterm", 279 | name: "", 280 | platforms: { darwin: true, linux: true }, 281 | restoreHistory: true, 282 | successExitCodes: DEFAULT_SUCCESS_EXIT_CODES, 283 | terminalOptions: DEFAULT_TERMINAL_OPTIONS, 284 | type: "external", 285 | }, 286 | zshIntegrated: { 287 | args: [], 288 | executable: "/bin/zsh", 289 | name: "", 290 | platforms: { darwin: true, linux: true }, 291 | pythonExecutable: DEFAULT_PYTHON_EXECUTABLE, 292 | restoreHistory: true, 293 | successExitCodes: DEFAULT_SUCCESS_EXIT_CODES, 294 | terminalOptions: DEFAULT_TERMINAL_OPTIONS, 295 | type: "integrated", 296 | useWin32Conhost: true, 297 | }, 298 | }) satisfies ProfilePresets0 299 | export const PROFILE_PRESETS = deepFreeze({ 300 | ...PROFILE_PRESETS0, 301 | darwinExternalDefault: { 302 | ...PROFILE_PRESETS0.terminalMacOSExternal, 303 | platforms: { darwin: true }, 304 | }, 305 | darwinIntegratedDefault: { 306 | ...PROFILE_PRESETS0.zshIntegrated, 307 | platforms: { darwin: true }, 308 | }, 309 | linuxExternalDefault: { 310 | ...PROFILE_PRESETS0.xtermExternal, 311 | platforms: { linux: true }, 312 | }, 313 | linuxIntegratedDefault: { 314 | ...PROFILE_PRESETS0.shIntegrated, 315 | platforms: { linux: true }, 316 | }, 317 | win32ExternalDefault: { 318 | ...PROFILE_PRESETS0.cmdExternal, 319 | platforms: { win32: true }, 320 | }, 321 | win32IntegratedDefault: { 322 | ...PROFILE_PRESETS0.cmdIntegrated, 323 | platforms: { win32: true }, 324 | }, 325 | }) satisfies ProfilePresets 326 | export type ProfilePresetKeys = readonly [ 327 | "empty", 328 | "developerConsole", 329 | 330 | "cmdExternal", 331 | "gnomeTerminalExternal", 332 | "iTerm2External", 333 | "konsoleExternal", 334 | "powershellExternal", 335 | "pwshExternal", 336 | "terminalMacOSExternal", 337 | "wtExternal", 338 | "xtermExternal", 339 | 340 | "bashIntegrated", 341 | "cmdIntegrated", 342 | "dashIntegrated", 343 | "gitBashIntegrated", 344 | "powershellIntegrated", 345 | "pwshIntegrated", 346 | "shIntegrated", 347 | "wslIntegrated", 348 | "zshIntegrated", 349 | 350 | "darwinExternalDefault", 351 | "linuxExternalDefault", 352 | "win32ExternalDefault", 353 | 354 | "darwinIntegratedDefault", 355 | "linuxIntegratedDefault", 356 | "win32IntegratedDefault", 357 | ] 358 | export const PROFILE_PRESET_KEYS = 359 | typedKeys()(PROFILE_PRESETS) 360 | export const PROFILE_PRESET_ORDERED_KEYS = 361 | deepFreeze(PROFILE_PRESET_KEYS.reduce((prev, cur) => { 363 | if (cur === "empty") { 364 | prev.unshift(cur) 365 | } else { 366 | prev.push(cur) 367 | } 368 | return prev 369 | }, [])) 370 | -------------------------------------------------------------------------------- /src/terminal/profile-properties.ts: -------------------------------------------------------------------------------- 1 | import { 2 | type AnyObject, 3 | Platform, 4 | deepFreeze, 5 | launderUnchecked, 6 | } from "@polyipseity/obsidian-plugin-library" 7 | import { 8 | Pseudoterminal, 9 | RefPsuedoterminal, 10 | TextPseudoterminal, 11 | } from "./pseudoterminal.js" 12 | import { 13 | SUPPORTS_EXTERNAL_TERMINAL_EMULATOR, 14 | spawnExternalTerminalEmulator, 15 | } from "./emulator.js" 16 | import type { AsyncOrSync } from "ts-essentials" 17 | import type { Settings } from "../settings-data.js" 18 | import type { TerminalPlugin } from "../main.js" 19 | 20 | export interface OpenOptions { 21 | readonly cwd?: string | undefined 22 | readonly terminal?: string | undefined 23 | } 24 | export const PROFILE_PROPERTIES: { 25 | readonly [key in Settings.Profile.Type]: { 26 | readonly available: boolean 27 | readonly valid: boolean 28 | readonly integratable: boolean 29 | readonly opener: ( 30 | context: TerminalPlugin, 31 | profile: Settings.Profile.Typed, 32 | options?: OpenOptions, 33 | ) => AsyncOrSync | null> 34 | } 35 | } = deepFreeze({ 36 | // eslint-disable-next-line @typescript-eslint/naming-convention 37 | "": { 38 | available: true, 39 | integratable: true, 40 | opener() { 41 | return new RefPsuedoterminal(new TextPseudoterminal()) 42 | }, 43 | valid: true, 44 | }, 45 | developerConsole: { 46 | available: true, 47 | integratable: true, 48 | async opener(context: TerminalPlugin) { 49 | return (await context.developerConsolePTY.onLoaded)().dup() 50 | }, 51 | valid: true, 52 | }, 53 | external: { 54 | available: SUPPORTS_EXTERNAL_TERMINAL_EMULATOR, 55 | integratable: false, 56 | async opener( 57 | _context: TerminalPlugin, 58 | profile: Settings.Profile.Typed<"external">, 59 | options?: OpenOptions, 60 | ) { 61 | await spawnExternalTerminalEmulator( 62 | profile.executable, 63 | profile.args, 64 | options?.cwd, 65 | ) 66 | return null 67 | }, 68 | valid: true, 69 | }, 70 | integrated: { 71 | available: Pseudoterminal.PLATFORM_PSEUDOTERMINAL !== null, 72 | integratable: true, 73 | opener( 74 | context: TerminalPlugin, 75 | profile: Settings.Profile.Typed<"integrated">, 76 | options?: OpenOptions, 77 | ) { 78 | if (!Pseudoterminal.PLATFORM_PSEUDOTERMINAL) { return null } 79 | const 80 | { 81 | args, 82 | platforms, 83 | useWin32Conhost, 84 | executable, 85 | pythonExecutable, 86 | } = profile, 87 | supported = launderUnchecked(platforms)[Platform.CURRENT] 88 | if (typeof supported !== "boolean" || !supported) { return null } 89 | return new RefPsuedoterminal( 90 | new Pseudoterminal.PLATFORM_PSEUDOTERMINAL(context, { 91 | args, 92 | cwd: options?.cwd, 93 | executable, 94 | pythonExecutable: pythonExecutable || void 0, 95 | terminal: options?.terminal, 96 | useWin32Conhost, 97 | }), 98 | ) 99 | }, 100 | valid: true, 101 | }, 102 | invalid: { 103 | available: true, 104 | integratable: true, 105 | opener() { return null }, 106 | valid: false, 107 | }, 108 | }) 109 | 110 | export function openProfile( 111 | context: TerminalPlugin, 112 | profile: Settings.Profile.Typed, 113 | options?: OpenOptions, 114 | ): AsyncOrSync | null> { 115 | const type0: T = profile.type 116 | return PROFILE_PROPERTIES[type0].opener(context, profile, options) 117 | } 118 | -------------------------------------------------------------------------------- /src/terminal/spawn.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Platform, 3 | newCollabrativeState, 4 | } from "@polyipseity/obsidian-plugin-library" 5 | import { FuzzySuggestModal } from "obsidian" 6 | import { Settings } from "../settings-data.js" 7 | import type { TerminalPlugin } from "../main.js" 8 | import { TerminalView } from "./view.js" 9 | 10 | export class SelectProfileModal 11 | extends FuzzySuggestModal { 12 | public constructor( 13 | protected readonly context: TerminalPlugin, 14 | protected readonly cwd?: string, 15 | ) { 16 | super(context.app) 17 | } 18 | 19 | public override getItems(): Settings.Profile.Entry[] { 20 | return Object.entries(this.context.settings.value.profiles) 21 | } 22 | 23 | public override getItemText(item: Settings.Profile.Entry): string { 24 | return this.context.language.value.t( 25 | `components.select-profile.item-text-${Settings.Profile 26 | .isCompatible(item[1], Platform.CURRENT) 27 | ? "" 28 | : "incompatible"}`, 29 | { 30 | info: Settings.Profile.info(item), 31 | interpolation: { escapeValue: false }, 32 | }, 33 | ) 34 | } 35 | 36 | public override onChooseItem( 37 | [, profile]: Settings.Profile.Entry, 38 | _evt: KeyboardEvent | MouseEvent, 39 | ): void { 40 | const { context: plugin, cwd } = this 41 | spawnTerminal(plugin, profile, cwd) 42 | } 43 | } 44 | 45 | export function spawnTerminal( 46 | context: TerminalPlugin, 47 | profile: Settings.Profile, 48 | cwd?: string, 49 | ): void { 50 | (async (): Promise => { 51 | try { 52 | await TerminalView.getLeaf(context).setViewState({ 53 | active: true, 54 | state: newCollabrativeState(context, new Map([ 55 | [ 56 | TerminalView.type, 57 | { 58 | cwd: cwd ?? null, 59 | focus: context.settings.value.focusOnNewInstance, 60 | profile, 61 | serial: null, 62 | } satisfies TerminalView.State, 63 | ], 64 | ])), 65 | type: TerminalView.type.namespaced(context), 66 | }) 67 | } catch (error) { 68 | self.console.error(error) 69 | } 70 | })() 71 | } 72 | -------------------------------------------------------------------------------- /src/terminal/unix_pseudoterminal.py: -------------------------------------------------------------------------------- 1 | from os import ( 2 | execvp as _execvp, 3 | read as _read, 4 | waitpid as _waitpid, 5 | waitstatus_to_exitcode as _ws_to_ec, 6 | write as _write, 7 | ) 8 | from selectors import DefaultSelector as _DefaultSelector, EVENT_READ as _EVENT_READ 9 | from struct import pack as _pack 10 | import sys as _sys 11 | from sys import exit as _exit, stdin as _stdin, stdout as _stdout 12 | from typing import Callable as _Callable, cast as _cast 13 | 14 | if _sys.platform != "win32": 15 | from fcntl import ioctl as _ioctl 16 | import pty as _pty 17 | from termios import TIOCSWINSZ as _TIOCSWINSZ 18 | 19 | _FORK = _cast( 20 | _Callable[[], tuple[int, int]], 21 | _pty.fork, # type: ignore 22 | ) 23 | _CHUNK_SIZE = 1024 24 | _STDIN = _stdin.fileno() 25 | _STDOUT = _stdout.fileno() 26 | _CMDIO = 3 27 | 28 | def main(): 29 | pid, pty_fd = _FORK() 30 | if pid == 0: 31 | _execvp(_sys.argv[1], _sys.argv[1:]) 32 | 33 | def write_all(fd: int, data: bytes): 34 | while data: 35 | data = data[_write(fd, data) :] 36 | 37 | with _DefaultSelector() as selector: 38 | running = True 39 | 40 | def pipe_pty(): 41 | try: 42 | data = _read(pty_fd, _CHUNK_SIZE) 43 | except OSError: 44 | data = b"" 45 | if not data: 46 | selector.unregister(pty_fd) 47 | global running 48 | running = False 49 | return 50 | write_all(_STDOUT, data) 51 | 52 | def pipe_stdin(): 53 | data = _read(_STDIN, _CHUNK_SIZE) 54 | if not data: 55 | selector.unregister(_STDIN) 56 | return 57 | write_all(pty_fd, data) 58 | 59 | def process_cmdio(): 60 | data = _read(_CMDIO, _CHUNK_SIZE) 61 | if not data: 62 | selector.unregister(_CMDIO) 63 | return 64 | for line in data.decode("UTF-8", "strict").splitlines(): 65 | rows, columns = (int(ss.strip()) for ss in line.split("x", 2)) 66 | _ioctl( 67 | pty_fd, 68 | _TIOCSWINSZ, 69 | _pack("HHHH", columns, rows, 0, 0), 70 | ) 71 | 72 | selector.register(pty_fd, _EVENT_READ, pipe_pty) 73 | selector.register(_STDIN, _EVENT_READ, pipe_stdin) 74 | selector.register(_CMDIO, _EVENT_READ, process_cmdio) 75 | while running: 76 | for key, _ in selector.select(): 77 | key.data() 78 | 79 | _exit(_ws_to_ec(_waitpid(pid, 0)[1])) 80 | 81 | else: 82 | 83 | def main(): 84 | raise NotImplementedError(_sys.platform) 85 | 86 | 87 | if __name__ == "__main__": 88 | main() 89 | -------------------------------------------------------------------------------- /src/terminal/view.css: -------------------------------------------------------------------------------- 1 | .workspace-leaf-content[data-type=terminal\:terminal] .view-content { 2 | overflow: clip; 3 | display: flex; 4 | flex-direction: column; 5 | } 6 | 7 | .terminal\:terminal { 8 | flex: 1; 9 | min-width: 0; 10 | min-height: 0; 11 | } 12 | 13 | .is-phone .workspace-leaf-content[data-type=terminal\:terminal] .view-content { 14 | padding-bottom: max(var(--size-4-4), calc(var(--icon-l) + var(--size-4-2) + max(var(--size-4-2), var(--safe-area-inset-bottom)))); 15 | } -------------------------------------------------------------------------------- /src/terminal/win32_resizer.py: -------------------------------------------------------------------------------- 1 | from contextlib import contextmanager as _contextmanager 2 | from itertools import chain as _chain 3 | from psutil import Process as _Process 4 | from pywinctl import Window as _Window, getAllWindows as _getAllWindows 5 | import sys as _sys 6 | from time import sleep as _sleep 7 | from typing import ( 8 | Any as _Any, 9 | Callable as _Callable, 10 | Generator as _Generator, 11 | Protocol as _Protocol, 12 | TypedDict as _TypedDict, 13 | cast as _cast, 14 | final as _final, 15 | ) 16 | 17 | if _sys.platform == "win32": 18 | from pywintypes import error as _error 19 | from win32api import SetConsoleCtrlHandler as _SetConsoleCtrlHandler 20 | from win32con import ( 21 | CTRL_BREAK_EVENT as _CTRL_BREAK_EVENT, 22 | CTRL_CLOSE_EVENT as _CTRL_CLOSE_EVENT, 23 | CTRL_C_EVENT as _CTRL_C_EVENT, 24 | FILE_SHARE_WRITE as _FILE_SHARE_WRITE, 25 | GENERIC_READ as _GENERIC_READ, 26 | GENERIC_WRITE as _GENERIC_WRITE, 27 | OPEN_EXISTING as _OPEN_EXISTING, 28 | SWP_NOACTIVATE as _SWP_NOACTIVATE, 29 | SWP_NOREDRAW as _SWP_NOREDRAW, 30 | SWP_NOZORDER as _SWP_NOZORDER, 31 | ) 32 | from win32console import ( 33 | AttachConsole as _AttachConsole, # type: ignore 34 | FreeConsole as _FreeConsole, 35 | PyCOORDType as _PyCOORDType, 36 | PySMALL_RECTType as _PySMALL_RECTType, 37 | PyConsoleScreenBufferType as _PyConsoleScreenBufferType, 38 | ) 39 | from win32file import CreateFile as _CreateFile 40 | from win32gui import SetWindowPos as _SetWindowPos 41 | from win32process import GetWindowThreadProcessId as _GetWindowThreadProcessId 42 | 43 | @_final 44 | class _PyCOORDType0(_Protocol): 45 | @property 46 | def X(self) -> int: ... 47 | 48 | @property 49 | def Y(self) -> int: ... 50 | 51 | @_final 52 | class _PySMALL_RECTType0(_Protocol): 53 | @property 54 | def Left(self) -> int: ... 55 | 56 | @property 57 | def Top(self) -> int: ... 58 | 59 | @property 60 | def Right(self) -> int: ... 61 | 62 | @property 63 | def Bottom(self) -> int: ... 64 | 65 | @_final 66 | class _PyConsoleScreenBufferInfo(_TypedDict): 67 | Size: _PyCOORDType0 68 | CursorPosition: _PyCOORDType0 69 | Attributes: int 70 | Window: _PySMALL_RECTType0 71 | MaximumWindowSize: _PyCOORDType0 72 | 73 | _ATTACH_CONSOLE = _cast( 74 | _Callable[[int], None], 75 | _AttachConsole, 76 | ) 77 | _PY_COORD_TYPE = _cast( 78 | _Callable[[int, int], _PyCOORDType], 79 | _PyCOORDType, 80 | ) 81 | _PY_CONSOLE_SCREEN_BUFFER_TYPE = _cast( 82 | _Callable[[_Any], _PyConsoleScreenBufferType], 83 | _PyConsoleScreenBufferType, 84 | ) 85 | _GET_CONSOLE_SCREEN_BUFFER_INFO = _cast( 86 | _Callable[[_PyConsoleScreenBufferType], _PyConsoleScreenBufferInfo], 87 | _PyConsoleScreenBufferType.GetConsoleScreenBufferInfo, # type: ignore 88 | ) 89 | _SET_CONSOLE_WINDOW_INFO = _cast( 90 | _Callable[ 91 | [_PyConsoleScreenBufferType, bool, _PySMALL_RECTType], 92 | None, 93 | ], 94 | _PyConsoleScreenBufferType.SetConsoleWindowInfo, # type: ignore 95 | ) 96 | _PY_SMALL_RECT_TYPE = _cast( 97 | _Callable[[int, int, int, int], _PySMALL_RECTType], 98 | _PySMALL_RECTType, 99 | ) 100 | _LOOKUP_RETRY_INTERVAL = 1 101 | _LOOKUP_RETRIES = 10 102 | _RESIZE_ITERATIONS = 2 103 | 104 | def main(): 105 | pid = int(input("PID: ")) 106 | print(f"received: {pid}") 107 | proc = _Process(pid) 108 | procs = {} 109 | windows = () 110 | for tries in range(_LOOKUP_RETRIES): 111 | procs = { 112 | proc.pid: proc 113 | for proc in _chain((proc,), proc.children(recursive=True)) 114 | } 115 | print(f"process(es) (try {tries + 1}): {procs}") 116 | windows = _getAllWindows() 117 | print(f"window(s) (try {tries + 1}): {windows}") 118 | for win in windows: 119 | win_pid = win_to_pid(win) 120 | if win_pid in procs: 121 | resizer(procs[win_pid], win) 122 | return 123 | _sleep(_LOOKUP_RETRY_INTERVAL) 124 | raise LookupError(procs, windows) 125 | 126 | def win_to_pid(window: _Window): 127 | return _GetWindowThreadProcessId(window.getHandle())[1] 128 | 129 | def resizer(process: _Process, window: _Window): 130 | print(f"window: {window}") 131 | writer = resizer_writer(process, window) 132 | next(writer) 133 | for size in resizer_reader(process): 134 | writer.send(size) 135 | 136 | def resizer_reader(process: _Process): 137 | while True: 138 | size0 = "" 139 | while not size0: # stdin watchdog triggers this loop 140 | if not process.is_running(): 141 | return 142 | size0 = input("size: ") 143 | rows, columns = (int(s.strip()) for s in size0.split("x", 2)) 144 | print(f"received: {rows}x{columns}") 145 | yield rows, columns 146 | 147 | def resizer_writer( 148 | process: _Process, window: _Window 149 | ) -> _Generator[None, tuple[int, int], None]: 150 | window.hide(True) 151 | 152 | def ignore_error(func: _Callable[[], None]): 153 | try: 154 | func() 155 | except _error: 156 | pass 157 | 158 | @_contextmanager 159 | def attach_console(pid: int): 160 | try: 161 | _ATTACH_CONSOLE(pid) 162 | yield _PY_CONSOLE_SCREEN_BUFFER_TYPE( 163 | _CreateFile( 164 | "CONOUT$", 165 | _GENERIC_READ | _GENERIC_WRITE, 166 | _FILE_SHARE_WRITE, 167 | None, 168 | _OPEN_EXISTING, 169 | 0, 170 | None, 171 | ) # GetStdHandle gives the piped handle instead of the console handle 172 | ) 173 | finally: 174 | _FreeConsole() 175 | 176 | def console_ctrl_handler(event: int): 177 | if event in ( 178 | _CTRL_C_EVENT, 179 | _CTRL_BREAK_EVENT, 180 | _CTRL_CLOSE_EVENT, 181 | ): 182 | return True 183 | return False 184 | 185 | _FreeConsole() 186 | with attach_console(process.pid) as console: 187 | _SetConsoleCtrlHandler(console_ctrl_handler, True) 188 | while True: 189 | columns, rows = yield 190 | # iterate to resize accurately 191 | for iter in range(_RESIZE_ITERATIONS): 192 | info = _GET_CONSOLE_SCREEN_BUFFER_INFO(console) 193 | old_rect = window.getClientFrame() 194 | old_actual_rect = window.size 195 | old_cols = info["Window"].Right - info["Window"].Left + 1 196 | old_rows = info["Window"].Bottom - info["Window"].Top + 1 197 | old_width = old_rect.right - old_rect.left 198 | old_height = old_rect.bottom - old_rect.top 199 | size = ( 200 | int(old_width * columns / old_cols) 201 | + old_actual_rect.width 202 | - old_width, 203 | int(old_height * rows / old_rows) 204 | + old_actual_rect.height 205 | - old_height, 206 | ) 207 | print(f"pixel size (iteration {iter + 1}): {size}") 208 | setters = [ 209 | # almost accurate, works for alternate screen buffer 210 | lambda: _SetWindowPos( 211 | _cast(int, window.getHandle()), 212 | None, 213 | 0, 214 | 0, 215 | *size, 216 | _SWP_NOACTIVATE | _SWP_NOREDRAW | _SWP_NOZORDER, 217 | ), 218 | # accurate, SetConsoleWindowInfo does not work for alternate screen buffer 219 | lambda: _SET_CONSOLE_WINDOW_INFO( 220 | console, 221 | True, 222 | _PY_SMALL_RECT_TYPE(0, 0, columns - 1, old_rows - 1), 223 | ), 224 | lambda: console.SetConsoleScreenBufferSize( 225 | _PY_COORD_TYPE(columns, old_rows) 226 | ), 227 | lambda: _SET_CONSOLE_WINDOW_INFO( 228 | console, 229 | True, 230 | _PY_SMALL_RECT_TYPE(0, 0, columns - 1, rows - 1), 231 | ), 232 | lambda: console.SetConsoleScreenBufferSize( 233 | _PY_COORD_TYPE(columns, rows) 234 | ), 235 | ] 236 | if old_cols < columns: 237 | setters[1], setters[2] = setters[2], setters[1] 238 | if old_rows < rows: 239 | setters[3], setters[4] = setters[4], setters[3] 240 | for setter in setters: 241 | ignore_error(setter) 242 | print(f"resized") 243 | 244 | else: 245 | 246 | def main(): 247 | raise NotImplementedError(_sys.platform) 248 | 249 | 250 | if __name__ == "__main__": 251 | main() 252 | -------------------------------------------------------------------------------- /src/util.ts: -------------------------------------------------------------------------------- 1 | import type { AsyncOrSync } from "ts-essentials" 2 | import type { ChildProcess } from "node:child_process" 3 | import type { Writable } from "node:stream" 4 | 5 | export async function spawnPromise( 6 | spawn: () => AsyncOrSync, 7 | ): Promise { 8 | const ret = await spawn() 9 | return new Promise((resolve, reject) => { 10 | ret.once("spawn", () => { resolve(ret) }) 11 | .once("error", reject) 12 | }) 13 | } 14 | 15 | export async function writePromise( 16 | stream: Writable, 17 | chunk: unknown, 18 | ): Promise { 19 | return new Promise((resolve, reject) => { 20 | const written = stream.write(chunk, error => { 21 | if (error) { reject(error) } else if (written) { resolve() } 22 | }) 23 | if (!written) { stream.once("drain", resolve) } 24 | }) 25 | } 26 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowArbitraryExtensions": true, 4 | "allowJs": true, 5 | "allowSyntheticDefaultImports": true, 6 | "allowUmdGlobalAccess": false, 7 | "allowUnreachableCode": false, 8 | "allowUnusedLabels": false, 9 | "alwaysStrict": true, 10 | "assumeChangesOnlyAffectDirectDependencies": false, 11 | "checkJs": true, 12 | "composite": false, 13 | "declaration": true, 14 | "declarationMap": true, 15 | "disableReferencedProjectLoad": true, 16 | "disableSizeLimit": false, 17 | "disableSolutionSearching": false, 18 | "disableSourceOfProjectReferenceRedirect": false, 19 | "downlevelIteration": true, 20 | "emitBOM": false, 21 | "emitDeclarationOnly": false, 22 | "emitDecoratorMetadata": false, 23 | "esModuleInterop": true, 24 | "exactOptionalPropertyTypes": true, 25 | "experimentalDecorators": true, 26 | "forceConsistentCasingInFileNames": true, 27 | "importHelpers": true, 28 | "incremental": true, 29 | "inlineSourceMap": true, 30 | "inlineSources": true, 31 | "jsx": "react", 32 | "lib": [ 33 | "DOM", 34 | "ES2018" 35 | ], 36 | "maxNodeModuleJsDepth": 0, 37 | "module": "NodeNext", 38 | "moduleDetection": "force", 39 | "moduleResolution": "nodenext", 40 | "moduleSuffixes": [ 41 | "" 42 | ], 43 | "noEmit": false, 44 | "noEmitHelpers": false, 45 | "noEmitOnError": false, 46 | "noErrorTruncation": true, 47 | "noFallthroughCasesInSwitch": true, 48 | "noImplicitAny": true, 49 | "noImplicitOverride": true, 50 | "noImplicitReturns": true, 51 | "noImplicitThis": true, 52 | "noImplicitUseStrict": false, 53 | "noLib": false, 54 | "noPropertyAccessFromIndexSignature": true, 55 | "noResolve": false, 56 | "noStrictGenericChecks": false, 57 | "noUncheckedIndexedAccess": true, 58 | "noUnusedLocals": true, 59 | "noUnusedParameters": true, 60 | "paths": { 61 | "obsidian": [ 62 | "./node_modules/@polyipseity/obsidian/obsidian.d.ts" 63 | ] 64 | }, 65 | "plugins": [ 66 | { 67 | "enableForWorkspaceTypeScriptVersions": false, 68 | "name": "typescript-eslint-language-service" 69 | }, 70 | { 71 | "enableForWorkspaceTypeScriptVersions": false, 72 | "name": "typescript-styled-plugin" 73 | } 74 | ], 75 | "preserveConstEnums": true, 76 | "pretty": true, 77 | "removeComments": true, 78 | "resolveJsonModule": true, 79 | "rootDirs": [ 80 | "." 81 | ], 82 | "skipLibCheck": true, 83 | "sourceMap": false, 84 | "strict": true, 85 | "suppressExcessPropertyErrors": false, 86 | "suppressImplicitAnyIndexErrors": false, 87 | "target": "ES2018", 88 | "tsBuildInfoFile": ".tsbuildinfo", 89 | "useDefineForClassFields": true, 90 | "verbatimModuleSyntax": true 91 | }, 92 | "exclude": [], 93 | "extends": [ 94 | "@tsconfig/recommended/tsconfig.json", 95 | "@tsconfig/strictest/tsconfig.json", 96 | "@tsconfig/node16/tsconfig.json" 97 | ], 98 | "include": [ 99 | "CHANGELOG.md", 100 | "README.md", 101 | "assets/**/*", 102 | "src/**/*" 103 | ], 104 | "references": [], 105 | "typeAcquisition": { 106 | "disableFilenameBasedTypeAcquisition": false, 107 | "enable": true, 108 | "exclude": [], 109 | "include": [] 110 | }, 111 | "watchOptions": { 112 | "excludeDirectories": [], 113 | "excludeFiles": [], 114 | "fallbackPolling": "dynamicpriority", 115 | "synchronousWatchDirectory": true, 116 | "watchDirectory": "useFsEvents", 117 | "watchFile": "useFsEvents" 118 | } 119 | } -------------------------------------------------------------------------------- /versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "0.0.1": "1.1.9", 3 | "1.0.0": "1.1.9", 4 | "2.0.0": "1.1.9", 5 | "2.1.0": "1.1.9", 6 | "2.2.0": "1.1.9", 7 | "2.3.0": "1.1.9", 8 | "2.3.1": "1.1.9", 9 | "2.3.2": "1.1.9", 10 | "2.3.3": "1.1.9", 11 | "2.4.0": "1.1.9", 12 | "2.4.1": "1.1.9", 13 | "2.4.2": "1.1.9", 14 | "2.5.0": "1.1.9", 15 | "2.5.1": "1.1.9", 16 | "2.6.0": "1.1.9", 17 | "2.6.1": "1.1.9", 18 | "2.7.0": "1.1.9", 19 | "2.8.0": "1.1.9", 20 | "2.8.1": "1.1.9", 21 | "2.9.0": "1.1.9", 22 | "2.10.0": "1.1.9", 23 | "2.11.0": "1.1.9", 24 | "3.0.0": "1.1.9", 25 | "3.1.0": "1.0.3", 26 | "3.2.0": "1.1.13", 27 | "3.3.0": "1.1.13", 28 | "3.4.0": "1.1.13", 29 | "3.4.1": "1.1.13", 30 | "3.5.0": "1.1.13", 31 | "3.5.1": "1.1.13", 32 | "3.6.0": "1.1.13", 33 | "3.7.0": "1.1.13", 34 | "3.8.0": "1.1.13", 35 | "3.9.0": "1.2.5", 36 | "3.9.1": "1.2.5", 37 | "3.9.2": "1.2.5", 38 | "3.10.0": "1.2.5", 39 | "3.11.0": "1.2.8", 40 | "3.11.1": "1.2.8", 41 | "3.12.0": "1.2.8", 42 | "3.12.1": "1.2.8", 43 | "3.12.2": "1.2.8", 44 | "3.12.3": "1.2.8", 45 | "3.13.0": "1.2.8", 46 | "3.14.0": "1.2.8", 47 | "3.15.0": "1.2.8", 48 | "3.15.1": "1.2.8", 49 | "3.16.0": "1.4.11" 50 | } --------------------------------------------------------------------------------