戻る
6570c11

Neovimの設定をlispで書く

Emacsの利点として設定をLispで書ける点が挙げられるが Neovimでも設定ファイルをLispで記述することが出来る。

#Fennel

Luaへコンパイル可能な言語には有名どころだと MoonScriptHaxe があるが、 Fennel というLisp方言もある。 最新版の 0.8.1 のリリースも2021年2月と更新も盛んなようだ。

基本的にはLexical scopeを持つシンプルなLispだが、 Luaとの互換性を重視して設計されているようで手で書いたLuaと近い形にコンパイルされるのを利点としている。 Lispなので強力なマクロ機能が使えるのが素のLuaに対する利点と言える。

#Aniseed

このFennelを使ってNeovimの設定を書けるプラグインとして Aniseed がある。 余談だがAniseedはアニスの果実の意味でフェンネルと同じ香り(アネトール)がする。

このプラグインは $XDG_HOME_CONFIG/nvim/fnl/*$XDG_HOME_CONFIG/nvim/lua/* のコンフィグへとコンパイルする機能と Fennelを使ってプラグインを書くための機能の2種類の機能があり、 またNeovimのapiのうち lsptreesitter を除いたものへの薄いラッパー、 いくつかのユーティリティ関数も提供されている。

ユーティリティ関数については :help aniseed に全て書かれているがラッパーAPIの詳細は nvim.lua に準拠しているため ここのドキュメントを参照する必要がある。

また当然ながらブートストラップのためのLuaは書く必要がある。 私はパッケージマネージャに packer を使っているため packer.nvimaniseed をインストールする以下のスクリプトを init.lua としている。

-- Bootstrap
function exists(file)
  local ok, err, code = os.rename(file, file)
  if not ok then
    if code == 13 then
      return true
    end
  end
  return ok, err
end
local install_path = vim.fn.stdpath('data')..'/site/pack/packer/opt/'
if not exists(install_path..'packer.nvim') then
  vim.cmd('!git clone https://github.com/wbthomason/packer.nvim '..install_path..'packer.nvim')
end
vim.cmd 'packadd packer.nvim'
if not exists(install_path..'aniseed') then
  vim.cmd('!git clone https://github.com/Olical/aniseed '..install_path..'aniseed')
end
vim.cmd 'packadd aniseed'
vim.api.nvim_set_var('aniseed#env', true)

#注意点

配列も連想配列(Luaでは配列と連想配列は同じ扱いだが)もLuaでは {} を使って書くが、 Fennelでは配列は [] 、連想配列は {} を使って書く。 Fennelのatom( :abc のように書く)を使えばLuaの {a = 42, b = "alpha"}{:a 42 :b "alpha"} と書ける。

また each を使うには素の配列ではダメで、 pairs または ipairs を使う必要がある。

モジュールの宣言時に必要なパッケージを require 出来る(Luaのパッケージも直接 require 出来る)が、 パッケージマネージャの設定より先にパッケージの require を書くとブートストラップが出来なくなるので その場合は個別に defrequire を使ってパッケージを読み込むべき。

#設定のサンプル

fnl/init.fnlfnl/cfg/completion.fnlfnl/cfg/statusline.fnl の順に載せる。

(module nvim-packages
  {require {a aniseed.core
            s aniseed.string
            completion_cfg cfg.completion
            statusline_cfg cfg.statusline
            nvim aniseed.nvim
            packer packer}})

(packer.startup (lambda []
                  (do
                    (each [_ pkg (pairs completion_cfg.packages)]
                      (use pkg))
                    (each [_ pkg (pairs statusline_cfg.packages)]
                      (use pkg))
                    (use "bakpakin/fennel.vim")
                    (use { 1 "Olical/aniseed" :opt true } )
                    (use { 1 "wbthomason/packer.nvim" :opt true } )
                    (use "lambdalisue/fern.vim")
                    (use "lambdalisue/nerdfont.vim")
                    (use "lambdalisue/fern-renderer-nerdfont.vim")
                    (use "lambdalisue/fern-git-status.vim")
                    (use "lambdalisue/fern-mapping-git.vim")
                    (use "lambdalisue/fern-hijack.vim")
                    (use "namachan10777/nvim-highlite-otynium")
                    ; (use { 1 'JuliaEditorSupport/julia-vim'  :ft='julia' }) bug?
                    (use { 1 "nvim-lua/plenary.nvim" :ft "lua" })
                    (use { 1 "tjdevries/manillua.nvim" :ft "lua" })
                    (use { 1 "euclidianAce/BetterLua.vim" :ft "lua" })
                    (use { 1 "pest-parser/pest.vim" :ft "pest" })
                    (use { 1 "ElmCast/elm-vim" :ft "elm"})
                    (use { 1 "prettier/vim-prettier" :ft ["typescript" "typescriptreact" "javascript"]})
                    (use { 1 "jalvesaq/Nvim-R" :ft "R" })
                    (use { 1 "qnighy/satysfi.vim" :ft "satysfi" })
                    (use { 1 "cespare/vim-toml" :ft "toml" })
                    (use { 1 "qnighy/lalrpop.vim" :ft "lalrpop" })
                    (use { 1 "namachan10777/tml.vim" :ft "tml" })
                    (use { 1 "ron-rs/ron.vim" :ft "ron" })
                    (use "t9md/vim-quickhl")
                    (use "nvim-treesitter/nvim-treesitter")
                    (use "nvim-treesitter/completion-treesitter"))))

(def treesitter (require "nvim-treesitter.configs"))

(completion_cfg.configure)
(statusline_cfg.configure)

(fn colorscheme [name]
  (nvim.ex.colorscheme name))

(fn list [...]
  [...])

(fn set_indent [confs]
  (do
    (nvim.ex.augroup :FileTypeIndent)
    (nvim.ex.autocmd_)
    (each [_ conf (pairs confs)]
      (nvim.ex.autocmd "FileType"
                       (s.join "," conf.ft)
                       "setlocal"
                       (.. "tabstop=" conf.w)
                       (.. "shiftwidth=" conf.w)
                       (if conf.expand :expandtab :noexpandtab)))
    (nvim.ex.augroup :END)))

(do
  (nvim.ex.augroup :SaveEditPos)
  (nvim.ex.autocmd_)
  (nvim.ex.autocmd :BufReadPost "*" "if line(\"'\\\"\") > 1 && line(\"'\\\"\") <= line(\"$\") | exe \"normal! g`\\\"\" | endif")
  (nvim.ex.augroup :END))

(set_indent (list
              {:ft (list :typescript :typescriptreact :javascript)
               :w 2
               :expand true}
              {:ft (list :python :haskell) :w 4 :expand true}
              {:ft (list :yaml) :w 2 :expand true}
              {:ft (list :plaintex :satysfi :tml) :w 2 :expand true}))

(nvim.set_keymap "n" "r" "diwi" { :noremap true })
(nvim.set_keymap "n" "j" "gj" { :noremap true })
(nvim.set_keymap "n" "k" "gk" { :noremap true })
(nvim.set_keymap "t" "<C-j>" "<C-\\><C-n>" { :noremap true })

; Fern
(nvim.set_var "fern#renderer" "nerdfont")
(nvim.set_keymap "n" "<space>f" ":Fern . -drawer<CR>" { :noremap true })
(nvim.set_keymap "x" "<space>f" ":Fern . -drawer<CR>" { :noremap true })

; Quickhl
(nvim.set_keymap "n" "<Space>m" "<Plug>(quickhl-manual-this)"  { :noremap false })
(nvim.set_keymap "x" "<Space>m" "<Plug>(quickhl-manual-this)"  { :noremap false })
(nvim.set_keymap "n" "<Space>M" "<Plug>(quickhl-manual-reset)" { :noremap false })
(nvim.set_keymap "x" "<Space>M" "<Plug>(quickhl-manual-reset)" { :noremap false })

(set nvim.bo.undofile true)
(set nvim.wo.foldmethod "marker")
(set nvim.o.undolevels 1024)
(set nvim.o.undoreload 8192)
(set nvim.o.swapfile false)
(set nvim.o.backup false)
(set nvim.o.writebackup false)
(set nvim.bo.tabstop 4)
(set nvim.bo.shiftwidth 4)
(set nvim.bo.expandtab false)
(set nvim.o.termguicolors true)
(set nvim.wo.number true)
(set nvim.wo.relativenumber true)
(set nvim.o.cmdheight 2)
(set nvim.o.hls true)
(set nvim.wo.list true)
(set nvim.o.listchars "tab:»-,trail:-,eol:↲,extends:»,precedes:«,nbsp:%")
(set nvim.o.hidden true)
(set nvim.o.updatetime 300)
(colorscheme "otynium")
(treesitter.setup {:ensure_installed "maintained"
                   :highlight {:enable true
                               :disable (list)}
                   :indent {:enable true}})
(module cfg.completion
  {require {a aniseed.core
            s aniseed.string
            lsp vim.lsp
            nvim aniseed.nvim}})

(def packages ["neovim/nvim-lspconfig"
               "nvim-lua/completion-nvim"
               "steelsojka/completion-buffers"
               "nvim-lua/lsp-status.nvim"])

(fn attach_completion []
  (do
    (nvim.ex.augroup :AttachCompletion)
    (nvim.ex.autocmd_)
    (nvim.ex.autocmd :BufEnter "*" "lua require'completion'.on_attach()")))

(defn configure []
  (let [lsp_status (require "lsp-status")
     lspconfig (require "lspconfig")]
    (do
      (lsp_status.register_progress)
      (lspconfig.pyright.setup {:on_attach lsp_status.on_attach
                                :capabilities lsp_status.capabilities})
      (lspconfig.ocamllsp.setup {:on_attach lsp_status.on_attach
                                 :capabilities lsp_status.capabilities})
      (lspconfig.rust_analyzer.setup {:on_attach lsp_status.on_attach
                                      :capabilities lsp_status.capabilities})
      (lspconfig.texlab.setup {:on_attach lsp_status.on_attach
                               :capabilities lsp_status.capabilities})
      (lspconfig.clangd.setup {:on_attach lsp_status.on_attach
                               :capabilities lsp_status.capabilities
                               :handlers (lsp_status.extensions.clangd.setup)})
      (attach_completion)
      (set nvim.g.completion_chain_complete_list
           {:default [{:complete_items ["buffers"]}
                      {:mode [ "<c-p>"]}
                      {:mode [ "<c-n>"]}]
            :python [{:complete_items ["lsp"]}
                     {:mode [ "<c-p>"]}
                     {:mode [ "<c-n>"]}]
            :ocaml [{:complete_items ["lsp"]}
                    {:mode [ "<c-p>"]}
                    {:mode [ "<c-n>"]}]
            :plaintex [{:complete_items ["lsp"]}
                       {:mode [ "<c-p>"]}
                       {:mode [ "<c-n>"]}]
            :rust [{:complete_items ["lsp"]}
                  {:mode [ "<c-p>"]}
                  {:mode [ "<c-n>"]}]
            :c [{:complete_items ["lsp"]}
                {:mode [ "<c-p>"]}
                {:mode [ "<c-n>"]}]
            :cpp [{:complete_items ["lsp"]}
                  {:mode [ "<c-p>"]}
                  {:mode [ "<c-n>"]}]})
      (nvim.ex.inoremap "<expr>" "<Tab>" "pumvisible() ? \"\\<C-n>\" : \"\\<Tab>\"")
      (nvim.ex.inoremap "<expr>" "<S-Tab>" "pumvisible() ? \"\\<C-n>\" : \"\\<S-Tab>\"")
      (set nvim.o.completeopt "menuone,noinsert,noselect")
      (set nvim.o.shortmess (.. nvim.o.shortmess "c")))))
(module cfg.statusline
  {require {a aniseed.core
            s aniseed.string
            lsp vim.lsp
            nvim aniseed.nvim}})

(def packages [{1 "glepnir/galaxyline.nvim"
                :branch "main"
                :requires {1 "kyazdani42/nvim-web-devicons" :opt true}}])

(fn buffer_not_empty []
 (~= (nvim.fn.empty (nvim.fn.expand "%:t")) 1))
(fn checkwidth []
 (> (nvim.fn.winwidth 0) 80))
(fn lspStatus []
  (let [lsp_status (require "lsp-status")]
    (if (> (length (vim.lsp.buf_get_clients)) 0)
      (lsp_status.status)
      "no")))

(def aliases {"n" "NORMAL" "i" "INSERT" "c" "COMMAND" "V" "VISUAL" "^V" "VISUAL"})

(defn configure []
  (let
    [gl (require "galaxyline")
     gls gl.section
     colors {:bg "#282c34"
             :yellow "#fabd2f"
             :cyan "#008080"
             :darkblue "#081633"
             :green "#afd700"
             :orange "#FF8800"
             :purple "#5d4d7a"
             :magenta "#d16d9e"
             :grey "#c0c0c0"
             :blue "#0087d7"
             :red "#ec5f67"
             }]
    (do
      (set gl.short_line_list ["LuaTree" "vista" "dbui"])
      (set gls.left [{:FirstElement {:provider (lambda [] " ")
                                     :highlight [colors.blue colors.yellow]}}
                     {:ViMode {:provider (lambda [] (. aliases (nvim.fn.mode)))
                               :separator ""
                               :separator_highlight [colors.yellow
                                                     (lambda []
                                                       (if (not (buffer_not_empty)) colors.purple colors.darkblue))]
                               :highlight [colors.magenta colors.yellow "bold"]}}
                     {:FileIcon {:provider "FileIcon"
                                 :condition buffer_not_empty
                                 :highlight [(lambda [] (require "galaxyline.provider_fileinfo").get_file_icon_color) colors.darkblue]}}
                     {:FileName {:provider ["FileIcon" "FileSize"]
                                 :condition buffer_not_empty
                                 :separator ""
                                 :separator_highlight [colors.purple colors.darkblue]
                                 :highlight [colors.magenta colors.darkblue]}}
                     {:GitIcon {:provider (lambda [] "")
                                :condition buffer_not_empty
                                :highlight [colors.orange colors.purple]}}
                     {:GitBranch {:provider "GitBranch"
                                  :condition buffer_not_empty
                                  :highlight [colors.grey colors.purple]}}
                     {:DiffAdd {:provider "DiffAdd"
                                :icon (lambda [] "")
                                :condition checkwidth
                                :highlight [colors.green colors.purple]}}
                     {:DiffModified {:provider "DiffModified"
                                     :icon (lambda [] "")
                                     :condition checkwidth
                                     :highlight [colors.green colors.purple]}}
                     {:DiffRemove {:provider "DiffRemove"
                                   :icon (lambda [] "")
                                   :condition checkwidth
                                   :highlight [colors.green colors.purple]}}
                     {:LeftEnd {:provider (lambda [] "")
                                 :separator ""
                                 :separator_highlight [colors.purple colors.bg]
                                 :highlight [colors.purple colors.purple]}}
                     {:DiagonosticError {:provider "DiagnosticError"
                                         :separator ""
                                         :highlight [colors.red colors.bg]}}
                     {:Space {:provider (lambda [] " ")}}
                     {:DiagnosticWarn {:provider "DiagnosticWarn"
                                       :separator ""
                                       :highlight [colors.blue colors.bg]}}
                     {:LspStatus {:provider lspStatus}}])
      (set gls.right [{:FileFormat {:provider "FileFormat"
                                    :separator ""
                                    :separator_highlight [colors.bg colors.purple]
                                    :highlight [colors.grey colors.purple]}}
                      {:LineInfo {:provider "LineColumn"
                                  :separator " | "
                                  :separator_highlight [colors.darkblue colors.purple]
                                  :highlight [colors.grey colors.purple]}}
                      {:LinePercent {:provider "LinePercent"
                                     :separator ""
                                     :separator_highlight [colors.darkblue colors.purple]
                                     :highlight [colors.grey colors.darkblue]}}
                      {:ScrollBar {:provider "ScrollBar"
                                   :highlight [colors.yellow colors.purple]}}])
      (set gls.short_line_left [{:FileTypeName {:provider "FileTypeName"
                                               :separator ""
                                               :separator_highlight [colors.purple colors.bg]
                                               :highlight [colors.grey colors.purple]}}
                                 {:BufferIcon {:provider "BufferIcon"
                                             :separator ""
                                             :separator_highlight [colors.purple colors.bg]
                                             :highlight [colors.grey colors.purple]}}])
      )))