ビットの海

ゆるふわソフトウェアエンジニアしゃぜのブログ

Pythonコーディング用にneovimを最低限整える(2024年夏)

さて前回のコレから5年が経過しています...

shase428.hatenablog.jp

shase428.hatenablog.jp

コンセプト

  • とりあえずこれくらいやればLSP動くよ〜という紹介
  • 脱 coc.nvim
  • LSは手動で管理するので、mason.nvim みたいなのは使わない
  • Python の LSP を最低限の設定で動かして、Ruff で Lint/Format する

環境

brew install nvim したもの

$ nvim -version
NVIM v0.10.0
Build type: Release
LuaJIT 2.1.1716656478

今回登場するプラグイン

プラグイン管理

folke/lazy.nvim でいきます。 Mac の場合は、~/.local/share/nvim/lazy 配下に git clone した plugin が置かれます。

vim.loader.enable()
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not vim.loop.fs_stat(lazypath) then
  vim.fn.system({
    "git",
    "clone",
    "--filter=blob:none",
    "https://github.com/folke/lazy.nvim.git",
    "--branch=stable", -- latest stable release
    lazypath,
  })
end
vim.opt.rtp:prepend(lazypath)

require("lazy").setup({
  "neovim/nvim-lspconfig",
  "hrsh7th/nvim-cmp",
  "hrsh7th/cmp-nvim-lsp",
  "hrsh7th/cmp-buffer",
  "hrsh7th/cmp-path",
  "saadparwaiz1/cmp_luasnip",
  "L3MON4D3/LuaSnip",
  "dense-analysis/ale",
})

LSPと補完の設定

なんでも良いんですけど、惰性で、jedi-language-server を使っています。

コンセプト

  • 各プロジェクトの .venv 配下を見せたい
  • $PROJECT_VENV を direnv で制御している
    • Pythonプロジェクトのrootに、以下のような .envrc を置いておく
export VENV_DIR=".venv"
export PROJECT_ROOT=`pwd`
export PROJECT_VENV=${PROJECT_ROOT}/${VENV_DIR}
export PATH=${PROJECT_VENV}/bin:$PATH

init.lua

-- lspのハンドラーに設定
capabilities = require("cmp_nvim_lsp").default_capabilities()

-- lspの設定後に追加
vim.opt.completeopt = "menu,menuone,noselect"

local cmp = require"cmp"
cmp.setup({
  snippet = {
    expand = function(args)
      require("luasnip").lsp_expand(args.body)
    end,
  },
  mapping = cmp.mapping.preset.insert({
    ["<C-p>"] = cmp.mapping.select_prev_item(),
    ["<C-n>"] = cmp.mapping.select_next_item(),
    ["<C-d>"] = cmp.mapping.scroll_docs(-4),
    ["<C-f>"] = cmp.mapping.scroll_docs(4),
    ["<C-Space>"] = cmp.mapping.complete(),
    ["<C-e>"] = cmp.mapping.close(),
    ["<CR>"] = cmp.mapping.confirm({ select = true }),
  }),
  sources = cmp.config.sources({
    { name = "nvim_lsp" },
    { name = "luasnip" },
  }, {
    { name = "buffer" },
  })
})

-- python の path の切り替え
local function expand(str)
  return vim.fn.expand(str)
end

local project_venv = expand('$PROJECT_VENV') .. '/bin/python3'

if vim.fn.executable(project_venv) == 1 then
  vim.g.python3_host_prog = project_venv
else
  local home_python = expand('$HOME/nvim-python/.venv/bin/python3')
  vim.g.python3_host_prog = home_python
end

-- lspconfig の有効化
require'lspconfig'.jedi_language_server.setup{}

pip

  • neovim を忘れずに
  • これは各プロジェクトの venv でのpipです
$ pip install jedi-language-server neovim

動作イメージ

Linter / Formatter

コンセプト

  • ruff で良いかなと
  • ただ、none-ls みたいなやつに、linter / formatter を統合するのにちょっと抵抗感があって、ale にしています

init.lua

-- ale
vim.g.ale_linters = {
    python = {'ruff'},
}

vim.g.ale_fixers = {
    python = {'ruff'},
}

vim.g.ale_fix_on_save = 1

pip

  • これは各プロジェクトの venv でのpipです
pip install ruff

動作イメージ

こんな感じで怒られる&保存するとfixされる

今回のinit.luaの全体

  • 今回紹介したプラグイン周りの全体像はこんな感じ。
  • 見ての通り最低限なので、ぼちぼち育てていきます。
vim.cmd('let $LANG = "en_US.UTF-8"')

vim.loader.enable()
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not vim.loop.fs_stat(lazypath) then
  vim.fn.system({
    "git",
    "clone",
    "--filter=blob:none",
    "https://github.com/folke/lazy.nvim.git",
    "--branch=stable", -- latest stable release
    lazypath,
  })
end
vim.opt.rtp:prepend(lazypath)

require("lazy").setup({
  "neovim/nvim-lspconfig",
  "hrsh7th/nvim-cmp",
  "hrsh7th/cmp-nvim-lsp",
  "hrsh7th/cmp-buffer",
  "hrsh7th/cmp-path",
  "saadparwaiz1/cmp_luasnip",
  "L3MON4D3/LuaSnip",
  "dense-analysis/ale",
})

-- lspのハンドラーに設定
capabilities = require("cmp_nvim_lsp").default_capabilities()

-- lspの設定後に追加
vim.opt.completeopt = "menu,menuone,noselect"

local cmp = require"cmp"
cmp.setup({
  snippet = {
    expand = function(args)
      require("luasnip").lsp_expand(args.body)
    end,
  },
  mapping = cmp.mapping.preset.insert({
    ["<C-p>"] = cmp.mapping.select_prev_item(),
    ["<C-n>"] = cmp.mapping.select_next_item(),
    ["<C-d>"] = cmp.mapping.scroll_docs(-4),
    ["<C-f>"] = cmp.mapping.scroll_docs(4),
    ["<C-Space>"] = cmp.mapping.complete(),
    ["<C-e>"] = cmp.mapping.close(),
    ["<CR>"] = cmp.mapping.confirm({ select = true }),
  }),
  sources = cmp.config.sources({
    { name = "nvim_lsp" },
    { name = "luasnip" },
  }, {
    { name = "buffer" },
  })
})

-- python の path の切り替え
local function expand(str)
  return vim.fn.expand(str)
end

local project_venv = expand('$PROJECT_VENV') .. '/bin/python3'

if vim.fn.executable(project_venv) == 1 then
  vim.g.python3_host_prog = project_venv
else
  local home_python = expand('$HOME/nvim-python/.venv/bin/python3')
  vim.g.python3_host_prog = home_python
end

-- lspconfig の有効化
require'lspconfig'.jedi_language_server.setup{}

-- ale
vim.g.ale_linters = {
    python = {'ruff'},
}

vim.g.ale_fixers = {
    python = {'ruff'},
}

vim.g.ale_fix_on_save = 1

参考にしました

zenn.dev

qiita.com

zenn.dev