Commit 47b30ed2ce00

Vincent Demeester <vincent@sbr.pm>
2026-01-23 11:50:05
feat: add LazyVim configuration with bépo keyboard support
- Add neovim config in dots/.config/nvim with LazyVim - Custom bépo keyboard mappings (ctsr -> hjkl) - Add neovim, unzip, luarocks to kyushu and aomi - Update dots/Makefile with nvim target Extras: Go, Python, Nix, JSON, YAML, Markdown, mini-files, mini-surround Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 10ab59a
dots/.config/nvim/lua/config/autocmds.lua
@@ -0,0 +1,70 @@
+-- Autocmds are automatically loaded on the VeryLazy event
+-- Default autocmds: https://github.com/LazyVim/LazyVim/blob/main/lua/lazyvim/config/autocmds.lua
+
+local autocmd = vim.api.nvim_create_autocmd
+local augroup = vim.api.nvim_create_augroup
+
+-- Highlight on yank
+autocmd("TextYankPost", {
+  group = augroup("highlight_yank", { clear = true }),
+  callback = function()
+    vim.highlight.on_yank({ higroup = "IncSearch", timeout = 200 })
+  end,
+})
+
+-- Resize splits when window is resized
+autocmd("VimResized", {
+  group = augroup("resize_splits", { clear = true }),
+  callback = function()
+    vim.cmd("tabdo wincmd =")
+  end,
+})
+
+-- Go to last location when opening a buffer
+autocmd("BufReadPost", {
+  group = augroup("last_loc", { clear = true }),
+  callback = function(event)
+    local exclude = { "gitcommit" }
+    local buf = event.buf
+    if vim.tbl_contains(exclude, vim.bo[buf].filetype) then
+      return
+    end
+    local mark = vim.api.nvim_buf_get_mark(buf, '"')
+    local lcount = vim.api.nvim_buf_line_count(buf)
+    if mark[1] > 0 and mark[1] <= lcount then
+      pcall(vim.api.nvim_win_set_cursor, 0, mark)
+    end
+  end,
+})
+
+-- Close some filetypes with <q>
+autocmd("FileType", {
+  group = augroup("close_with_q", { clear = true }),
+  pattern = {
+    "help",
+    "lspinfo",
+    "man",
+    "qf",
+    "query",
+    "spectre_panel",
+    "startuptime",
+    "notify",
+    "checkhealth",
+  },
+  callback = function(event)
+    vim.bo[event.buf].buflisted = false
+    vim.keymap.set("n", "q", "<cmd>close<cr>", { buffer = event.buf, silent = true })
+  end,
+})
+
+-- Auto create dir when saving a file
+autocmd("BufWritePre", {
+  group = augroup("auto_create_dir", { clear = true }),
+  callback = function(event)
+    if event.match:match("^%w%w+://") then
+      return
+    end
+    local file = vim.loop.fs_realpath(event.match) or event.match
+    vim.fn.mkdir(vim.fn.fnamemodify(file, ":p:h"), "p")
+  end,
+})
dots/.config/nvim/lua/config/keymaps.lua
@@ -0,0 +1,64 @@
+-- Keymaps are automatically loaded on the VeryLazy event
+-- Default keymaps: https://github.com/LazyVim/LazyVim/blob/main/lua/lazyvim/config/keymaps.lua
+
+local map = vim.keymap.set
+
+-- Quick escape and save
+map("i", "jj", "<ESC>:w<CR>", { noremap = true, desc = "ESC and Save" })
+map("i", "jk", "<ESC>", { noremap = true, desc = "ESC" })
+
+-- Clear search highlight
+map("n", "<Esc>", "<cmd>nohlsearch<CR>", { desc = "Clear search highlight" })
+
+-- Quick quit
+map("n", "<C-q>", "<cmd>qa<CR>", { desc = "Quit all" })
+
+-- Change inner word with Ctrl-c
+map("n", "<C-c>", "ciw", { noremap = true, desc = "Change inner word" })
+
+-- Replace word under cursor
+map("n", "<Leader>cr", [[:%s/\<<C-r><C-w>\>//g<Left><Left>]], { noremap = true, desc = "Replace word under cursor" })
+
+-- Redo with U (more intuitive)
+map("n", "U", "<cmd>redo<CR>", { noremap = true, desc = "Redo" })
+
+-- Move to start/end of line (swap 0 and ^)
+map("n", "0", "^", { noremap = true, desc = "Move to first non-blank" })
+map("n", "^", "0", { noremap = true, desc = "Move to start of line" })
+map("v", "0", "^", { noremap = true, desc = "Move to first non-blank" })
+map("v", "^", "0", { noremap = true, desc = "Move to start of line" })
+map("v", "$", "g_", { noremap = true, desc = "Move to last non-blank" })
+
+-- Copy line up/down
+map("n", "zj", 'mz"yyy"yP`z', { noremap = true, desc = "Copy line down" })
+map("n", "zk", 'mz"yyy"yP`zk', { noremap = true, desc = "Copy line up" })
+
+-- Select last paste
+map("n", "gp", "`[v`]", { noremap = true, desc = "Select last paste" })
+
+-- Put text before/after line
+map("n", "go", "<cmd>put<CR>", { noremap = true, desc = "Put text after line" })
+map("n", "gO", "<cmd>put!<CR>", { noremap = true, desc = "Put text before line" })
+
+-- Word navigation with Ctrl+Arrow
+map("n", "<C-Left>", "B", { noremap = true, desc = "Previous word" })
+map("n", "<C-Right>", "e", { noremap = true, desc = "End of word" })
+map("i", "<C-Left>", "<C-o>B", { noremap = true, desc = "Previous word" })
+map("i", "<C-Right>", "<C-o>W", { noremap = true, desc = "Next word" })
+
+-- Remove trailing whitespace
+map("n", "<Leader>cw", [[:%s/\s\+$//e<cr>]], { noremap = true, desc = "Remove trailing whitespace" })
+
+-- Surround with double quote in visual mode
+map("v", '"', 's"<C-r>""<Esc>', { noremap = true, silent = true, desc = "Surround with quotes" })
+
+-- CD to current file directory
+map("n", "<leader>C", "<cmd>cd %:h<cr>", { noremap = true, desc = "CD to current dir" })
+
+-- Mini.files (if installed)
+map("n", "<leader>e", function()
+  local ok, mini_files = pcall(require, "mini.files")
+  if ok then
+    mini_files.open(vim.api.nvim_buf_get_name(0), true)
+  end
+end, { desc = "Open mini.files", noremap = true })
dots/.config/nvim/lua/config/options.lua
@@ -0,0 +1,44 @@
+-- Options are automatically loaded before lazy.nvim startup
+-- Default options that are always set: https://github.com/LazyVim/LazyVim/blob/main/lua/lazyvim/config/options.lua
+
+local opt = vim.opt
+
+-- General
+opt.clipboard = "unnamedplus" -- Sync with system clipboard
+opt.confirm = true -- Confirm before closing unsaved buffer
+opt.mouse = "a" -- Enable mouse
+opt.undofile = true -- Persistent undo
+opt.undolevels = 10000
+
+-- UI
+opt.number = true
+opt.relativenumber = true
+opt.signcolumn = "yes"
+opt.termguicolors = true
+opt.wrap = false
+
+-- Indentation
+opt.expandtab = true
+opt.shiftwidth = 2
+opt.tabstop = 2
+opt.smartindent = true
+
+-- Search
+opt.ignorecase = true
+opt.smartcase = true
+opt.hlsearch = true
+opt.incsearch = true
+
+-- Splits
+opt.splitbelow = true
+opt.splitright = true
+
+-- Scrolling
+opt.scrolloff = 8
+opt.sidescrolloff = 8
+
+-- Completion
+opt.completeopt = "menu,menuone,noselect"
+
+-- Spelling (French)
+opt.spelllang = { "en", "fr" }
dots/.config/nvim/lua/plugins/bepo.lua
@@ -0,0 +1,63 @@
+-- Bépo keyboard layout support
+-- Remaps hjkl to ctsr (bépo home row) and related keys
+--
+-- Mappings applied:
+--   c → h (left)    |  h → t (find char)
+--   t → j (down)    |  j → r (replace)
+--   s → k (up)      |  k → s (substitute)
+--   r → l (right)   |  l → c (change)
+--
+
+local function apply_bepo_mappings()
+  local modes = { "n", "v", "o" } -- normal, visual, operator-pending
+
+  -- Movement: ctsr -> hjkl
+  for _, mode in ipairs(modes) do
+    vim.keymap.set(mode, "c", "h", { noremap = true, desc = "Left" })
+    vim.keymap.set(mode, "t", "j", { noremap = true, desc = "Down" })
+    vim.keymap.set(mode, "s", "k", { noremap = true, desc = "Up" })
+    vim.keymap.set(mode, "r", "l", { noremap = true, desc = "Right" })
+
+    -- Capitals
+    vim.keymap.set(mode, "C", "H", { noremap = true, desc = "Top of screen" })
+    vim.keymap.set(mode, "T", "J", { noremap = true, desc = "Join lines" })
+    vim.keymap.set(mode, "S", "K", { noremap = true, desc = "Keyword lookup" })
+    vim.keymap.set(mode, "R", "L", { noremap = true, desc = "Bottom of screen" })
+
+    -- Displaced keys: hjkl get the original ctsr functions
+    vim.keymap.set(mode, "h", "t", { noremap = true, desc = "Find char forward" })
+    vim.keymap.set(mode, "j", "r", { noremap = true, desc = "Replace char" })
+    vim.keymap.set(mode, "k", "s", { noremap = true, desc = "Substitute" })
+    vim.keymap.set(mode, "l", "c", { noremap = true, desc = "Change" })
+
+    -- Capitals for displaced
+    vim.keymap.set(mode, "H", "T", { noremap = true, desc = "Find char backward" })
+    vim.keymap.set(mode, "J", "R", { noremap = true, desc = "Replace mode" })
+    vim.keymap.set(mode, "K", "S", { noremap = true, desc = "Substitute line" })
+    vim.keymap.set(mode, "L", "C", { noremap = true, desc = "Change to EOL" })
+  end
+
+  -- Window navigation: w + ctsr
+  vim.keymap.set("n", "<C-w>c", "<C-w>h", { noremap = true, desc = "Window left" })
+  vim.keymap.set("n", "<C-w>t", "<C-w>j", { noremap = true, desc = "Window down" })
+  vim.keymap.set("n", "<C-w>s", "<C-w>k", { noremap = true, desc = "Window up" })
+  vim.keymap.set("n", "<C-w>r", "<C-w>l", { noremap = true, desc = "Window right" })
+
+  vim.notify("Bépo mappings applied", vim.log.levels.INFO)
+end
+
+return {
+  {
+    "bepo-mappings", -- Virtual plugin name
+    virtual = true,
+    lazy = false,
+    config = function()
+      -- Apply bépo mappings after everything else loads
+      vim.api.nvim_create_autocmd("VimEnter", {
+        callback = function()
+          vim.defer_fn(apply_bepo_mappings, 100)
+        end,
+      })
+    end,
+  },
+}
dots/.config/nvim/lua/plugins/extras.lua
@@ -0,0 +1,87 @@
+-- Extra plugins and customizations
+return {
+  -- Colorscheme
+  {
+    "folke/tokyonight.nvim",
+    lazy = false,
+    priority = 1000,
+    opts = {
+      style = "night",
+    },
+  },
+
+  -- vim-rsi: Readline style insertion
+  { "tpope/vim-rsi" },
+
+  -- Better whitespace handling
+  {
+    "ntpeters/vim-better-whitespace",
+    cmd = "StripWhitespace",
+    keys = {
+      { "<leader>S", "<cmd>StripWhitespace<cr>", desc = "Strip Whitespace" },
+    },
+  },
+
+  -- Mini.align for alignment
+  {
+    "nvim-mini/mini.align",
+    config = true,
+  },
+
+  -- Toggle terminal
+  {
+    "akinsho/toggleterm.nvim",
+    version = "*",
+    opts = {
+      size = 20,
+      open_mapping = [[<c-`>]],
+      direction = "float",
+      close_on_exit = true,
+    },
+  },
+
+  -- Markdown rendering
+  {
+    "MeanderingProgrammer/render-markdown.nvim",
+    dependencies = { "nvim-treesitter/nvim-treesitter" },
+    ft = { "markdown" },
+    config = true,
+  },
+
+  -- Debug print statements
+  {
+    "andrewferrier/debugprint.nvim",
+    config = true,
+    keys = {
+      { "g?p", mode = "n", desc = "Debug print below" },
+      { "g?P", mode = "n", desc = "Debug print above" },
+      { "g?v", mode = "n", desc = "Debug print variable below" },
+      { "g?V", mode = "n", desc = "Debug print variable above" },
+    },
+  },
+
+  -- Switch between values (yes/no, true/false, etc.)
+  {
+    "AndrewRadev/switch.vim",
+    config = function()
+      vim.g.switch_mapping = ""
+      vim.g["switch_custom_definitions"] = {
+        vim.fn["switch#NormalizedCase"]({ "yes", "no" }),
+        vim.fn["switch#NormalizedCase"]({ "on", "off" }),
+        vim.fn["switch#NormalizedCase"]({ "true", "false" }),
+        vim.fn["switch#NormalizedCase"]({ "enable", "disable" }),
+        vim.fn["switch#NormalizedCase"]({ "enabled", "disabled" }),
+        { "==", "!=" },
+      }
+    end,
+    keys = {
+      { "<leader>sx", "<cmd>Switch<cr>", desc = "Switch value" },
+    },
+  },
+
+  -- Highlight function arguments
+  {
+    "m-demare/hlargs.nvim",
+    config = true,
+  },
+}
dots/.config/nvim/init.lua
@@ -0,0 +1,62 @@
+-- LazyVim configuration
+-- Bootstrap lazy.nvim
+local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
+if not (vim.uv or vim.loop).fs_stat(lazypath) then
+  local lazyrepo = "https://github.com/folke/lazy.nvim.git"
+  local out = vim.fn.system({ "git", "clone", "--filter=blob:none", "--branch=stable", lazyrepo, lazypath })
+  if vim.v.shell_error ~= 0 then
+    vim.api.nvim_echo({
+      { "Failed to clone lazy.nvim:\n", "ErrorMsg" },
+      { out, "WarningMsg" },
+      { "\nPress any key to exit..." },
+    }, true, {})
+    vim.fn.getchar()
+    os.exit(1)
+  end
+end
+vim.opt.rtp:prepend(lazypath)
+
+-- Make sure to setup `mapleader` and `maplocalleader` before
+-- loading lazy.nvim so that mappings are correct.
+vim.g.mapleader = " "
+vim.g.maplocalleader = ","
+
+-- Setup lazy.nvim
+require("lazy").setup({
+  spec = {
+    -- Import LazyVim and its plugins
+    { "LazyVim/LazyVim", import = "lazyvim.plugins" },
+    -- Import extras
+    { import = "lazyvim.plugins.extras.lang.go" },
+    { import = "lazyvim.plugins.extras.lang.python" },
+    { import = "lazyvim.plugins.extras.lang.nix" },
+    { import = "lazyvim.plugins.extras.lang.json" },
+    { import = "lazyvim.plugins.extras.lang.yaml" },
+    { import = "lazyvim.plugins.extras.lang.toml" },
+    { import = "lazyvim.plugins.extras.lang.markdown" },
+    { import = "lazyvim.plugins.extras.editor.mini-files" },
+    { import = "lazyvim.plugins.extras.coding.mini-surround" },
+    -- Import custom plugins
+    { import = "plugins" },
+  },
+  defaults = {
+    lazy = false,
+    version = false,
+  },
+  install = { colorscheme = { "tokyonight", "habamax" } },
+  checker = {
+    enabled = true,
+    notify = false,
+  },
+  performance = {
+    rtp = {
+      disabled_plugins = {
+        "gzip",
+        "tarPlugin",
+        "tohtml",
+        "tutor",
+        "zipPlugin",
+      },
+    },
+  },
+})
dots/.config/nvim/lazy-lock.json
@@ -0,0 +1,48 @@
+{
+  "LazyVim": { "branch": "main", "commit": "28db03f958d58dfff3c647ce28fdc1cb88ac158d" },
+  "SchemaStore.nvim": { "branch": "main", "commit": "a0fad73139f98ea1d6e9d380374c16245e562f4f" },
+  "bepo.nvim": { "branch": "main", "commit": "4eda76f40c05071e4c4a441597849a35e87d4615" },
+  "blink.cmp": { "branch": "main", "commit": "b19413d214068f316c78978b08264ed1c41830ec" },
+  "bufferline.nvim": { "branch": "main", "commit": "655133c3b4c3e5e05ec549b9f8cc2894ac6f51b3" },
+  "catppuccin": { "branch": "main", "commit": "beaf41a30c26fd7d6c386d383155cbd65dd554cd" },
+  "conform.nvim": { "branch": "master", "commit": "c2526f1cde528a66e086ab1668e996d162c75f4f" },
+  "debugprint.nvim": { "branch": "main", "commit": "1f5de64ac5d0d0ca20a1a535e1ab2fe2c7d4359f" },
+  "flash.nvim": { "branch": "main", "commit": "fcea7ff883235d9024dc41e638f164a450c14ca2" },
+  "friendly-snippets": { "branch": "main", "commit": "572f5660cf05f8cd8834e096d7b4c921ba18e175" },
+  "gitsigns.nvim": { "branch": "main", "commit": "abf82a65f185bd54adc0679f74b7d6e1ada690c9" },
+  "grug-far.nvim": { "branch": "main", "commit": "1f7a722a9b9f0206a345377c13e62542f484398a" },
+  "hlargs.nvim": { "branch": "main", "commit": "0b29317c944fb1f76503ce4540d6dceffbb5ccd2" },
+  "lazy.nvim": { "branch": "main", "commit": "85c7ff3711b730b4030d03144f6db6375044ae82" },
+  "lazydev.nvim": { "branch": "main", "commit": "5231c62aa83c2f8dc8e7ba957aa77098cda1257d" },
+  "lualine.nvim": { "branch": "master", "commit": "47f91c416daef12db467145e16bed5bbfe00add8" },
+  "markdown-preview.nvim": { "branch": "master", "commit": "a923f5fc5ba36a3b17e289dc35dc17f66d0548ee" },
+  "mason-lspconfig.nvim": { "branch": "main", "commit": "4823a251e7578a835bb979c37df390fca692ba39" },
+  "mason.nvim": { "branch": "main", "commit": "44d1e90e1f66e077268191e3ee9d2ac97cc18e65" },
+  "mini.ai": { "branch": "main", "commit": "9eae720f2b20f6ad28cbfa0ddc524e10dc2c3201" },
+  "mini.align": { "branch": "main", "commit": "6d8a6ca3a0fd313938374abccb50cad026229db3" },
+  "mini.files": { "branch": "main", "commit": "fafacfecdd6c5a66bb10d173a749f3c098e84498" },
+  "mini.icons": { "branch": "main", "commit": "efc85e42262cd0c9e1fdbf806c25cb0be6de115c" },
+  "mini.pairs": { "branch": "main", "commit": "4089aa6ea6423e02e1a8326a7a7a00159f6f5e04" },
+  "mini.surround": { "branch": "main", "commit": "f9358544ed06bc10197e698b7fc8dc5963be3f4b" },
+  "noice.nvim": { "branch": "main", "commit": "7bfd942445fb63089b59f97ca487d605e715f155" },
+  "nui.nvim": { "branch": "main", "commit": "de740991c12411b663994b2860f1a4fd0937c130" },
+  "nvim-lint": { "branch": "master", "commit": "ca6ea12daf0a4d92dc24c5c9ae22a1f0418ade37" },
+  "nvim-lspconfig": { "branch": "master", "commit": "419b082102fa813739588dd82e19a8b6b2442855" },
+  "nvim-treesitter": { "branch": "main", "commit": "88f1dfc211c3a2fb47f1451fd5edc972ec697e58" },
+  "nvim-treesitter-textobjects": { "branch": "main", "commit": "4d55f63252e04c5212daed958e4e940915ff16ce" },
+  "nvim-ts-autotag": { "branch": "main", "commit": "db15f2e0df2f5db916e511e3fffb682ef2f6354f" },
+  "persistence.nvim": { "branch": "main", "commit": "b20b2a7887bd39c1a356980b45e03250f3dce49c" },
+  "plenary.nvim": { "branch": "master", "commit": "b9fd5226c2f76c951fc8ed5923d85e4de065e509" },
+  "render-markdown.nvim": { "branch": "main", "commit": "c54380dd4d8d1738b9691a7c349ecad7967ac12e" },
+  "snacks.nvim": { "branch": "main", "commit": "fe7cfe9800a182274d0f868a74b7263b8c0c020b" },
+  "switch.vim": { "branch": "main", "commit": "0fe38fd3fa6433101f4e173f681bb134e175c022" },
+  "todo-comments.nvim": { "branch": "main", "commit": "31e3c38ce9b29781e4422fc0322eb0a21f4e8668" },
+  "toggleterm.nvim": { "branch": "main", "commit": "50ea089fc548917cc3cc16b46a8211833b9e3c7c" },
+  "tokyonight.nvim": { "branch": "main", "commit": "5da1b76e64daf4c5d410f06bcb6b9cb640da7dfd" },
+  "trouble.nvim": { "branch": "main", "commit": "bd67efe408d4816e25e8491cc5ad4088e708a69a" },
+  "ts-comments.nvim": { "branch": "main", "commit": "123a9fb12e7229342f807ec9e6de478b1102b041" },
+  "venv-selector.nvim": { "branch": "main", "commit": "98c04c70d11007b84111c5fa94b6ab9f2745b934" },
+  "vim-better-whitespace": { "branch": "master", "commit": "de99b55a6fe8c96a69f9376f16b1d5d627a56e81" },
+  "vim-rsi": { "branch": "master", "commit": "45540637ead22f011e8215f1c90142e49d946a54" },
+  "which-key.nvim": { "branch": "main", "commit": "3aab2147e74890957785941f0c1ad87d0a44c15a" }
+}
dots/.config/nvim/lazyvim.json
@@ -0,0 +1,10 @@
+{
+  "extras": [
+
+  ],
+  "install_version": 8,
+  "news": {
+    "NEWS.md": "11866"
+  },
+  "version": 8
+}
\ No newline at end of file
dots/Makefile
@@ -22,6 +22,9 @@ niri : ~/.config/niri/config.kdl
 all += emacs
 emacs : ~/.config/emacs
 
+all += nvim
+nvim : ~/.config/nvim
+
 all += claude-skills
 claude-skills : ~/.config/claude/skills
 
systems/aomi/home.nix
@@ -18,6 +18,9 @@
     claude-hooks
 
     lazygit
+    neovim
+    unzip # for mason.nvim
+    luarocks # for lazy.nvim rocks support
   ];
 
   # Passage update service and timer
systems/kyushu/home.nix
@@ -76,6 +76,10 @@ in
     claude-hooks
     toggle-color-scheme
     shpool-remote
+
+    neovim
+    unzip # for mason.nvim
+    luarocks # for lazy.nvim rocks support
   ];
 
   # Automatic color scheme switching