├── .eslintrc.json ├── .gitattributes ├── .github └── FUNDING.yml ├── .gitignore ├── .vscode ├── extensions.json ├── launch.json ├── settings.json ├── spellright.dict └── tasks.json ├── .vscodeignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── defaults ├── greeks.json └── math.json ├── images └── unicode-input.gif ├── package-lock.json ├── package.json ├── src ├── api.ts ├── exception.ts ├── extension.ts ├── input_method.ts └── test │ ├── runTest.ts │ └── suite │ ├── extension.test.ts │ └── index.ts ├── tsconfig.json ├── tslint.json └── vsc-extension-quickstart.md /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "eslint:recommended", 4 | "plugin:@typescript-eslint/eslint-recommended", 5 | "plugin:@typescript-eslint/recommended", 6 | "plugin:prettier/recommended", 7 | "prettier/@typescript-eslint" 8 | ], 9 | "plugins": [ 10 | "@typescript-eslint" 11 | ], 12 | "env": { "node": true, "es6": true }, 13 | "parser": "@typescript-eslint/parser", 14 | "parserOptions": { 15 | "sourceType": "module", 16 | "project": "./tsconfig.json" 17 | }, 18 | "rules": { 19 | } 20 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set default behavior to automatically normalize line endings. 2 | * text=auto 3 | 4 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [konn] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | types 3 | node_modules 4 | .vscode-test/ 5 | *.vsix 6 | *.mov -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "dbaeumer.vscode-eslint" 6 | ] 7 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "runtimeExecutable": "${execPath}", 13 | "args": [ 14 | "--extensionDevelopmentPath=${workspaceFolder}" 15 | ], 16 | "outFiles": [ 17 | "${workspaceFolder}/out/**/*.js" 18 | ], 19 | "preLaunchTask": "npm: watch" 20 | }, 21 | { 22 | "name": "Extension Tests", 23 | "type": "extensionHost", 24 | "request": "launch", 25 | "runtimeExecutable": "${execPath}", 26 | "args": [ 27 | "--extensionDevelopmentPath=${workspaceFolder}", 28 | "--extensionTestsPath=${workspaceFolder}/out/test" 29 | ], 30 | "outFiles": [ 31 | "${workspaceFolder}/out/test/**/*.js" 32 | ], 33 | "preLaunchTask": "npm: watch" 34 | } 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": false // set this to true to hide the "out" folder with the compiled JS files 5 | }, 6 | "search.exclude": { 7 | "out": true // set this to false to include "out" folder in search results 8 | }, 9 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 10 | "typescript.tsc.autoDetect": "off", 11 | "editor.tabSize": 2, 12 | "licenser.author": "Hiromi ISHII", 13 | "licenser.license": "BSD3", 14 | "licenser.projectName": "generic-input-method" 15 | } 16 | -------------------------------------------------------------------------------- /.vscode/spellright.dict: -------------------------------------------------------------------------------- 1 | redtt-diag 2 | catex 3 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch", 9 | "problemMatcher": "$tsc-watch", 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "never" 13 | }, 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | out/test/** 4 | out/**/*.map 5 | src/** 6 | .gitignore 7 | tsconfig.json 8 | vsc-extension-quickstart.md 9 | tslint.json -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [0.0.11] - 2018-12-05 4 | 5 | - Revives `defaults/greeks.json` dictionary from [CaTeX] extension. 6 | There is no default input-method using `defaults/greeks.json`, but you can use it to implement custom input methods for other extensions. 7 | 8 | ## [0.0.10] - 2018-11-28 9 | 10 | - Automatic settings update 11 | 12 | ## [0.0.9] - 2018-11-27 13 | 14 | - Workaround for `event-stream` vulnerability 15 | 16 | ## [0.0.8] 17 | 18 | - Moves LaTeX-related functionalities to [CaTeX] extension. 19 | - Custom QuickPick invoker 20 | - Fixes Expander registration logic 21 | - Path reference now respects absolute paths 22 | 23 | [CaTeX]: https://marketplace.visualstudio.com/items?itemName=mr-konn.catex 24 | 25 | ## [0.0.7] 26 | 27 | - Uses `body` field if the `description` misses. 28 | - Introduces generic snippet expander mechanism. 29 | - Exposes expander registration API: `registerExpander`. 30 | - Renames `SnippetString` renderer to `Snippet`. 31 | - Custom Exception for InputMethods. 32 | 33 | ## [0.0.6] 34 | 35 | ### Added 36 | 37 | - Implements `extension.input-methods.invoke` command. 38 | - Adds `invokeInputMethod` function to the API. 39 | 40 | ### Changed 41 | 42 | - README enhancement 43 | - Started to keep a Changelog. 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2022, hiromi 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Generic Input Method for VSCode 2 | 3 | [![Visual Studio Marketplace](https://img.shields.io/vscode-marketplace/v/mr-konn.generic-input-method.svg)](https://marketplace.visualstudio.com/items?itemName=mr-konn.generic-input-method) 4 | [![Visual Studio Marketplace](https://vsmarketplacebadge.apphb.com/installs/mr-konn.generic-input-method.svg)](https://marketplace.visualstudio.com/items?itemName=mr-konn.generic-input-method) 5 | 6 | This extension provides a generic input method within VSCode. 7 | It can be used as a [YaTeX][yatex]-like image completion for LaTeX or Unicode Math input for theorem provers such as Agda or Lean. 8 | 9 | [yatex]: http://yatex.org 10 | 11 | ## Features 12 | 13 | - ~~[YaTeX][yatex]-like image-completion for LaTeX~~ 14 | - Since version 0.0.8, LaTeX-related functionalities are moved to [CaTeX] extension. 15 | - Unicode Math input for Markdown 16 | - Ability to configure your own custom input methods! 17 | - Automatic configuration update 18 | 19 | [CaTeX]: https://marketplace.visualstudio.com/items?itemName=mr-konn.catex 20 | 21 | ## Demos 22 | 23 | ### Unicode Math Input 24 | 25 | ![GIF Anime](images/unicode-input.gif) 26 | 27 | ## Configuration 28 | 29 | You can define and/or modify input methods in `generic-input-method.input-methods`. 30 | This must be an array of input method. 31 | Each input method is defined as follows: 32 | 33 | ```javascript 34 | { 35 | // Unique name for the input method 36 | "name": "Example Math Input", 37 | 38 | // Languages where the IM is activated. 39 | "languages": ["markdown", "latex"], 40 | 41 | // Characters to trigger conversion 42 | "triggers": ["\\"], 43 | 44 | // How to render each items? 45 | // Available: "string" or "snippet"; other extension can add more. 46 | // `string` just prints the content of "body" property. 47 | // See `defaults/images.json` for examples. 48 | // You can also define custom expander for render. 49 | "renderMode": "string", 50 | 51 | // Suffix for a text-editor command; 52 | // you can invoke it as `extension.complete.custom.example-math`. 53 | "commandName": "custom.example-math", 54 | 55 | // An array of items or a reference to the default dictionary. 56 | "dictionary": [ 57 | // Default Math dictionary shipped with this extension 58 | "defaults/math.json", 59 | 60 | // Push `\||-` to input `\Vvdash`. 61 | // Shows `⊪` as a preview. 62 | { "label": "|||-", "body": "\\Vvdash", "description": "⊪" } 63 | ] 64 | } 65 | ``` 66 | 67 | Currently, you can only refer to the default dictionaries shipped with the `generic-input-method` extension. 68 | 69 | ## External API 70 | 71 | This extension provides an API to un/register custom input methods. 72 | 73 | ### Registering a new Input Method dynamically 74 | 75 | ```typescript 76 | import { extensions, ExtensionContext } from "vscode"; 77 | 78 | function async activate(context: ExtensionContext) { 79 | let ims = extensions.getExtension("mr-konn.generic-input-method"); 80 | if (gim) { 81 | const api: any = await gim.activate(); 82 | const im: any = { 83 | name: "My Great IM Dynamically Registered", 84 | languages: ["redtt"], 85 | triggers: ["\\"], 86 | dictionary: ["defaults/math.json", { label: "to", body: "→" }] 87 | }; 88 | api.registerInputMethod(im); 89 | } 90 | } 91 | ``` 92 | 93 | See [CaTeX][catex-repo] and [`redtt-diagnostics`][redtt-diag] for an example usage. 94 | 95 | [catex-repo]: https://github.com/konn/catex 96 | [redtt-diag]: https://github.com/konn/vscode-redtt-diagnostics 97 | 98 | ### Invoking Input Method Forcedly 99 | 100 | You can use `api.invokeInputMethod(editor?, nameOrIM?)` to invoke an input method, regardless of the language of editor. 101 | 102 | ## TODOs 103 | 104 | - Cool screenshot GIFs 105 | - Underlining converted input 106 | - Contextual completion based on scopes 107 | - Split LaTeX-related input methods as an external extension? 108 | -------------------------------------------------------------------------------- /defaults/greeks.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "label": "a", 4 | "body": "alpha", 5 | "description": "α" 6 | }, 7 | { 8 | "label": "b", 9 | "body": "beta", 10 | "description": "β" 11 | }, 12 | { 13 | "label": "g", 14 | "body": "gamma", 15 | "description": "γ" 16 | }, 17 | { 18 | "label": "G", 19 | "body": "Gamma", 20 | "description": "Γ" 21 | }, 22 | { 23 | "label": "d", 24 | "body": "delta", 25 | "description": "δ" 26 | }, 27 | { 28 | "label": "D", 29 | "body": "Delta", 30 | "description": "Δ" 31 | }, 32 | { 33 | "label": "e", 34 | "body": "epsilon", 35 | "description": "ϵ" 36 | }, 37 | { 38 | "label": "e-", 39 | "body": "varepsilon", 40 | "description": "ε" 41 | }, 42 | { 43 | "label": "z", 44 | "body": "zeta", 45 | "description": "ζ" 46 | }, 47 | { 48 | "label": "et", 49 | "body": "eta", 50 | "description": "η" 51 | }, 52 | { 53 | "label": "th", 54 | "body": "theta", 55 | "description": "θ" 56 | }, 57 | { 58 | "label": "Th", 59 | "body": "Theta", 60 | "description": "Θ" 61 | }, 62 | { 63 | "label": "th-", 64 | "body": "vartheta", 65 | "description": "ϑ" 66 | }, 67 | { 68 | "label": "i", 69 | "body": "iota", 70 | "description": "ι" 71 | }, 72 | { 73 | "label": "k", 74 | "body": "kappa", 75 | "description": "κ" 76 | }, 77 | { 78 | "label": "k-", 79 | "body": "varkappa", 80 | "description": "ϰ" 81 | }, 82 | { 83 | "label": "l", 84 | "body": "lambda", 85 | "description": "λ" 86 | }, 87 | { 88 | "label": "L", 89 | "body": "Lambda", 90 | "description": "Λ" 91 | }, 92 | { 93 | "label": "m", 94 | "body": "mu", 95 | "description": "μ" 96 | }, 97 | { 98 | "label": "n", 99 | "body": "nu", 100 | "description": "ν" 101 | }, 102 | { 103 | "label": "x", 104 | "body": "xi", 105 | "description": "ξ" 106 | }, 107 | { 108 | "label": "X", 109 | "body": "Xi", 110 | "description": "Ξ" 111 | }, 112 | { 113 | "label": "p", 114 | "body": "pi", 115 | "description": "π" 116 | }, 117 | { 118 | "label": "P", 119 | "body": "Pi", 120 | "description": "Π" 121 | }, 122 | { 123 | "label": "p-", 124 | "body": "varpi", 125 | "description": "ϖ" 126 | }, 127 | { 128 | "label": "r", 129 | "body": "rho", 130 | "description": "ρ" 131 | }, 132 | { 133 | "label": "r-", 134 | "body": "varrho", 135 | "description": "ϱ" 136 | }, 137 | { 138 | "label": "s", 139 | "body": "sigma", 140 | "description": "σ" 141 | }, 142 | { 143 | "label": "S", 144 | "body": "Sigma", 145 | "description": "Σ" 146 | }, 147 | { 148 | "label": "s-", 149 | "body": "varsigma", 150 | "description": "ς" 151 | }, 152 | { 153 | "label": "t", 154 | "body": "tau", 155 | "description": "τ" 156 | }, 157 | { 158 | "label": "u", 159 | "body": "upsilon", 160 | "description": "υ" 161 | }, 162 | { 163 | "label": "y", 164 | "body": "upsilon", 165 | "description": "υ" 166 | }, 167 | { 168 | "label": "U", 169 | "body": "Upsilon", 170 | "description": "Υ" 171 | }, 172 | { 173 | "label": "Y", 174 | "body": "Upsilon", 175 | "description": "Υ" 176 | }, 177 | { 178 | "label": "ph", 179 | "body": "phi", 180 | "description": "φ" 181 | }, 182 | { 183 | "label": "Ph", 184 | "body": "Phi", 185 | "description": "Φ" 186 | }, 187 | { 188 | "label": "ph-", 189 | "body": "varphi", 190 | "description": "φ" 191 | }, 192 | { 193 | "label": "c", 194 | "body": "chi", 195 | "description": "χ" 196 | }, 197 | { 198 | "label": "ps", 199 | "body": "psi", 200 | "description": "ψ" 201 | }, 202 | { 203 | "label": "Ps", 204 | "body": "Psi", 205 | "description": "Ψ" 206 | }, 207 | { 208 | "label": "o", 209 | "body": "omega", 210 | "description": "ω" 211 | }, 212 | { 213 | "label": "w", 214 | "body": "omega", 215 | "description": "ω" 216 | }, 217 | { 218 | "label": "O", 219 | "body": "Omega", 220 | "description": "Ω" 221 | }, 222 | { 223 | "label": "W", 224 | "body": "Omega", 225 | "description": "Ω" 226 | } 227 | ] 228 | -------------------------------------------------------------------------------- /defaults/math.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "label": "integral", 4 | "description": "∫", 5 | "body": "∫" 6 | }, 7 | { 8 | "label": "ointegral", 9 | "description": "∮", 10 | "body": "∮" 11 | }, 12 | { 13 | "label": "sqrt", 14 | "description": "√", 15 | "body": "√" 16 | }, 17 | { 18 | "label": "root", 19 | "description": "√", 20 | "body": "√" 21 | }, 22 | { 23 | "label": "A", 24 | "description": "∀", 25 | "body": "∀" 26 | }, 27 | { 28 | "label": "E", 29 | "description": "∃", 30 | "body": "∃" 31 | }, 32 | { 33 | "label": "E/", 34 | "description": "∄", 35 | "body": "∄" 36 | }, 37 | { 38 | "label": "!", 39 | "description": "¬", 40 | "body": "¬" 41 | }, 42 | { 43 | "label": "-+", 44 | "description": "∓", 45 | "body": "∓" 46 | }, 47 | { 48 | "label": "f", 49 | "description": "÷", 50 | "body": "÷" 51 | }, 52 | { 53 | "label": "*", 54 | "description": "*", 55 | "body": "*" 56 | }, 57 | { 58 | "label": "o", 59 | "description": "o", 60 | "body": "o" 61 | }, 62 | { 63 | "label": "o*", 64 | "description": "∙", 65 | "body": "∙" 66 | }, 67 | { 68 | "label": ".", 69 | "description": "⋅", 70 | "body": "⋅" 71 | }, 72 | { 73 | "label": "cap", 74 | "description": "∩", 75 | "body": "∩" 76 | }, 77 | { 78 | "label": "cup", 79 | "description": "∪", 80 | "body": "∪" 81 | }, 82 | { 83 | "label": "u+", 84 | "description": "⊎", 85 | "body": "⊎" 86 | }, 87 | { 88 | "label": "|~|", 89 | "description": "⊓", 90 | "body": "⊓" 91 | }, 92 | { 93 | "label": "|_|", 94 | "description": "⊔", 95 | "body": "⊔" 96 | }, 97 | { 98 | "label": "v", 99 | "description": "v", 100 | "body": "v" 101 | }, 102 | { 103 | "label": "\\\\", 104 | "description": "⧵", 105 | "body": "⧵" 106 | }, 107 | { 108 | "label": ")(", 109 | "body": "≀" 110 | }, 111 | { "label": "||-", "body": "⊩", "description": "⊩" }, 112 | { 113 | "label": "<|", 114 | "description": "◁", 115 | "body": "◁" 116 | }, 117 | { 118 | "label": "|>", 119 | "description": "▷", 120 | "body": "▷" 121 | }, 122 | { 123 | "label": "<||", 124 | "description": "⊲", 125 | "body": "⊲" 126 | }, 127 | { 128 | "label": "||>", 129 | "description": "⊳", 130 | "body": "⊳" 131 | }, 132 | { 133 | "label": "<|-", 134 | "description": "⊴", 135 | "body": "⊴" 136 | }, 137 | { 138 | "label": "|>-", 139 | "description": "⊵", 140 | "body": "⊵" 141 | }, 142 | { 143 | "label": "o+", 144 | "description": "⊕", 145 | "body": "⊕" 146 | }, 147 | { 148 | "label": "o-", 149 | "description": "⊖", 150 | "body": "⊖" 151 | }, 152 | { 153 | "label": "ox", 154 | "description": "⊗", 155 | "body": "⊗" 156 | }, 157 | { 158 | "label": "o/", 159 | "description": "⊘", 160 | "body": "⊘" 161 | }, 162 | { 163 | "label": "o.", 164 | "description": "⊙", 165 | "body": "⊙" 166 | }, 167 | { 168 | "label": "O", 169 | "description": "○", 170 | "body": "○" 171 | }, 172 | { 173 | "label": "t", 174 | "description": "†", 175 | "body": "†" 176 | }, 177 | { 178 | "label": "tt", 179 | "description": "‡", 180 | "body": "‡" 181 | }, 182 | { 183 | "label": "coprod", 184 | "description": "⨿", 185 | "body": "⨿" 186 | }, 187 | { 188 | "label": "-=", 189 | "description": "≡", 190 | "body": "≡" 191 | }, 192 | { 193 | "label": "=-", 194 | "description": "≡", 195 | "body": "≡" 196 | }, 197 | { 198 | "label": "---", 199 | "description": "≡", 200 | "body": "≡" 201 | }, 202 | { 203 | "label": "(", 204 | "description": "⊂", 205 | "body": "⊂" 206 | }, 207 | { 208 | "label": "(-", 209 | "description": "⊆", 210 | "body": "⊆" 211 | }, 212 | { 213 | "label": ")", 214 | "description": "⊃", 215 | "body": "⊃" 216 | }, 217 | { 218 | "label": ")-", 219 | "description": "⊇", 220 | "body": "⊇" 221 | }, 222 | { 223 | "label": "[", 224 | "description": "⊏", 225 | "body": "⊏" 226 | }, 227 | { 228 | "label": "[-", 229 | "description": "⊑", 230 | "body": "⊑" 231 | }, 232 | { 233 | "label": "]", 234 | "description": "⊐", 235 | "body": "⊐" 236 | }, 237 | { 238 | "label": "]-", 239 | "description": "⊒", 240 | "body": "⊒" 241 | }, 242 | { 243 | "label": "{", 244 | "description": "∈", 245 | "body": "∈" 246 | }, 247 | { 248 | "label": "}", 249 | "description": "∋", 250 | "body": "∋" 251 | }, 252 | { 253 | "label": "II", 254 | "body": "𝕀", 255 | "description": "𝕀" 256 | }, 257 | { 258 | "label": "|-", 259 | "description": "⊢", 260 | "body": "⊢" 261 | }, 262 | { 263 | "label": "-|", 264 | "description": "⊣", 265 | "body": "⊣" 266 | }, 267 | { 268 | "label": "~", 269 | "description": "~", 270 | "body": "~" 271 | }, 272 | { 273 | "label": "~-", 274 | "description": "≃", 275 | "body": "≃" 276 | }, 277 | { 278 | "label": "asymp", 279 | "description": "≍", 280 | "body": "≍" 281 | }, 282 | { 283 | "label": "~~", 284 | "description": "≈", 285 | "body": "≈" 286 | }, 287 | { 288 | "label": "~=", 289 | "description": "≅", 290 | "body": "≅" 291 | }, 292 | { 293 | "label": ".=", 294 | "description": "≐", 295 | "body": "≐" 296 | }, 297 | { 298 | "label": "o<", 299 | "description": "∝", 300 | "body": "∝" 301 | }, 302 | { 303 | "label": "|=", 304 | "description": "⊧", 305 | "body": "⊧" 306 | }, 307 | { 308 | "label": "_|_", 309 | "description": "⟂", 310 | "body": "⟂" 311 | }, 312 | { 313 | "label": "|", 314 | "description": "|", 315 | "body": "|" 316 | }, 317 | { 318 | "label": "||", 319 | "description": "∥", 320 | "body": "∥" 321 | }, 322 | { 323 | "label": "bowtie", 324 | "description": "⋈", 325 | "body": "⋈" 326 | }, 327 | { 328 | "label": "|><|", 329 | "description": "⋈", 330 | "body": "⋈" 331 | }, 332 | { 333 | "label": "\\_/", 334 | "description": "⌣", 335 | "body": "⌣" 336 | }, 337 | { 338 | "label": "/~\\", 339 | "description": "⌢", 340 | "body": "⌢" 341 | }, 342 | { 343 | "label": "<<", 344 | "description": "⋘", 345 | "body": "⋘" 346 | }, 347 | { 348 | "label": ">>>", 349 | "description": "⋙", 350 | "body": "⋙" 351 | }, 352 | { 353 | "label": "<=", 354 | "description": "⇐", 355 | "body": "⇐" 356 | }, 357 | { 358 | "label": "<==", 359 | "description": "⟸", 360 | "body": "⟸" 361 | }, 362 | { 363 | "label": "=>", 364 | "description": "⇒", 365 | "body": "⇒" 366 | }, 367 | { 368 | "label": "==>", 369 | "description": "⟹", 370 | "body": "⟹" 371 | }, 372 | { 373 | "label": "<=>", 374 | "description": "⇔", 375 | "body": "⇔" 376 | }, 377 | { 378 | "label": "<==>", 379 | "description": "⟺", 380 | "body": "⟺" 381 | }, 382 | { 383 | "label": "^||", 384 | "description": "⇑", 385 | "body": "⇑" 386 | }, 387 | { 388 | "label": "v||", 389 | "description": "⇓", 390 | "body": "⇓" 391 | }, 392 | { 393 | "label": "ne", 394 | "description": "↗", 395 | "body": "↗" 396 | }, 397 | { 398 | "label": "nw", 399 | "description": "↖", 400 | "body": "↖" 401 | }, 402 | { 403 | "label": "se", 404 | "description": "↘", 405 | "body": "↘" 406 | }, 407 | { 408 | "label": "sw", 409 | "description": "↙", 410 | "body": "↙" 411 | }, 412 | { 413 | "label": "/-", 414 | "description": "↼", 415 | "body": "↼" 416 | }, 417 | { 418 | "label": "\\-", 419 | "description": "↽", 420 | "body": "↽" 421 | }, 422 | { 423 | "label": "-/", 424 | "description": "⇁", 425 | "body": "⇁" 426 | }, 427 | { 428 | "label": "-\\", 429 | "description": "⇀", 430 | "body": "⇀" 431 | }, 432 | { 433 | "label": "CUP", 434 | "description": "⋃", 435 | "body": "⋃" 436 | }, 437 | { 438 | "label": "union", 439 | "description": "⋃", 440 | "body": "⋃" 441 | }, 442 | { 443 | "label": "CAP", 444 | "description": "⋂", 445 | "body": "⋂" 446 | }, 447 | { 448 | "label": "isc", 449 | "description": "⋂", 450 | "body": "⋂" 451 | }, 452 | { 453 | "label": "O+", 454 | "description": "⨁", 455 | "body": "⨁" 456 | }, 457 | { 458 | "label": "Ox", 459 | "description": "⨂", 460 | "body": "⨂" 461 | }, 462 | { 463 | "label": "Z", 464 | "description": "ℵ", 465 | "body": "ℵ" 466 | }, 467 | { 468 | "label": "|\\|", 469 | "description": "ℵ", 470 | "body": "ℵ" 471 | }, 472 | { 473 | "label": "h-", 474 | "description": "ℏ", 475 | "body": "ℏ" 476 | }, 477 | { 478 | "label": "l", 479 | "description": "ℓ", 480 | "body": "ℓ" 481 | }, 482 | { 483 | "label": "wp", 484 | "description": "℘", 485 | "body": "℘" 486 | }, 487 | { 488 | "label": "R", 489 | "description": "ℜ", 490 | "body": "ℜ" 491 | }, 492 | { 493 | "label": "Im", 494 | "description": "ℑ", 495 | "body": "ℑ" 496 | }, 497 | { 498 | "label": "mho", 499 | "description": "℧", 500 | "body": "℧" 501 | }, 502 | { 503 | "label": "'", 504 | "description": "′", 505 | "body": "′" 506 | }, 507 | { 508 | "label": "0", 509 | "description": "∅", 510 | "body": "∅" 511 | }, 512 | { 513 | "label": "nabla", 514 | "description": "∇", 515 | "body": "∇" 516 | }, 517 | { 518 | "label": "\\/", 519 | "description": "√", 520 | "body": "√" 521 | }, 522 | { 523 | "label": "surd", 524 | "description": "√", 525 | "body": "√" 526 | }, 527 | { 528 | "label": "top", 529 | "description": "⊤", 530 | "body": "⊤" 531 | }, 532 | { 533 | "label": "bot", 534 | "description": "⊥", 535 | "body": "⊥" 536 | }, 537 | { 538 | "label": "b", 539 | "description": "♭", 540 | "body": "♭" 541 | }, 542 | { 543 | "label": "LT", 544 | "description": "♮", 545 | "body": "♮" 546 | }, 547 | { 548 | "label": "6", 549 | "description": "∂", 550 | "body": "∂" 551 | }, 552 | { 553 | "label": "partial", 554 | "description": "∂", 555 | "body": "∂" 556 | }, 557 | { 558 | "label": "round", 559 | "description": "∂", 560 | "body": "∂" 561 | }, 562 | { 563 | "label": "[]", 564 | "description": "□", 565 | "body": "□" 566 | }, 567 | { 568 | "label": "no", 569 | "description": "(", 570 | "body": "(" 571 | }, 572 | { 573 | "label": "Diamond", 574 | "description": "◇", 575 | "body": "◇" 576 | }, 577 | { 578 | "label": "3", 579 | "description": "△", 580 | "body": "△" 581 | }, 582 | { 583 | "label": "C", 584 | "description": "♣", 585 | "body": "♣" 586 | }, 587 | { 588 | "label": "D", 589 | "description": "♢", 590 | "body": "♢" 591 | }, 592 | { 593 | "label": "H", 594 | "description": "♡", 595 | "body": "♡" 596 | }, 597 | { 598 | "label": "S", 599 | "description": "♠", 600 | "body": "♠" 601 | }, 602 | { 603 | "label": "||", 604 | "description": "‖", 605 | "body": "‖" 606 | }, 607 | { 608 | "label": "sum", 609 | "description": "∑", 610 | "body": "∑" 611 | }, 612 | { 613 | "label": "sigma", 614 | "description": "∑", 615 | "body": "∑" 616 | }, 617 | { 618 | "label": "oo", 619 | "description": "∞", 620 | "body": "∞" 621 | }, 622 | { 623 | "label": "\\", 624 | "description": "\", 625 | "body": "\" 626 | }, 627 | { 628 | "label": "...", 629 | "description": "…", 630 | "body": "…" 631 | }, 632 | { 633 | "label": "+-", 634 | "description": "±", 635 | "body": "±" 636 | }, 637 | { 638 | "label": "x", 639 | "description": "×", 640 | "body": "×" 641 | }, 642 | { 643 | "label": "/", 644 | "description": "÷", 645 | "body": "÷" 646 | }, 647 | { 648 | "label": "#", 649 | "description": "★", 650 | "body": "★" 651 | }, 652 | { 653 | "label": "/\\-", 654 | "description": "△", 655 | "body": "△" 656 | }, 657 | { 658 | "label": "-\\/", 659 | "description": "▽", 660 | "body": "▽" 661 | }, 662 | { 663 | "label": "l", 664 | "body": "𝓁" 665 | }, 666 | { 667 | "label": "ell", 668 | "body": "𝓁" 669 | }, 670 | 671 | { 672 | "label": "<", 673 | "description": "<", 674 | "body": "<" 675 | }, 676 | { 677 | "label": "=<", 678 | "description": "≦", 679 | "body": "≦" 680 | }, 681 | { 682 | "label": ">", 683 | "description": ">", 684 | "body": ">" 685 | }, 686 | { 687 | "label": ">=", 688 | "description": "≧", 689 | "body": "≧" 690 | }, 691 | { 692 | "label": "=:", 693 | "description": "≒", 694 | "body": "≒" 695 | }, 696 | { 697 | "label": "-<", 698 | "description": "く", 699 | "body": "く" 700 | }, 701 | { 702 | "label": "-<=", 703 | "description": "く", 704 | "body": "く" 705 | }, 706 | { 707 | "label": "<<", 708 | "description": "《", 709 | "body": "《" 710 | }, 711 | { 712 | "label": ">>", 713 | "description": "》", 714 | "body": "》" 715 | }, 716 | { 717 | "label": "<-", 718 | "description": "←", 719 | "body": "←" 720 | }, 721 | { 722 | "label": "<--", 723 | "description": "⟵", 724 | "body": "⟵" 725 | }, 726 | { 727 | "label": "->", 728 | "description": "→", 729 | "body": "→" 730 | }, 731 | { 732 | "label": "-->", 733 | "description": "⟶", 734 | "body": "⟶" 735 | }, 736 | { 737 | "label": "<->", 738 | "description": "↔", 739 | "body": "↔" 740 | }, 741 | { 742 | "label": "<-->", 743 | "description": "⟷", 744 | "body": "⟷" 745 | }, 746 | { 747 | "label": "^|", 748 | "description": "↑", 749 | "body": "↑" 750 | }, 751 | { 752 | "label": "v|", 753 | "description": "↓", 754 | "body": "↓" 755 | }, 756 | { 757 | "label": "|->", 758 | "description": "↦", 759 | "body": "↦" 760 | }, 761 | { 762 | "label": "<-)", 763 | "description": "↩", 764 | "body": "↩" 765 | }, 766 | { 767 | "label": "(->", 768 | "description": "↪", 769 | "body": "↪" 770 | }, 771 | { 772 | "label": "~>", 773 | "description": "⟶", 774 | "body": "⟶" 775 | }, 776 | { 777 | "label": "VEC", 778 | "description": "⟶", 779 | "body": "⟶" 780 | }, 781 | { 782 | "label": "prod", 783 | "description": "∏", 784 | "body": "∏" 785 | }, 786 | { 787 | "label": "angle", 788 | "description": "∠", 789 | "body": "∠" 790 | }, 791 | { 792 | "label": "/_", 793 | "description": "∠", 794 | "body": "∠" 795 | }, 796 | { 797 | "label": ".'.", 798 | "description": "∴", 799 | "body": "∴" 800 | }, 801 | { 802 | "label": "'.'", 803 | "description": "∵", 804 | "body": "∵" 805 | }, 806 | { 807 | "label": "_0", 808 | "body": "₀" 809 | }, 810 | { 811 | "label": "_1", 812 | "body": "₁" 813 | }, 814 | { 815 | "label": "_2", 816 | "body": "₂" 817 | }, 818 | { 819 | "label": "_3", 820 | "body": "₃" 821 | }, 822 | { 823 | "label": "_4", 824 | "body": "₄" 825 | }, 826 | { 827 | "label": "_5", 828 | "body": "₅" 829 | }, 830 | { 831 | "label": "_6", 832 | "body": "₆" 833 | }, 834 | { 835 | "label": "_7", 836 | "body": "₇" 837 | }, 838 | { 839 | "label": "_8", 840 | "body": "₈" 841 | }, 842 | { 843 | "label": "_9", 844 | "body": "₉" 845 | }, 846 | { 847 | "label": "^0", 848 | "body": "⁰" 849 | }, 850 | { 851 | "label": "^1", 852 | "body": "¹" 853 | }, 854 | { 855 | "label": "^2", 856 | "body": "²" 857 | }, 858 | { 859 | "label": "^3", 860 | "body": "³" 861 | }, 862 | { 863 | "label": "^4", 864 | "body": "⁴" 865 | }, 866 | { 867 | "label": "^5", 868 | "body": "⁵" 869 | }, 870 | { 871 | "label": "^6", 872 | "body": "⁶" 873 | }, 874 | { 875 | "label": "^7", 876 | "body": "⁷" 877 | }, 878 | { 879 | "label": "^8", 880 | "body": "⁸" 881 | }, 882 | { 883 | "label": "^9", 884 | "body": "⁹" 885 | }, 886 | { 887 | "label": "Ga", 888 | "body": "α" 889 | }, 890 | { 891 | "label": "Gb", 892 | "body": "β" 893 | }, 894 | { 895 | "label": "Gg", 896 | "body": "γ" 897 | }, 898 | { 899 | "label": "GG", 900 | "body": "Γ" 901 | }, 902 | { 903 | "label": "Gd", 904 | "body": "δ" 905 | }, 906 | { 907 | "label": "GD", 908 | "body": "Δ" 909 | }, 910 | { 911 | "label": "Ge", 912 | "body": "ϵ" 913 | }, 914 | { 915 | "label": "Ge-", 916 | "body": "ε" 917 | }, 918 | { 919 | "label": "Gz", 920 | "body": "ζ" 921 | }, 922 | { 923 | "label": "Get", 924 | "body": "η" 925 | }, 926 | { 927 | "label": "Gth", 928 | "body": "θ" 929 | }, 930 | { 931 | "label": "GTh", 932 | "body": "Θ" 933 | }, 934 | { 935 | "label": "Gth-", 936 | "body": "ϑ" 937 | }, 938 | { 939 | "label": "Gi", 940 | "body": "ι" 941 | }, 942 | { 943 | "label": "Gk", 944 | "body": "κ" 945 | }, 946 | { 947 | "label": "Gk-", 948 | "body": "ϰ" 949 | }, 950 | { 951 | "label": "Gl", 952 | "body": "λ" 953 | }, 954 | { 955 | "label": "GL", 956 | "body": "Λ" 957 | }, 958 | { 959 | "label": "Gm", 960 | "body": "μ" 961 | }, 962 | { 963 | "label": "Gn", 964 | "body": "ν" 965 | }, 966 | { 967 | "label": "Gx", 968 | "body": "ξ" 969 | }, 970 | { 971 | "label": "GX", 972 | "body": "Ξ" 973 | }, 974 | { 975 | "label": "Gp", 976 | "body": "π" 977 | }, 978 | { 979 | "label": "GP", 980 | "body": "Π" 981 | }, 982 | { 983 | "label": "Gp-", 984 | "body": "ϖ" 985 | }, 986 | { 987 | "label": "Gr", 988 | "body": "ρ" 989 | }, 990 | { 991 | "label": "Gr-", 992 | "body": "ϱ" 993 | }, 994 | { 995 | "label": "Gs", 996 | "body": "σ" 997 | }, 998 | { 999 | "label": "GS", 1000 | "body": "Σ" 1001 | }, 1002 | { 1003 | "label": "Gs-", 1004 | "body": "ς" 1005 | }, 1006 | { 1007 | "label": "Gt", 1008 | "body": "τ" 1009 | }, 1010 | { 1011 | "label": "Gu", 1012 | "body": "υ" 1013 | }, 1014 | { 1015 | "label": "Gy", 1016 | "body": "υ" 1017 | }, 1018 | { 1019 | "label": "GU", 1020 | "body": "Υ" 1021 | }, 1022 | { 1023 | "label": "GY", 1024 | "body": "Υ" 1025 | }, 1026 | { 1027 | "label": "Gph", 1028 | "body": "φ" 1029 | }, 1030 | { 1031 | "label": "GPh", 1032 | "body": "Φ" 1033 | }, 1034 | { 1035 | "label": "Gph-", 1036 | "body": "φ" 1037 | }, 1038 | { 1039 | "label": "Gc", 1040 | "body": "χ" 1041 | }, 1042 | { 1043 | "label": "Gps", 1044 | "body": "ψ" 1045 | }, 1046 | { 1047 | "label": "GPs", 1048 | "body": "Ψ" 1049 | }, 1050 | { 1051 | "label": "Go", 1052 | "body": "ω" 1053 | }, 1054 | { 1055 | "label": "Gw", 1056 | "body": "ω" 1057 | }, 1058 | { 1059 | "label": "GO", 1060 | "body": "Ω" 1061 | }, 1062 | { 1063 | "label": "GW", 1064 | "body": "Ω" 1065 | } 1066 | ] 1067 | -------------------------------------------------------------------------------- /images/unicode-input.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/konn/vscode-generic-input-method/61cb350c71a2ea6fca82f1542a12dd7bec924823/images/unicode-input.gif -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "generic-input-method", 3 | "displayName": "Generic Input Method", 4 | "description": "A generic input method which is suitable for YaTeX-like image completion for LaTeX or Unicode Symbol input for theorem provers such as Lean or Agda.", 5 | "version": "0.0.12", 6 | "publisher": "mr-konn", 7 | "keywords": [ 8 | "Unicode", 9 | "Math", 10 | "Theorem Prover", 11 | "Input Method" 12 | ], 13 | "engines": { 14 | "vscode": "^1.66.0" 15 | }, 16 | "categories": [ 17 | "Other" 18 | ], 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/konn/vscode-generic-input-method" 22 | }, 23 | "activationEvents": [ 24 | "onStartupFinished" 25 | ], 26 | "main": "./out/extension", 27 | "contributes": { 28 | "commands": [ 29 | { 30 | "title": "Invoke Input Method", 31 | "command": "extension.input-methods.invoke" 32 | } 33 | ], 34 | "configuration": [ 35 | { 36 | "type": "object", 37 | "title": "General Input Method Configuration", 38 | "properties": { 39 | "generic-input-methods.input-methods": { 40 | "description": "Input Method Definitions", 41 | "type": "array", 42 | "default": [ 43 | { 44 | "name": "Unicode Math", 45 | "commandName": "text.math", 46 | "languages": [ 47 | "markdown" 48 | ], 49 | "triggers": [ 50 | "\\" 51 | ], 52 | "dictionary": [ 53 | "defaults/math.json" 54 | ] 55 | } 56 | ], 57 | "items": { 58 | "type": "object", 59 | "title": "Input Method Definition", 60 | "properties": { 61 | "name": { 62 | "type": "string", 63 | "description": "Input Method Name" 64 | }, 65 | "languages": { 66 | "type": "array", 67 | "items": { 68 | "type": "string" 69 | }, 70 | "description": "Language Ids in which the IM will be activated" 71 | }, 72 | "triggers": { 73 | "type": "array", 74 | "description": "Trigger characters to invoke the Input Method", 75 | "items": { 76 | "type": "string" 77 | } 78 | }, 79 | "dictionary": { 80 | "anyOf": [ 81 | { 82 | "type": "string", 83 | "description": "Path to configuration json file" 84 | }, 85 | { 86 | "type": "array", 87 | "description": "Input items", 88 | "items": { 89 | "anyOf": [ 90 | { 91 | "type": "string", 92 | "description": "External file to include as a dictionary" 93 | }, 94 | { 95 | "type": "object", 96 | "title": "Completion Item", 97 | "properties": { 98 | "label": { 99 | "type": "string", 100 | "description": "Keystroke" 101 | }, 102 | "body": { 103 | "type": "string", 104 | "description": "Completions to input" 105 | }, 106 | "description": { 107 | "type": "string", 108 | "description": "Preview string", 109 | "default": "" 110 | }, 111 | "required": [ 112 | "label", 113 | "body" 114 | ] 115 | } 116 | } 117 | ] 118 | } 119 | } 120 | ] 121 | } 122 | } 123 | } 124 | } 125 | } 126 | } 127 | ] 128 | }, 129 | "scripts": { 130 | "vscode:prepublish": "npm run compile", 131 | "compile": "tsc -p ./", 132 | "watch": "tsc -watch -p ./", 133 | "pretest": "npm run compile && npm run lint", 134 | "lint": "eslint src --ext ts", 135 | "test": "node ./out/test/runTest.js" 136 | }, 137 | "dependencies": { 138 | "braces": ">=2.3.1", 139 | "event-stream": ">= 3.3.4", 140 | "fstream": ">=1.0.12", 141 | "js-yaml": ">=3.13.1", 142 | "minimist": ">=1.2.6", 143 | "path": "*", 144 | "querystringify": "~>2.0.0", 145 | "tar": ">=4.4.18" 146 | }, 147 | "devDependencies": { 148 | "@types/glob": "^7.2.0", 149 | "@types/mocha": "^9.1.0", 150 | "@types/node": "14.x", 151 | "@types/vscode": "^1.66.0", 152 | "@typescript-eslint/eslint-plugin": "^5.20.0", 153 | "@typescript-eslint/parser": "^5.20.0", 154 | "@vscode/test-electron": "^2.1.3", 155 | "eslint": "^8.14.0", 156 | "eslint-config-prettier": "^8.5.0", 157 | "eslint-plugin-prettier": "^4.0.0", 158 | "glob": "^7.2.0", 159 | "mocha": "^9.2.2", 160 | "prettier": "^2.6.2", 161 | "typescript": "^4.5.5" 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/api.ts: -------------------------------------------------------------------------------- 1 | import { InputMethodConf, Expander } from "./input_method"; 2 | import { TextEditor } from "vscode"; 3 | 4 | export default interface GenericInputMethodAPI { 5 | registerInputMethod: (imConf: InputMethodConf) => void; 6 | registerExpander: (name: string, expander: Expander) => boolean; 7 | unregisterInputMethodByName: (name: string) => Thenable; 8 | invokeInputMethod: ( 9 | editor: TextEditor, 10 | name?: string | InputMethodConf 11 | ) => void; 12 | } 13 | -------------------------------------------------------------------------------- /src/exception.ts: -------------------------------------------------------------------------------- 1 | export class InputMethodException implements Error { 2 | constructor(public name: string, public message: string) {} 3 | 4 | toString() { 5 | return `InputMethod error: ${this.name}, ${this.message}`; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | // The module 'vscode' contains the VS Code extensibility API 3 | // Import the module and reference it with the alias vscode in your code below 4 | import { 5 | commands, 6 | ExtensionContext, 7 | languages, 8 | window, 9 | workspace, 10 | WorkspaceConfiguration, 11 | Disposable, 12 | TextEditor 13 | } from "vscode"; 14 | import InputMethod, { 15 | InputMethodConf, 16 | Expander, 17 | Expanders 18 | } from "./input_method"; 19 | import GenericInputMethodAPI from "./api"; 20 | 21 | const registered: Map = new Map(); 22 | 23 | function isInputMethodConf( 24 | im: InputMethodConf | InputMethod 25 | ): im is InputMethodConf { 26 | const casted = im; 27 | return ( 28 | casted.provideCompletionItems === undefined || 29 | casted.invokeQuickPick === undefined 30 | ); 31 | } 32 | 33 | const INPUT_METHOD_DEFINITIONS_KEY = "generic-input-methods.input-methods"; 34 | const defaultInputMethodNames: Set = new Set(); 35 | 36 | // this method is called when your extension is activated 37 | // your extension is activated the very first time the command is executed 38 | export function activate(context: ExtensionContext): GenericInputMethodAPI { 39 | let conf: WorkspaceConfiguration = workspace.getConfiguration(); 40 | 41 | let inputMethods: InputMethodConf[] = conf.get( 42 | INPUT_METHOD_DEFINITIONS_KEY, 43 | [] 44 | ); 45 | 46 | const registerInputMethod = (imConf: InputMethodConf) => { 47 | const im: InputMethod = isInputMethodConf(imConf) 48 | ? new InputMethod(context, imConf) 49 | : imConf; 50 | defaultInputMethodNames.add(im.name); 51 | registered.set(im.name, [im, []]); 52 | const desps: Disposable[] = (registered.get(im.name) || [undefined, []])[1]; 53 | im.languages.forEach(lang => { 54 | let compProvider = languages.registerCompletionItemProvider( 55 | lang, 56 | im, 57 | ...im.triggers 58 | ); 59 | desps.push(compProvider); 60 | context.subscriptions.push(compProvider); 61 | }); 62 | let cmd_name = im.commandName; 63 | if (cmd_name) { 64 | let pickCommand = commands.registerTextEditorCommand( 65 | `extension.complete.${cmd_name}`, 66 | editor => im.invokeQuickPick(editor) 67 | ); 68 | desps.push(pickCommand); 69 | context.subscriptions.push(pickCommand); 70 | } 71 | }; 72 | 73 | inputMethods.forEach(registerInputMethod); 74 | workspace.onDidChangeConfiguration( 75 | evt => { 76 | if (evt.affectsConfiguration(INPUT_METHOD_DEFINITIONS_KEY)) { 77 | let inputMethods: InputMethodConf[] = workspace 78 | .getConfiguration() 79 | .get(INPUT_METHOD_DEFINITIONS_KEY, []); 80 | defaultInputMethodNames.forEach(v => { 81 | if (inputMethods.every(i => i.name !== v)) { 82 | defaultInputMethodNames.delete(v); 83 | const target = registered.get(v); 84 | if (target) { 85 | target[1].forEach(d => d.dispose()); 86 | registered.delete(v); 87 | } 88 | } 89 | }); 90 | inputMethods.forEach(imConf => { 91 | let im = registered.get(imConf.name); 92 | if (im) { 93 | im[0].updateConf(context, imConf); 94 | } else { 95 | registerInputMethod(imConf); 96 | } 97 | }); 98 | } 99 | }, 100 | null, 101 | context.subscriptions 102 | ); 103 | 104 | const registerExpander = (name: string, expand: Expander): boolean => { 105 | if (Expanders.has(name)) { 106 | return false; 107 | } else { 108 | Expanders.set(name, expand); 109 | return true; 110 | } 111 | }; 112 | 113 | const invokeInputMethod = async ( 114 | editor: TextEditor, 115 | name?: string | InputMethodConf 116 | ) => { 117 | let im: InputMethod | undefined; 118 | if (!name) { 119 | const items: { label: string; im: InputMethod }[] = []; 120 | registered.forEach(([i, _], label) => { 121 | items.push({ label, im: i }); 122 | }); 123 | let item = await window.showQuickPick(items); 124 | if (item && item.im) { 125 | im = item.im; 126 | } 127 | } else if (typeof name === "string") { 128 | const targ = registered.get(name); 129 | if (targ) { 130 | im = targ[0]; 131 | } 132 | } else if (name) { 133 | im = new InputMethod(context, name); 134 | } 135 | 136 | if (im) { 137 | im.invokeQuickPick(editor, true); 138 | } 139 | }; 140 | 141 | let invoker = commands.registerTextEditorCommand( 142 | "extension.input-methods.invoke", 143 | (editor, _edit, ...args) => invokeInputMethod(editor, ...args) 144 | ); 145 | context.subscriptions.push(invoker); 146 | 147 | const api: GenericInputMethodAPI = { 148 | unregisterInputMethodByName: (name: string): Thenable => 149 | new Promise((resolve, _) => { 150 | const targ = registered.get(name); 151 | if (targ) { 152 | registered.delete(name); 153 | targ[1].forEach(d => d.dispose()); 154 | resolve(true); 155 | } else { 156 | resolve(false); 157 | } 158 | }), 159 | registerInputMethod, 160 | invokeInputMethod, 161 | registerExpander 162 | }; 163 | 164 | return api; 165 | } 166 | 167 | // this method is called when your extension is deactivated 168 | export function deactivate() {} 169 | -------------------------------------------------------------------------------- /src/input_method.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CompletionItem, 3 | ExtensionContext, 4 | SnippetString, 5 | CompletionItemProvider, 6 | TextDocument, 7 | Position, 8 | CancellationToken, 9 | CompletionContext, 10 | CompletionTriggerKind, 11 | Range, 12 | TextEdit, 13 | QuickPickItem, 14 | TextEditor, 15 | window, 16 | workspace, 17 | ConfigurationChangeEvent 18 | } from "vscode"; 19 | import { readFileSync } from "fs"; 20 | import { InputMethodException } from "./exception"; 21 | import * as path from "path"; 22 | 23 | const CHAR_SPACE: number = 32; 24 | const CHAR_TILDE: number = 126; 25 | const ASCII_CHARS: string[] = Array(CHAR_TILDE - CHAR_SPACE + 1) 26 | .fill(0) 27 | .map((_, offset) => String.fromCharCode(offset + CHAR_SPACE)); 28 | export const Expanders: Map = new Map(); 29 | 30 | export default class InputMethod implements CompletionItemProvider { 31 | public name: string = "Dummy"; 32 | public languages: string[] = []; 33 | public triggers: string[] = []; 34 | public renderMode: Expander = SimpleExpander; 35 | public commandName?: string; 36 | 37 | private oldConf: InputMethodConf = { 38 | name: "", 39 | languages: [], 40 | triggers: [], 41 | dictionary: [] 42 | }; 43 | private completionItems: CompletionItem[] = []; 44 | public dictionary: InputMethodItem[] = []; 45 | private showQuickPick?: ( 46 | im: InputMethod, 47 | editor: TextEditor, 48 | forced?: boolean 49 | ) => any; 50 | 51 | constructor(context: ExtensionContext, confSeed: InputMethodConf) { 52 | let conf: InputMethodConf; 53 | 54 | if (confSeed.configurationName) { 55 | const confKey = confSeed.configurationName; 56 | const confer: 57 | | InputMethodConf 58 | | undefined = workspace.getConfiguration().get(confKey); 59 | if (!confer) { 60 | throw new InputMethodException( 61 | "Configuration scope error", 62 | `Configuration scope "${confSeed}" not found` 63 | ); 64 | } else { 65 | const builder = confSeed.onDidChangeConfiguration; 66 | let updateIt = (v: any) => { 67 | this.updateConf(context, v); 68 | }; 69 | if (builder) { 70 | updateIt = v => { 71 | const newConf = Object.assign({}, this.oldConf); 72 | const is = builder(v); 73 | newConf.dictionary = is; 74 | this.updateConf(context, newConf); 75 | }; 76 | } 77 | 78 | const onChange = (evt: ConfigurationChangeEvent) => { 79 | if (evt.affectsConfiguration(confKey)) { 80 | const val = workspace.getConfiguration().get(confKey); 81 | if (val) { 82 | updateIt(val); 83 | } else { 84 | window 85 | .showErrorMessage( 86 | `Configuration ${confSeed} updated, but ill-formed`, 87 | "Revert" 88 | ) 89 | .then(msg => { 90 | if (msg) { 91 | switch (msg) { 92 | case "Revert": 93 | workspace 94 | .getConfiguration() 95 | .update(confKey, this.oldConf); 96 | } 97 | } 98 | }); 99 | } 100 | } 101 | }; 102 | 103 | const updater = workspace.onDidChangeConfiguration( 104 | onChange, 105 | this, 106 | context.subscriptions 107 | ); 108 | 109 | context.subscriptions.push(updater); 110 | } 111 | } 112 | conf = confSeed; 113 | 114 | this.updateConf(context, conf); 115 | } 116 | 117 | public updateConf( 118 | context: ExtensionContext, 119 | conf: T 120 | ) { 121 | this.oldConf = conf; 122 | this.name = conf.name; 123 | this.languages = conf.languages; 124 | this.triggers = conf.triggers; 125 | this.dictionary = []; 126 | this.showQuickPick = conf.showQuickPick; 127 | const renderModeSeed = conf.renderMode 128 | ? conf.renderMode 129 | : RenderMode.Snippet; 130 | if (typeof renderModeSeed === "string") { 131 | if (renderModeSeed === RenderMode.Snippet) { 132 | this.renderMode = SimpleExpander; 133 | } else if (renderModeSeed === RenderMode.String) { 134 | this.renderMode = RawStringExpander; 135 | } else { 136 | let exp = Expanders.get(renderModeSeed); 137 | if (exp) { 138 | this.renderMode = exp; 139 | } else { 140 | throw new InputMethodException( 141 | "Initialisation Error", 142 | `No expander \`${renderModeSeed}' found` 143 | ); 144 | } 145 | } 146 | } else { 147 | this.renderMode = renderModeSeed; 148 | } 149 | 150 | this.commandName = conf.commandName; 151 | 152 | function parseFile(fp: string) { 153 | if (!path.isAbsolute(fp)) { 154 | fp = context.asAbsolutePath(fp); 155 | } 156 | 157 | return JSON.parse(readFileSync(fp).toString()); 158 | } 159 | 160 | const dictSeed = conf.dictionary; 161 | let dict: InputMethodItemConfig[] = []; 162 | if (typeof dictSeed === "string") { 163 | dict = parseFile(dictSeed); 164 | } else { 165 | dictSeed.forEach(i => { 166 | if (typeof i === "string") { 167 | dict = dict.concat(parseFile(i)); 168 | } else { 169 | dict.push(i); 170 | } 171 | }); 172 | } 173 | 174 | this.dictionary = dict.map(this.renderMode); 175 | 176 | const commiters = ASCII_CHARS.filter( 177 | c => !this.dictionary.some(i => i.label.indexOf(c) !== -1) 178 | ); 179 | this.completionItems = this.dictionary.map(i => ({ 180 | label: i.label, 181 | insertText: i.toSnippet(), 182 | filterText: i.label, 183 | documentation: i.description || i.toSnippet().value, 184 | commitCharacters: commiters 185 | })); 186 | } 187 | 188 | public async provideCompletionItems( 189 | document: TextDocument, 190 | position: Position, 191 | token: CancellationToken, 192 | context: CompletionContext 193 | ): Promise { 194 | if ( 195 | context.triggerKind === CompletionTriggerKind.TriggerCharacter && 196 | this.triggers.some(c => c === context.triggerCharacter) 197 | ) { 198 | return Promise.all( 199 | this.completionItems.map(async item => { 200 | let start = position; 201 | if (position.character > 0) { 202 | start = new Position(position.line, position.character - 1); 203 | } 204 | let range = new Range(start, position); 205 | if (document.getText(range) === context.triggerCharacter) { 206 | item.additionalTextEdits = [TextEdit.delete(range)]; 207 | } 208 | return item; 209 | }) 210 | ); 211 | } else { 212 | throw Promise.reject(); 213 | } 214 | } 215 | 216 | /** 217 | * quickPickItems 218 | */ 219 | public async quickPickItems(): Promise { 220 | return Promise.all( 221 | this.dictionary.map(async i => { 222 | return { 223 | description: i.description, 224 | label: i.label, 225 | toSnippet: (e?: string) => i.toSnippet(e) 226 | }; 227 | }) 228 | ); 229 | } 230 | 231 | public async invokeQuickPick(editor: TextEditor, forced: boolean = false) { 232 | if (this.showQuickPick) { 233 | this.showQuickPick(this, editor, forced); 234 | } else { 235 | if ( 236 | forced || 237 | this.languages.some(i => i === editor.document.languageId) 238 | ) { 239 | const picks: RenderableQuickPickItem[] = await this.quickPickItems(); 240 | let selection: string | undefined; 241 | if (!editor.selection.isEmpty) { 242 | selection = editor.document.getText(editor.selection); 243 | } 244 | const item = await window.showQuickPick(picks); 245 | if (!item) { 246 | return Promise.reject(); 247 | } 248 | editor.insertSnippet(item.toSnippet(selection)); 249 | } 250 | } 251 | } 252 | } 253 | export class SimpleInputMethodItem implements ToSnippet { 254 | public label: string; 255 | public body: string; 256 | public description?: string; 257 | constructor(i: InputMethodItemConfig) { 258 | this.label = i.label; 259 | this.body = i.body; 260 | this.description = i.description; 261 | } 262 | 263 | /** 264 | * toSnippet 265 | */ 266 | public toSnippet(_?: string): SnippetString { 267 | return new SnippetString(this.body); 268 | } 269 | } 270 | 271 | export interface InputMethodItem extends ToSnippet { 272 | label: string; 273 | body: string; 274 | description?: string; 275 | } 276 | export interface ToSnippet { 277 | toSnippet(selection?: string): SnippetString; 278 | } 279 | 280 | export enum RenderMode { 281 | Snippet = "snippet", 282 | String = "string" 283 | } 284 | 285 | export interface RenderableQuickPickItem extends QuickPickItem, ToSnippet {} 286 | 287 | export interface InputMethodConf { 288 | name: string; 289 | languages: string[]; 290 | triggers: string[]; 291 | dictionary: (InputMethodItemConfig | string)[] | string; 292 | renderMode?: RenderMode | string | Expander; 293 | commandName?: string; 294 | showQuickPick?: ( 295 | im: InputMethod, 296 | editor: TextEditor, 297 | forced?: boolean 298 | ) => any; 299 | 300 | configurationName?: string; 301 | onDidChangeConfiguration?: (config: any) => InputMethodItemConfig[]; 302 | } 303 | 304 | export interface InputMethodItemConfig { 305 | label: string; 306 | body: string; 307 | description?: string; 308 | [index: string]: any; 309 | } 310 | 311 | export type Expander = (conf: InputMethodItemConfig) => InputMethodItem; 312 | 313 | export const SimpleExpander: Expander = i => new SimpleInputMethodItem(i); 314 | 315 | export const RawStringExpander: Expander = i => { 316 | i.body = i.body.replace("$", "\\$").replace("}", "\\}"); 317 | return new SimpleInputMethodItem(i); 318 | }; 319 | -------------------------------------------------------------------------------- /src/test/runTest.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | 3 | import { runTests } from '@vscode/test-electron'; 4 | 5 | async function main() { 6 | try { 7 | // The folder containing the Extension Manifest package.json 8 | // Passed to `--extensionDevelopmentPath` 9 | const extensionDevelopmentPath = path.resolve(__dirname, '../../'); 10 | 11 | // The path to test runner 12 | // Passed to --extensionTestsPath 13 | const extensionTestsPath = path.resolve(__dirname, './suite/index'); 14 | 15 | // Download VS Code, unzip it and run the integration test 16 | await runTests({ extensionDevelopmentPath, extensionTestsPath }); 17 | } catch (err) { 18 | console.error('Failed to run tests'); 19 | process.exit(1); 20 | } 21 | } 22 | 23 | main(); 24 | -------------------------------------------------------------------------------- /src/test/suite/extension.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | 3 | // You can import and use all API from the 'vscode' module 4 | // as well as import your extension to test it 5 | import * as vscode from 'vscode'; 6 | // import * as myExtension from '../../extension'; 7 | 8 | suite('Extension Test Suite', () => { 9 | vscode.window.showInformationMessage('Start all tests.'); 10 | 11 | test('Sample test', () => { 12 | assert.strictEqual(-1, [1, 2, 3].indexOf(5)); 13 | assert.strictEqual(-1, [1, 2, 3].indexOf(0)); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /src/test/suite/index.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import * as Mocha from 'mocha'; 3 | import * as glob from 'glob'; 4 | 5 | export function run(): Promise { 6 | // Create the mocha test 7 | const mocha = new Mocha({ 8 | ui: 'tdd', 9 | color: true 10 | }); 11 | 12 | const testsRoot = path.resolve(__dirname, '..'); 13 | 14 | return new Promise((c, e) => { 15 | glob('**/**.test.js', { cwd: testsRoot }, (err, files) => { 16 | if (err) { 17 | return e(err); 18 | } 19 | 20 | // Add files to the test suite 21 | files.forEach(f => mocha.addFile(path.resolve(testsRoot, f))); 22 | 23 | try { 24 | // Run the mocha test 25 | mocha.run(failures => { 26 | if (failures > 0) { 27 | e(new Error(`${failures} tests failed.`)); 28 | } else { 29 | c(); 30 | } 31 | }); 32 | } catch (err) { 33 | console.error(err); 34 | e(err); 35 | } 36 | }); 37 | }); 38 | } 39 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declarationDir": "types", 5 | "declaration": true, 6 | "target": "es6", 7 | "outDir": "out", 8 | "lib": ["es6"], 9 | "sourceMap": true, 10 | "rootDir": "src", 11 | /* Strict Type-Checking Option */ 12 | "strict": true /* enable all strict type-checking options */, 13 | /* Additional Checks */ 14 | "noUnusedLocals": true /* Report errors on unused locals. */ 15 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 16 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 17 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 18 | }, 19 | "exclude": ["node_modules", ".vscode-test", "types"] 20 | } 21 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-string-throw": true, 4 | "no-unused-expression": true, 5 | "no-duplicate-variable": true, 6 | "curly": true, 7 | "class-name": true, 8 | "semicolon": [ 9 | true, 10 | "always" 11 | ], 12 | "triple-equals": true 13 | }, 14 | "defaultSeverity": "warning" 15 | } -------------------------------------------------------------------------------- /vsc-extension-quickstart.md: -------------------------------------------------------------------------------- 1 | # Welcome to your VS Code Extension 2 | 3 | ## What's in the folder 4 | * This folder contains all of the files necessary for your extension. 5 | * `package.json` - this is the manifest file in which you declare your extension and command. 6 | The sample plugin registers a command and defines its title and command name. With this information 7 | VS Code can show the command in the command palette. It doesn’t yet need to load the plugin. 8 | * `src/extension.ts` - this is the main file where you will provide the implementation of your command. 9 | The file exports one function, `activate`, which is called the very first time your extension is 10 | activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`. 11 | We pass the function containing the implementation of the command as the second parameter to 12 | `registerCommand`. 13 | 14 | ## Get up and running straight away 15 | * Press `F5` to open a new window with your extension loaded. 16 | * Run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World`. 17 | * Set breakpoints in your code inside `src/extension.ts` to debug your extension. 18 | * Find output from your extension in the debug console. 19 | 20 | ## Make changes 21 | * You can relaunch the extension from the debug toolbar after changing code in `src/extension.ts`. 22 | * You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes. 23 | 24 | ## Explore the API 25 | * You can open the full set of our API when you open the file `node_modules/vscode/vscode.d.ts`. 26 | 27 | ## Run tests 28 | * Open the debug viewlet (`Ctrl+Shift+D` or `Cmd+Shift+D` on Mac) and from the launch configuration dropdown pick `Launch Tests`. 29 | * Press `F5` to run the tests in a new window with your extension loaded. 30 | * See the output of the test result in the debug console. 31 | * Make changes to `test/extension.test.ts` or create new test files inside the `test` folder. 32 | * By convention, the test runner will only consider files matching the name pattern `**.test.ts`. 33 | * You can create folders inside the `test` folder to structure your tests any way you want. 34 | --------------------------------------------------------------------------------