1文字でも予測変換（suggest）を出したい！



# 目的

LaTeX-Workshop で LaTeX 書くときに at-suggestion というのがあるじゃないですか。`@`を打つとシュバババ！って変換候補が出てくるあれのことです。 `@a`でαが suggest されるなど便利なので重宝しています。でも Markdown で数式書くときそれ使えない、というか`@`打っても出てこないんですよね。`@`の後に文字を打ったら後にある文字だけで suggestion されてしまうんです。これでは使いづらいので無理やり at-suggestion してしまおうと思った次第であります。

そこで問題になったのが`@`1文字でどうsuggestionさせるか？というところでした（そのためこのようなタイトルになっています）。



ちなみに今回紹介する方法は突き詰めると「英数字以外の記号であれば1文字を打っただけで suggest できるようになる」という汎用性が高いものなので Markdown、LaTeX 以外の他の言語等でも使えます。




# suggestionの表示

Markdown ファイルに`@`を一文字入力すると勝手に自分で定めた snippets を suggestion をしてくれるようにする、という今回の目的を確認しました。

さて、それをできる方法がないかなといじっているうちに途中に気づいたのですが、さすがはVSCode、`editor.action.triggerSuggest`という手動でsuggestionを表示させるコマンドがありました。ということは`@`をショートカットキーとして`editor.action.triggerSuggest`すればいいのでは！？と思いましたがこれだけではうまくいきません。肝心の`@`の文字が入力できないのです。

結局やりたかったことは`@`キーを押すと

- `@`を出力する
- `editor.action.triggerSuggest`

の2つだったわけです。いろいろ試した結果これを可能にしてくれたのが、VSCodeの拡張機能 [multi-command](https://marketplace.visualstudio.com/items?itemName=ryuta46.multi-command)でした。`multi-command`そのものの使い方は本家のサイトを見ていただければわかると思います。

`settings.json`に以下を記入します。

```json:settings.json
{
"multiCommand.atsuggestion": {
	"sequence": [
		{
			"command": "type",
			"args": {
				"text": "@"
			}
		},
		"editor.action.triggerSuggest"
	]
},
}
```

typeという文字`@`を打ち込むコマンドの後にsuggestするmulticommandです。

`keybindings.json`に以下を記入します。


```json:keybindings.json
{
		"key": "[BracketLeft]",
		"command": "extension.multiCommand.execute",
		"args": {
			"command": "multiCommand.atsuggestion"
		},
		"when": "editorFocus && editorLangId == markdown || latex"
}
```

`@`はJISキーボードとUSキーボードの配列の違いから[BracketLeft]のようです。こういう、何が何に対応しているのかは「キーボードショートカット」（jsonじゃない方）下の画像の部分を押して確認したいキーを打つと出ます。今回は`@`を打ちました。

![SS_2022-09-03_22-51-51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2823185/77fc60d7-b5ea-c3f4-c1b4-307048216e1b.png)


なお`when`以下の内容は現在のエディタの言語設定（とVSCodeがみなすもの）がmarkdownかlatexのときにキーボードショートカットを行うことを意味します。この「言語設定」、数式打ってる途中は本来`latex`になる（したがってlatexで定めたsnippetが使える）のところ inline 数式ではよくわからないルールで突然`markdown`になるのでどっちも含めて置くのが安全です。

ちなみに Markdown で at-suggestion するにはその上で手動で`@`から始まる snippets を登録し直す必要があります。その際`markdown`と`latex`の2つの言語で登録し直す必要があるので言語ごとのスニペットではなくグローバルスニペットファイルに登録して`scope`で言語設定を行います。`@`で始まるもののスニペットを根こそぎ作り直します。[at-suggestion](https://github.com/James-Yu/LaTeX-Workshop/blob/master/data/at-suggestions.json) のユーザースニペットと同じ形で書かれたsnippetファイルがあるのでそこからごっそり移植して`scope`を`latex,markdown`にします。ちなみに数式関係でmarkdownでもlatexでも使いそうなものはこの他にもこのファイルに登録した方が汎用性が広がると思います。

[LaTeX-Workshop](https://github.com/James-Yu/LaTeX-Workshop)によるとMITライセンス（[https://github.com/James-Yu/LaTeX-Workshop/blob/master/LICENSE.txt](https://github.com/James-Yu/LaTeX-Workshop/blob/master/LICENSE.txt) の内容より）ということなので冒頭にMITライセンスの内容を掲載しています。この記事ではMITライセンスを根拠として修正（modify）しています。

<details>
<summary>atで始まるもののスニペット</summary>


```jsonc:グローバルsnippetsファイル
{
// The MIT License (MIT)

// Copyright (c) 2022 migawariw

// // Permission is hereby granted, free of charge, to any person obtaining a copy
// // of this software and associated documentation files (the "Software"), to deal
// // in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// // The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.

// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.


// The MIT License (MIT)

// Copyright (c) 2016 James Yu

// // Permission is hereby granted, free of charge, to any person obtaining a copy
// // of this software and associated documentation files (the "Software"), to deal
// // in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// // The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.

// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.


	"cdot": {
		"prefix": "@.",
		"scope":"latex,markdown",
		"body": "\\cdot ",
		"description": "\\cdot"
	},
	"infinity": {
		"prefix": "@8",
		"scope":"latex,markdown",
		"body": "\\infty ",
		"description": "infinity symbol"
	},
	"partial": {
		"prefix": "@6",
		"scope":"latex,markdown",
		"body": "\\partial ",
		"description": "partial derivative symbol"
	},
	"fraction": {
		"prefix": "@/",
		"scope":"latex,markdown",
		"body": "\\frac{$1}{$2}$0 ",
		"description": "fraction"
	},
	"fraction2": {
		"prefix": "@%",
		"scope":"latex,markdown",
		"body": "\\frac{$1}{$2}$0 ",
		"description": "fraction"
	},
	"hat": {
		"prefix": "@^",
		"scope":"latex,markdown",
		"body": "\\hat{${1:${TM_SELECTED_TEXT}}}$0 ",
		"description": "hat"
	},
	"bar": {
		"prefix": "@_",
		"scope":"latex,markdown",
		"body": "\\bar{${1:${TM_SELECTED_TEXT}}}$0 ",
		"description": "bar"
	},
	"circ": {
		"prefix": "@@",
		"scope":"latex,markdown",
		"body": "\\circ ",
		"description": "circ"
	},
	"supcirc": {
		"prefix": "@0",
		"scope":"latex,markdown",
		"body": "^\\circ ",
		"description": "superscript circ"
	},
	"dot": {
		"prefix": "@;",
		"scope":"latex,markdown",
		"body": "\\dot{${1:${TM_SELECTED_TEXT}}}$0 ",
		"description": "dot"
	},
	"ddot": {
		"prefix": "@:",
		"scope":"latex,markdown",
		"body": "\\ddot{${1:${TM_SELECTED_TEXT}}}$0 ",
		"description": "ddot"
	},

	"equiv": {
		"prefix": "@=",
		"scope":"latex,markdown",
		"body": "\\equiv ",
		"description": "equiv symbol"
	},
	"times": {
		"prefix": "@*",
		"scope":"latex,markdown",
		"body": "\\times ",
		"description": "times symbol"
	},
	"leq": {
		"prefix": "@<",
		"scope":"latex,markdown",
		"body": "\\leq ",
		"description": "leq symbol"
	},
	"geq": {
		"prefix": "@>",
		"scope":"latex,markdown",
		"body": "\\geq ",
		"description": "geq symbol"
	},
	"sqrt": {
		"prefix": "@2",
		"scope":"latex,markdown",
		"body": "\\sqrt{${1:${TM_SELECTED_TEXT}}}$0 ",
		"description": "sqrt command"
	},
	"int": {
		"prefix": "@I",
		"scope":"latex,markdown",
		"body": "\\int_{$1}^{$2}$0 ",
		"description": "integral"
	},
	"Big|": {
		"prefix": "@|",
		"scope":"latex,markdown",
		"body": "\\Big| ",
		"description": "Big |"
	},
	"bigcup": {
		"prefix": "@+",
		"scope":"latex,markdown",
		"body": "\\bigcup ",
		"description": "bigcup"
	},
	"bigcap": {
		"prefix": "@-",
		"scope":"latex,markdown",
		"body": "\\bigcap ",
		"description": "bigcap"
	},
	"nonumber": {
		"prefix": "@,",
		"scope":"latex,markdown",
		"body": "\\nonumber ",
		"description": "nonumber"
	},
	"alpha": {
		"prefix": "@a",
		"scope":"latex,markdown",
		"body": "\\alpha ",
		"description": "alpha"
	},
	"beta": {
		"prefix": "@b",
		"scope":"latex,markdown",
		"body": "\\beta ",
		"description": "beta"
	},
	"chi": {
		"prefix": "@c",
		"scope":"latex,markdown",
		"body": "\\chi ",
		"description": "chi"
	},
	"delta": {
		"prefix": "@d",
		"scope":"latex,markdown",
		"body": "\\delta ",
		"description": "delta"
	},
	"epsilon": {
		"prefix": "@e",
		"scope":"latex,markdown",
		"body": "\\epsilon ",
		"description": "epsilon"
	},
	"varepsilon": {
		"prefix": "@ve",
		"scope":"latex,markdown",
		"body": "\\varepsilon ",
		"description": "varepsilon"
	},
	"phi": {
		"prefix": "@f",
		"scope":"latex,markdown",
		"body": "\\phi ",
		"description": "phi"
	},
	"varphi": {
		"prefix": "@vf",
		"scope":"latex,markdown",
		"body": "\\varphi ",
		"description": "varphi"
	},
	"gamma": {
		"prefix": "@g",
		"scope":"latex,markdown",
		"body": "\\gamma ",
		"description": "gamma"
	},
	"eta": {
		"prefix": "@h",
		"scope":"latex,markdown",
		"body": "\\eta ",
		"description": "eta"
	},
	"iota": {
		"prefix": "@i",
		"scope":"latex,markdown",
		"body": "\\iota ",
		"description": "iota"
	},
	"kappa": {
		"prefix": "@k",
		"scope":"latex,markdown",
		"body": "\\kappa ",
		"description": "kappa"
	},
	"lambda": {
		"prefix": "@l",
		"scope":"latex,markdown",
		"body": "\\lambda ",
		"description": "lambda"
	},
	"mu": {
		"prefix": "@m",
		"scope":"latex,markdown",
		"body": "\\mu ",
		"description": "mu"
	},
	"nu": {
		"prefix": "@n",
		"scope":"latex,markdown",
		"body": "\\nu ",
		"description": "nu"
	},
	"pi": {
		"prefix": "@p",
		"scope":"latex,markdown",
		"body": "\\pi ",
		"description": "pi"
	},
	"varpi": {
		"prefix": "@vp",
		"scope":"latex,markdown",
		"body": "\\varpi ",
		"description": "varpi"
	},
	"theta": {
		"prefix": "@q",
		"scope":"latex,markdown",
		"body": "\\theta ",
		"description": "theta"
	},
	"vartheta": {
		"prefix": "@vq",
		"scope":"latex,markdown",
		"body": "\\vartheta ",
		"description": "vartheta"
	},
	"rho": {
		"prefix": "@r",
		"scope":"latex,markdown",
		"body": "\\rho ",
		"description": "rho"
	},
	"varrho": {
		"prefix": "@vr",
		"scope":"latex,markdown",
		"body": "\\varrho ",
		"description": "varrho"
	},
	"sigma": {
		"prefix": "@s",
		"scope":"latex,markdown",
		"body": "\\sigma ",
		"description": "sigma"
	},
	"varsigma": {
		"prefix": "@vs",
		"scope":"latex,markdown",
		"body": "\\varsigma ",
		"description": "varsigma"
	},
	"tau": {
		"prefix": "@t",
		"scope":"latex,markdown",
		"body": "\\tau ",
		"description": "tau"
	},
	"upsilon": {
		"prefix": "@u",
		"scope":"latex,markdown",
		"body": "\\upsilon ",
		"description": "upsilon"
	},
	"omega": {
		"prefix": "@o",
		"scope":"latex,markdown",
		"body": "\\omega ",
		"description": "omega"
	},
	"wedge": {
		"prefix": "@&",
		"scope":"latex,markdown",
		"body": "\\wedge ",
		"description": "wedge"
	},
	"xi": {
		"prefix": "@x",
		"scope":"latex,markdown",
		"body": "\\xi ",
		"description": "xi"
	},
	"psi": {
		"prefix": "@y",
		"scope":"latex,markdown",
		"body": "\\psi ",
		"description": "psi"
	},
	"zeta": {
		"prefix": "@z",
		"scope":"latex,markdown",
		"body": "\\zeta ",
		"description": "zeta"
	},
	"Delta": {
		"prefix": "@D",
		"scope":"latex,markdown",
		"body": "\\Delta ",
		"description": "Delta"
	},
	"Phi": {
		"prefix": "@F",
		"scope":"latex,markdown",
		"body": "\\Phi ",
		"description": "Phi"
	},
	"Gamma": {
		"prefix": "@G",
		"scope":"latex,markdown",
		"body": "\\Gamma ",
		"description": "Gamma"
	},
	"Theta": {
		"prefix": "@Q",
		"scope":"latex,markdown",
		"body": "\\Theta ",
		"description": "Theta"
	},
	"Lambda": {
		"prefix": "@L",
		"scope":"latex,markdown",
		"body": "\\Lambda ",
		"description": "Lambda"
	},
	"Pi": {
		"prefix": "@P",
		"scope":"latex,markdown",
		"body": "\\Pi ",
		"description": "Pi"
	},
	"Xi": {
		"prefix": "@X",
		"scope":"latex,markdown",
		"body": "\\Xi ",
		"description": "Xi"
	},
	"Psi": {
		"prefix": "@Y",
		"scope":"latex,markdown",
		"body": "\\Psi ",
		"description": "Psi"
	},
	"Sigma": {
		"prefix": "@S",
		"scope":"latex,markdown",
		"body": "\\Sigma ",
		"description": "Sigma"
	},
	"Upsilon": {
		"prefix": "@U",
		"scope":"latex,markdown",
		"body": "\\Upsilon ",
		"description": "Upsilon"
	},
	"Omega": {
		"prefix": "@W",
		"scope":"latex,markdown",
		"body": "\\Omega ",
		"description": "Omega"
	},
	"(": {
		"prefix": "@(",
		"scope":"latex,markdown",
		"body": "\\left( ${1:${TM_SELECTED_TEXT}} \\right) ",
		"description": "left( ... right)"
	},
	"{": {
		"prefix": "@{",
		"scope":"latex,markdown",
		"body": "\\left\\{ ${1:${TM_SELECTED_TEXT}} \\right\\\\} ",
		"description": "left{ ... right}"
	},
	"[": {
		"prefix": "@[",
		"scope":"latex,markdown",
		"body": "\\left[ ${1:${TM_SELECTED_TEXT}} \\right] ",
		"description": "left[ ... right]"
	}
}

```
</details>

これは自分の好みなのですがギリシャ文字の後に空白を入れるようにしています。空白を入れたくない場合はこのファイルの空白文字を全置換するとなくせます。あと$\Omega$のprefixがデフォルトのLaTeX-Workshopの設定だと`@W`になっていたので`@O`に変えています。

あとこのままだとtexファイル入力中にもともとの at-suggestion と競合してしまうので settings.json を変更して at-suggestion をほとんどなくしておきます。
<details>
<summary>settings.json</summary>

```jsonc:settings.json
"latex-workshop.intellisense.atSuggestionJSON.replace": {
		"@a": "",
		"@b": "",
		"@c": "",
		"@d": "",
		"@e": "",
		"@ve": "",
		"@f": "",
		"@ff": "",
		"@g": "",
		"@h": "",
		"@i": "",
		"@k": "",
		"@l": "",
		"@m": "",
		"@n": "",
		"@p": "",
		"@vp": "",
		"@q": "",
		"@vq": "",
		"@r": "",
		"@vr": "",
		"@s": "",
		"@vs": "",
		"@t": "",
		"@u": "",
		"@o": "",
		"@x": "",
		"@y": "",
		"@z": "",
		"@D": "",
		"@F": "",
		"@G": "",
		"@Q": "",
		"@L": "",
		"@P": "",
		"@X": "",
		"@Y": "",
		"@S": "",
		"@U": "",
		"@W": "",
		//"@{": "",
		"@.": "",
		"@8": "",
		"@6": "",
		"@/": "",
		"@%": "",
		"@_": "",
		"@@": "",
		"@0": "",
		"@;": "",
		"@:": "",
		"@=": "",
		"@*": "",
		"@<": "",
		"@>": "",
		"@2": "",
		"@|": "",
		"@+": "",
		"@-": "",
		"@,": "",
		"@(": "",
		"@[": "",
		"@&": "",
	},
```
</details>


どうやらデフォルトでは`{`が`@`みたいな「トリガー文字」（1文字でsuggestionする文字）になっているようで（理由はよくわからないのですが）`@`を`multicommand`でいじると`@{`で`\left\{\right\}`を suggest できなくなります。




# やってみる

![SS_25_trim.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2823185/3a25bc32-4597-d450-42bd-d0939519c2f9.gif)


無事 at-suggestion できました！なおこの方法を使うと tex ファイルにおいても

- `/`だけで`\frac{}{}`をsuggestする（wordで数式使うノリ）
- `*`だけで`\times`をsuggestする
- `.`だけで`\cdot`をsuggestする
- `@`だけで`&`をsuggestする

みたいなこともできます。`@`だけで`&`をsuggestすると行列打つときに`shift`打ちながら`6`打つというちょっと頭を使う（？）作業をしなくて済むのでちょっと気持ちが楽になります。

# 参考文献

https://marketplace.visualstudio.com/items?itemName=ryuta46.multi-command


https://github.com/James-Yu/LaTeX-Workshop