「全バージョンが LTS なら、何と比較して Long Term なサポートなんやねん」と思った人もいるかもしれません。ちょっと混乱するのですが、Node.js における「LTS」は major バージョンに割り当てられるステータスのようなものです。新たな major バージョンがリリースされると、まず半年間 Current というステータスになって、その後 30ヶ月間 LTS になります。Current では新機能がどんどん実装されていきますが、LTS ではセキュリティ修正などを主に受け付ける期間になります *1。
OWNER=$(gh repo view --json owner -q .owner.login)REPO=$(gh repo view --json name -q .name)# Setup common repository settings
gh repo edit \--delete-branch-on-merge\--enable-auto-merge\--enable-discussions=false\--enable-projects=false\--enable-secret-scanning\--enable-secret-scanning-push-protection\--enable-wiki=false# Enable Code scanning
gh api -X PATCH /repos/$OWNER/$REPO/code-scanning/default-setup -fstate=configured
# Enable immutable releases
gh api -X PUT /repos/$OWNER/$REPO/immutable-releases
# Setup rulesetsDEFAULT_BRANCH_PROTECTION=$(gh api /repos/mizdra/npm-package-template/rulesets/13184851)VERSION_TAG_PROTECTION=$(gh api /repos/mizdra/npm-package-template/rulesets/13184887)
gh api -X POST /repos/$OWNER/$REPO/rulesets --input - <<<$DEFAULT_BRANCH_PROTECTION
gh api -X POST /repos/$OWNER/$REPO/rulesets --input - <<<$VERSION_TAG_PROTECTION# Require actions to be pinned to a full-length commit SHA
gh api -X PUT /repos/$OWNER/$REPO/actions/permissions -Fenabled=true -Fsha_pinning_required=true
いくつかの設定は gh repo edit でできるのでそれで設定して、残りは gh api で REST API 叩いて設定してる。欲しかった REST API 全部存在してて GitHub すごい。
Emacs にはエディタの動作を切り替える仕組みがあり、これを「Mode」と呼ぶ。Mode には major mode と minor mode の二種類がある。major mode は特定のファイルタイプに特化した機能を提供するもので、1つのファイルにつき 1つだけ割り当てられる。一方 minor mode は1つのファイルに複数割り当て可能なもの。
Emacs 側の議論はちゃんと追ってないけど、多分 tree-sitter の構文定義が統一されているので major mode も統一しているのだろう。
Language Server の設定
先ほど少し触れたが、Emacs には Eglot という Language Client が組み込まれている。major mode と Language Server の起動コマンドの組のリストからなる eglot-server-programs という変数があり、この定義に従って Language Server が起動される。ちなみにデフォルトでは JavaScript と TypeScript では typescript-language-server が Language Server の実装として使われる。
勘の良い人は気づいたかもしれないが、Eglot は 1 つの major mode に複数の Language Server を起動することができない。eglot-server-programs の先頭に定義されている Language Server が優先して起動されることになる。
つまり今回 id:mizdra が紹介した init.el では、*.css に対して TypeScript/CSS 両方の Language Server を起動できない。TypeScript の Language Server だけが起動される。そのため @css-modules-kit/ts-plugin が提供する言語機能 (クラスセレクターの Go to Definition や Find All References など) は機能するが、CSS の Language Server が提供する言語機能 (プロパティの補完など) は機能しない。
Neovim は LSP のサポートを組み込んでいる。ただし、デフォルトではどの Language Server も起動されないようになっている。ユーザが Language Server を起動するための設定が必要になる。
まず Language Server をインストールする必要がある。TypeScript の Language Server は typescript-language-server と vtsls の二種類がある。今回は typescript-language-server を使ってみる。以下のコマンドでインストールできる。
npm i -g typescript-language-server typescript
そして init.lua に以下のような設定を書く。
-- 補完を有効にするための設定
vim.cmd[[set completeopt+=menuone,noselect,popup]]
vim.api.nvim_create_autocmd('LspAttach', {
callback = function(ev)
local client = vim.lsp.get_client_by_id(ev.data.client_id)
if client and client:supports_method('textDocument/completion') then
vim.lsp.completion.enable(true, client.id, ev.buf, { autotrigger = true})
endend,
})
-- TypeScript の Language Server の設定
vim.lsp.config('ts_ls', {
init_options = { hostInfo = 'neovim'},
cmd = {'typescript-language-server', '--stdio'},
filetypes = {'javascript',
'javascriptreact',
'javascript.jsx',
'typescript',
'typescriptreact',
'typescript.tsx',
},
root_markers = {'tsconfig.json', 'jsconfig.json', 'package.json', '.git'},
})
-- TypeScript の Language Server を有効に
vim.lsp.enable('ts_ls')
-- 補完を有効にするための設定
vim.cmd[[set completeopt+=menuone,noselect,popup]]
vim.api.nvim_create_autocmd('LspAttach', {
callback = function(ev)
local client = vim.lsp.get_client_by_id(ev.data.client_id)
if client and client:supports_method('textDocument/completion') then
vim.lsp.completion.enable(true, client.id, ev.buf, { autotrigger = true})
endend,
})
-- TypeScript の Language Server を有効に
vim.lsp.enable('ts_ls')
これだけみると、「nvim-lspconfig から提供される Language Server の設定はどこで読み込んでるんだ?」とびっくりするかも (id:mizdra はびっくりした)。実は vim.lsp.enable('ts_ls') を実行した時に読み込まれている。Package 内の lsp/* から Language Server の設定が自動で探索される挙動となってて、結果として nvim-lspconfig/lsp/ts_ls.lua が読み込まれている。
ちなみに open . で Finder でディレクトリを開くのも、ちゃんと動きます。当初は Finder が Apple Seatbelt の制限を引き継ぐせいで上手く動かないのでは、と思っていたのですが、制限を引き継がずに起動されるようです。open で起動されたアプリケーションは、シェルではなく launchd が親プロセスになり、その結果 Apple Seatbelt による保護が効かないようです。保護を回避できて安全性が下がってしまってますが、普段使いする分にはこちらのほうが都合が良いとも言えます。