Post

Neovim 환경 구축하기 - lazy.nvim, tokyonight 테마 설정

최근 다양한 CLI 및 텍스트 편집기를 활용하는데, 이 과정에서 생산성을 높이고 효율적인 환경을 갖추기 위해 Neovim 에디터에 관심을 갖게 되었습니다. Neovim은 Vim과 호환되는 동시에 Lua 기반 설정을 통한 확장성과 편의성을 제공합니다.

이번 포스팅에서는 Neovim을 간단히 소개하고, 플러그인 매니저인 lazy.nvim과 아름다운 테마인 tokyonight를 연계하여 깔끔한 개발 환경을 만드는 방법을 다뤄보겠습니다. 추가로, 위에서 제시한 설정 파일(init.lua)을 바탕으로 설정 방법을 간략히 정리하겠습니다.


1. Neovim이란?

Neovim은 Vim의 발전형 에디터로, Vimscript에 비해 더욱 현대적인 확장성과 성능을 제공합니다. 특히 Lua 언어를 통한 설정과 플러그인 관리, 그리고 내장 LSP 클라이언트 지원 등을 통해 IDE급 개발 환경을 쉽게 구축할 수 있습니다. 전통적인 Vim 사용자들에게는 익숙한 키 바인딩과 기능을 이어받으며도, 새로운 유저에게는 간결한 설정 파일 구조와 강력한 플러그인 생태계를 통해 강력한 편의성을 제공합니다.

1.1 Neovim의 특징

  • Lua 기반 설정: Vimscript 대신 Lua를 통한 설정으로, 더 직관적이고 유지보수가 쉬운 설정 파일 관리 가능
  • 내장 LSP 지원: 언어 서버 프로토콜(Language Server Protocol)을 통해 풍부한 코드 인텔리전스, 자동완성, 진단 지원 가능
  • 플러그인 생태계: Lazy.nvim, Packer, Treesitter 등 다양한 Lua 기반 플러그인 매니저와 도구 활용 용이
  • 성능 및 안정성 개선: Vim과 호환성을 유지하면서도 성능 최적화 및 비동기 기능 지원 강화

2. lazy.nvim이란?

플러그인 관리자는 Neovim 사용자에게 필수 요소 중 하나입니다. 기존에는 vim-plug, packer.nvim 등이 주류였지만, 최근에는 lazy.nvim이 빠르게 인기를 얻고 있습니다.

2.1 lazy.nvim의 특징

  • 속도 최적화: Neovim 시작 시 불필요한 플러그인 로드를 최소화하고, 이벤트나 조건에 따른 지연 로딩(laod)을 지원
  • Lua 기반 설정: Lua 테이블 형식으로 플러그인을 선언, 설정, 의존성 관리가 용이
  • 직관적인 설정 방식: require("lazy").setup({ ... }) 형태로 간단하게 플러그인 리스트와 설정을 모듈화

쉽게 말해, lazy.nvim을 사용하면 Neovim 실행 속도를 개선하고, 더 체계적으로 플러그인을 관리할 수 있습니다.


3. tokyonight 테마란?

tokyonight는 Neovim, VSCode, JetBrains IDE 등 다양한 에디터에서 사용할 수 있는 고급스럽고 차분한 다크 테마입니다. Neovim에서 tokyonight 테마를 사용하면 다음과 같은 이점을 얻을 수 있습니다.

  • 고대비 & 눈에 편한 색상 팔레트: 긴 시간의 개발에도 눈의 피로도를 줄여줌
  • 다양한 변형(theme style) 지원: night, storm, day, moon 등 다양한 스타일로 취향에 맞게 선택 가능
  • LSP, Treesitter, Statusline 등 다양한 플러그인과 호환: 에디터의 UI 요소를 테마와 자연스럽게 통일

tokyonight 테마는 단순히 색상만 변경하는 것이 아니라, 코드 가독성과 UI 일관성을 모두 챙겨주는 훌륭한 선택지입니다.


4. 준비물: 필수/권장 패키지

아래 패키지들이 있으면 본문 설정이 문제없이 동작합니다. 시스템 환경에 맞게 설치한 뒤 진행하세요.

4.1 필수 패키지

  • pynvim: Neovim Python3 provider(UltiSnips 연계)에 필요
  • git: lazy.nvim 부트스트랩, gitsigns 동작에 필요
  • ripgrep (rg): Telescope live_grep에 필요
  • 컴파일러/빌드 도구: Treesitter 컴파일용(gcc 또는 clang, make, unzip)
  • terraform: vim-terraform의 포맷(저장 시 fmt) 수행에 필요
  • tflint: nvim-lint로 Terraform 파일 저장 시 검사에 필요
  • terraform-ls: Terraform LSP(수동 설치 시). Mason으로 설치해도 무방

4.2 권장 패키지

  • fd: Telescope find_files 가속(없어도 동작, 있으면 더 빠름)
  • 클립보드 도구: clipboard=unnamedplus
    • X11: xclip 또는 xsel
    • Wayland: wl-clipboard
    • macOS: 기본 pbcopy/pbpaste로 OK
  • Nerd Font: lualine/아이콘 표시를 깔끔하게

4.3 설치 예시

1
2
3
4
5
6
7
8
9
10
11
# Ubuntu/Debian
sudo apt update
sudo apt install -y git ripgrep build-essential unzip xclip
# fd가 필요한 경우
sudo apt install -y fd-find   # 실행명은 fdfind

# macOS (Homebrew)
brew install git ripgrep fd terraform tflint

# Python provider(pynvim) — init.lua가 참조하는 python3에 설치
python3 -m pip install --user -U pynvim

4.4 설치 확인

  • Neovim에서 :checkhealth provider로 Python3 OK(pynvim) 확인
  • :checkhealth telescope에서 rg 인식 확인
  • :checkhealth nvim_treesitter로 컴파일 체인 확인
  • :Mason에서 terraform-ls, lua-language-server 상태 확인

5. 설정 예제 (init.lua)

아래는 제가 사용하고 있는 init.lua 설정 파일입니다. 이 설정에서는 lazy.nvim을 통해 tokyonight 테마와 nerdtree, lualine 등의 플러그인을 설치 및 관리합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
-- ~/.config/nvim/init.lua

-- 선택: init.lua 내에서만 vim 전역 경고 제거
-- ---@diagnostic disable: undefined-global

--------------------------------
-- 기본 옵션
--------------------------------
vim.g.mapleader = " "

vim.opt.number = true
vim.opt.autoindent = true
vim.opt.tabstop = 2
vim.opt.expandtab = true
vim.opt.shiftwidth = 2
vim.opt.smarttab = true
vim.opt.softtabstop = 2
vim.opt.termguicolors = true
vim.opt.mouse = "" -- 터미널 복사 충돌 방지
vim.opt.ignorecase = true
vim.opt.smartcase = true

-- UX 강화
vim.opt.clipboard     = "unnamedplus"
vim.opt.cursorline    = true
vim.opt.signcolumn    = "yes"
vim.opt.scrolloff     = 5
vim.opt.sidescrolloff = 8
vim.opt.splitbelow    = true
vim.opt.splitright    = true
vim.opt.updatetime    = 200
vim.opt.timeoutlen    = 400
vim.opt.undofile      = true
vim.opt.inccommand    = "split"
vim.opt.list          = true
vim.opt.listchars     = { tab = "▸ ", trail = "·", extends = ">", precedes = "<" }
vim.opt.shortmess:append("I") -- intro 숨김
vim.opt.wrap = false

-- Python3 provider (UltiSnips/cmp 대비)
if vim.fn.executable("python3") == 1 then
  vim.g.python3_host_prog = vim.fn.exepath("python3")
end

-- Git/cURL 트레이스 차단(부팅 시 불필요 로그 방지)
vim.env.GIT_TRACE = nil
vim.env.GIT_TRACE_CURL = nil
vim.env.GIT_CURL_VERBOSE = nil

--------------------------------
-- lazy.nvim 부트스트랩 (fs_stat deprecated 회피)
--------------------------------
local uv = vim.uv or vim.loop
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not (uv.fs_stat and uv.fs_stat(lazypath)) then
  if vim.fn.executable("git") == 1 then
    local cmd = {
      "git","-c","http.extraHeader=","-c","http.proxy=","-c","https.proxy=",
      "clone","--filter=blob:none","https://github.com/folke/lazy.nvim.git","--branch=stable",lazypath,
    }
    if vim.system then
      vim.system(cmd, { stdout = false, stderr = false }):wait()
    else
      vim.fn.system({ "sh","-c", table.concat(cmd, " ") .. " >/dev/null 2>&1" })
    end
  end
end
vim.opt.rtp:prepend(lazypath)

--------------------------------
-- 플러그인
--------------------------------
require("lazy").setup({
  -- Lua LS에 Neovim API 타입 주입
  { "folke/neodev.nvim", lazy = false, opts = {}, priority = 10000 },

  -- 테마
  {
    "folke/tokyonight.nvim",
    priority = 9999,
    lazy = false,
    config = function()
      ---@diagnostic disable-next-line: missing-fields
      require("tokyonight").setup({
        style = "storm",
        on_highlights = function(hl, _)
          hl.LineNr = { fg = "#00ff00" }
          hl.CursorLineNr = { fg = "#ff66ff", bold = true }
        end,
      })
      vim.cmd("colorscheme tokyonight")
    end,
  },

  -- 아이콘
  { "nvim-tree/nvim-web-devicons", lazy = true, opts = { default = true } },

  -- 상태줄
  {
    "nvim-lualine/lualine.nvim",
    event = "VeryLazy",
    dependencies = { "nvim-tree/nvim-web-devicons" },
    config = function()
      require("lualine").setup({ options = { theme = "tokyonight", icons_enabled = true } })
    end,
  },

  -- 파일 트리: 토글은 <C-n>, 나머지는 <leader> 조합
  {
    "nvim-tree/nvim-tree.lua",
    cmd = { "NvimTreeToggle", "NvimTreeFindFile", "NvimTreeRefresh", "NvimTreeFocus" },
    keys = {
      { "<C-n>",    "<cmd>NvimTreeToggle<CR>",   mode = "n", silent = true, desc = "NvimTree Toggle" },

      { "<leader>fe","<cmd>NvimTreeFindFile<CR>",mode = "n", silent = true, desc = "NvimTree Find Current File" },
      { "<leader>ne","<cmd>NvimTreeFocus<CR>",   mode = "n", silent = true, desc = "NvimTree Focus" },
      { "<leader>fr","<cmd>NvimTreeRefresh<CR>", mode = "n", silent = true, desc = "NvimTree Refresh" },

      -- 편집 프롬프트로 즉시 진입
      { "<leader>er", function() local api=require("nvim-tree.api"); api.tree.focus(); api.fs.rename() end,
        mode="n", silent=true, desc="Explorer: Rename" },
      { "<leader>en", function() local api=require("nvim-tree.api"); api.tree.focus(); api.fs.create() end,
        mode="n", silent=true, desc="Explorer: New (file/dir)" },
      { "<leader>ed", function() local api=require("nvim-tree.api"); api.tree.focus(); api.fs.remove() end,
        mode="n", silent=true, desc="Explorer: Delete" },
      { "<leader>em", function() local api=require("nvim-tree.api"); api.tree.focus(); api.fs.rename_sub() end,
        mode="n", silent=true, desc="Explorer: Move/Rename (path)" },
    },
    dependencies = { "nvim-tree/nvim-web-devicons" },
    config = function()
      require("nvim-tree").setup({
        view = { width = 36 },
        renderer = { group_empty = true },
        filters = { dotfiles = false },
        git = { enable = true },
      })
    end,
  },

  -- 주석: Ctrl+/
  {
    "numToStr/Comment.nvim",
    event = { "BufReadPost", "BufNewFile" },
    config = function()
      local api = require("Comment.api")
      require("Comment").setup()
      vim.keymap.set("n", "<C-_>", function() api.toggle.linewise.current() end, { noremap = true, silent = true, desc="Toggle Comment" })
      vim.keymap.set("x", "<C-_>", function() api.toggle.linewise(vim.fn.visualmode()) end, { noremap = true, silent = true, desc="Toggle Comment (v)" })
    end,
  },

  -- Treesitter (하이라이트/인덴트 + Textobjects 이동)
  {
    "nvim-treesitter/nvim-treesitter",
    build = ":TSUpdate",
    event = { "BufReadPost", "BufNewFile" },
    config = function()
      require("nvim-treesitter.configs").setup({
        ensure_installed = { "lua", "terraform", "hcl", "yaml", "json", "bash", "markdown" },
        highlight = { enable = true },
        indent = { enable = true },
        auto_install = false,
      })
    end,
  },
  {
    "nvim-treesitter/nvim-treesitter-textobjects",
    event = { "BufReadPost", "BufNewFile" },
    dependencies = { "nvim-treesitter/nvim-treesitter" },
    config = function()
      require("nvim-treesitter.configs").setup({
        textobjects = {
          move = {
            enable = true, set_jumps = true,
            goto_next_start = { ["]m"] = "@function.outer", ["]]"] = "@class.outer" },
            goto_previous_start = { ["[m"] = "@function.outer", ["[["] = "@class.outer" },
            goto_next_end = { ["]M"] = "@function.outer" },
            goto_previous_end = { ["[M"] = "@function.outer" },
          },
        },
      })
    end,
  },

  -- Terraform
  {
    "hashivim/vim-terraform",
    ft = { "terraform", "tf", "hcl" },
    config = function()
      vim.g.terraform_fmt_on_save = 1
      vim.g.terraform_align = 1
    end,
  },

  -- Mason
  { "williamboman/mason.nvim", build = ":MasonUpdate", config = true },

  -- Mason LSP bridge
  {
    "williamboman/mason-lspconfig.nvim",
    dependencies = { "williamboman/mason.nvim" },
    config = function()
      require("mason-lspconfig").setup({
        ensure_installed = { "terraformls", "lua_ls" },
        automatic_installation = false,
      })
    end,
  },

  -- LSP 설정
  {
    "neovim/nvim-lspconfig",
    event = { "BufReadPre", "BufNewFile" },
    dependencies = { "folke/neodev.nvim" },
    config = function()
      vim.diagnostic.config({
        virtual_text = { spacing = 2, prefix = "●" },
        float = { border = "rounded" },
        severity_sort = true,
      })

      local capabilities = vim.lsp.protocol.make_client_capabilities()
      local ok_cmp, cmp_nvim_lsp = pcall(require, "cmp_nvim_lsp")
      if ok_cmp then
        capabilities = cmp_nvim_lsp.default_capabilities(capabilities)
      end

      local on_attach = function(_, bufnr)
        local buf = function(mode, lhs, rhs, desc)
          vim.keymap.set(mode, lhs, rhs, { noremap = true, silent = true, buffer = bufnr, desc = desc })
        end
        buf("n", "gd", vim.lsp.buf.definition,        "Go to definition")
        buf("n", "gr", vim.lsp.buf.references,        "References")
        buf("n", "gi", vim.lsp.buf.implementation,    "Implementation")
        buf("n", "K",  vim.lsp.buf.hover,             "Hover")
        buf("n", "<leader>rn", vim.lsp.buf.rename,    "Rename symbol")  -- LSP rename
        buf("n", "<leader>ca", vim.lsp.buf.code_action,"Code action")
        buf("n", "[d", vim.diagnostic.goto_prev,      "Prev diagnostic")
        buf("n", "]d", vim.diagnostic.goto_next,      "Next diagnostic")
        -- 포맷 (서버가 지원할 때만)
        if vim.lsp.buf.format then
          buf("n", "<leader>cf", function() vim.lsp.buf.format({ async = true }) end, "Format buffer")
        end
      end

      local lsp = require("lspconfig")
      if vim.fn.executable("terraform-ls") == 1 then
        lsp.terraformls.setup({ on_attach = on_attach, capabilities = capabilities })
      end
      require("neodev").setup({})
      lsp.lua_ls.setup({
        on_attach = on_attach,
        capabilities = capabilities,
        settings = {
          Lua = {
            runtime = { version = "LuaJIT" },
            diagnostics = { globals = { "vim" } },
            workspace = { checkThirdParty = false, library = vim.api.nvim_get_runtime_file("", true) },
            telemetry = { enable = false },
          },
        },
      })
    end,
  },

  -- TFLint
  {
    "mfussenegger/nvim-lint",
    ft = { "terraform", "tf", "hcl" },
    config = function()
      local lint = require("lint")
      lint.linters_by_ft = { terraform = { "tflint" }, tf = { "tflint" }, hcl = { "tflint" } }
      vim.api.nvim_create_autocmd("BufWritePost", {
        callback = function() require("lint").try_lint() end,
      })
    end,
  },

  -- 스니펫
  { "SirVer/ultisnips", event = "InsertEnter" },
  { "honza/vim-snippets", event = "InsertEnter" },

  -- 자동 완성 (cmp + UltiSnips + autopairs 연동)
  {
    "hrsh7th/nvim-cmp",
    event = "InsertEnter",
    dependencies = {
      "quangnguyen30192/cmp-nvim-ultisnips",
      "hrsh7th/cmp-buffer",
      "hrsh7th/cmp-path",
      "hrsh7th/cmp-nvim-lsp",
    },
    config = function()
      local cmp = require("cmp")
      cmp.setup({
        snippet = { expand = function(args) vim.fn["UltiSnips#Anon"](args.body) end },
        mapping = cmp.mapping.preset.insert({
          ["<C-n>"] = cmp.mapping.select_next_item({ behavior = cmp.SelectBehavior.Insert }),
          ["<C-p>"] = cmp.mapping.select_prev_item({ behavior = cmp.SelectBehavior.Insert }),
          ["<CR>"]  = cmp.mapping.confirm({ select = false }),
          ["<Tab>"] = cmp.mapping(function(fallback)
            if cmp.visible() then
              cmp.select_next_item()
            elseif vim.fn["UltiSnips#CanJumpForwards"]() == 1 then
              vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes("<Plug>(ultisnips_jump_forward)", true, true, true), "m", true)
            else
              fallback()
            end
          end, { "i", "s" }),
          ["<S-Tab>"] = cmp.mapping(function(fallback)
            if cmp.visible() then
              cmp.select_prev_item()
            elseif vim.fn["UltiSnips#CanJumpBackwards"]() == 1 then
              vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes("<Plug>(ultisnips_jump_backward)", true, true, true), "m", true)
            else
              fallback()
            end
          end, { "i", "s" }),
        }),
        sources = {
          { name = "ultisnips" },
          { name = "nvim_lsp" },
          { name = "buffer" },
          { name = "path" },
        },
        experimental = { ghost_text = false },
      })
    end,
  },

  -- 자동 괄호 (cmp confirm 연동)
  {
    "windwp/nvim-autopairs",
    event = "InsertEnter",
    config = function()
      require("nvim-autopairs").setup({})
      local ok, cmp = pcall(require, "cmp")
      if ok then
        local cmp_autopairs = require("nvim-autopairs.completion.cmp")
        cmp.event:on("confirm_done", cmp_autopairs.on_confirm_done())
      end
    end,
  },

  -- Telescope + 파일 브라우저 확장(디렉토리 네비 강화)
  {
    "nvim-telescope/telescope.nvim",
    branch = "0.1.x",
    dependencies = { "nvim-lua/plenary.nvim" },
    cmd = "Telescope",
    keys = {
      { "<leader>ff", "<cmd>Telescope find_files<CR>", mode="n", silent=true, desc="Find files" },
      { "<leader>fg", "<cmd>Telescope live_grep<CR>",  mode="n", silent=true, desc="Live grep" },
      { "<leader>fb", "<cmd>Telescope buffers<CR>",    mode="n", silent=true, desc="Buffers" },
      { "<leader>fh", "<cmd>Telescope help_tags<CR>",  mode="n", silent=true, desc="Help tags" },
    },
    config = function()
      require("telescope").setup({
        defaults = {
          layout_strategy = "horizontal",
          sorting_strategy = "ascending",
          layout_config = { prompt_position = "top" },
        },
      })
    end,
  },
  {
    "nvim-telescope/telescope-file-browser.nvim",
    dependencies = { "nvim-telescope/telescope.nvim" },
    keys = {
      -- 현재 버퍼 디렉토리 기준 브라우저
      { "<leader>fB", function()
          require("telescope").extensions.file_browser.file_browser({
            path = "%:p:h", select_buffer = true, respect_gitignore = false, hidden = true,
          })
        end, mode="n", silent=true, desc="File Browser (buffer dir)" },
    },
    config = function()
      require("telescope").load_extension("file_browser")
    end,
  },

  -- Git 변경 표시
  {
    "lewis6991/gitsigns.nvim",
    event = { "BufReadPost", "BufNewFile" },
    config = function()
      require("gitsigns").setup({
        current_line_blame = false, -- 부팅시 Git 호출 억제
        current_line_blame_opts = { delay = 500 },
      })
      local gs = package.loaded.gitsigns
      vim.keymap.set("n", "]h", gs.next_hunk, { silent = true, desc = "Next hunk" })
      vim.keymap.set("n", "[h", gs.prev_hunk, { silent = true, desc = "Prev hunk" })
      vim.keymap.set("n", "<leader>hp", gs.preview_hunk, { silent = true, desc = "Preview hunk" })
      vim.keymap.set("n", "<leader>hs", gs.stage_hunk,   { silent = true, desc = "Stage hunk" })
      vim.keymap.set("n", "<leader>hu", gs.undo_stage_hunk, { silent = true, desc = "Undo stage hunk" })
    end,
  },

  -- 진단 패널
  {
    "folke/trouble.nvim",
    cmd = { "Trouble", "TroubleToggle" },
    dependencies = { "nvim-tree/nvim-web-devicons" },
    keys = {
      { "<leader>xx", "<cmd>Trouble diagnostics toggle<CR>", mode="n", silent=true, desc="Diagnostics" },
      { "<leader>xq", "<cmd>Trouble qflist toggle<CR>",      mode="n", silent=true, desc="Quickfix" },
    },
    opts = {},
  },

  -- 세션 복원
  {
    "folke/persistence.nvim",
    event = "BufReadPre",
    config = true,
    keys = {
      { "<leader>ss", function() require("persistence").load() end,                mode="n", silent=true, desc="Session restore" },
      { "<leader>sl", function() require("persistence").load({ last = true }) end, mode="n", silent=true, desc="Session last" },
      { "<leader>sd", function() require("persistence").stop() end,                mode="n", silent=true, desc="Session stop" },
    },
  },
}, {
  checker = { enabled = false },      -- 부팅시 업데이트 체크 비활성화
  change_detection = { enabled = false },
})

--------------------------------
-- 자동명령
--------------------------------
-- Yank 하이라이트
vim.api.nvim_create_autocmd("TextYankPost", {
  callback = function() vim.highlight.on_yank() end,
})

-- 외부 변경 자동 반영
vim.api.nvim_create_autocmd({ "FocusGained", "BufEnter", "CursorHold" }, {
  callback = function() if vim.fn.getcmdwintype() == "" then vim.cmd("checktime") end end,
})

-- 마지막 커서 위치 복귀
vim.api.nvim_create_autocmd("BufReadPost", {
  callback = function()
    local mark = vim.api.nvim_buf_get_mark(0, '"')
    local lcount = vim.api.nvim_buf_line_count(0)
    if mark[1] > 0 and mark[1] <= lcount then pcall(vim.api.nvim_win_set_cursor, 0, mark) end
  end,
})

-- 저장 시 트레일링 공백 제거(마크다운/커밋 제외)
vim.api.nvim_create_autocmd("BufWritePre", {
  pattern = "*",
  callback = function()
    local ft = vim.bo.filetype
    if ft ~= "markdown" and ft ~= "gitcommit" then
      local view = vim.fn.winsaveview()
      vim.cmd([[%s/\s\+$//e]])
      vim.fn.winrestview(view)
    end
  end,
})

-- 시작 직후 메시지라인 정리
vim.schedule(function() vim.cmd("echo ''") vim.cmd("redraw!") end)

--------------------------------
-- 전역 키매핑
--------------------------------
local map = vim.keymap.set
local opts = { noremap = true, silent = true }

-- 창 이동
map("n", "<C-h>", "<C-w>h", opts)
map("n", "<C-j>", "<C-w>j", opts)
map("n", "<C-k>", "<C-w>k", opts)
map("n", "<C-l>", "<C-w>l", opts)

-- 편의
map("n", "<leader>w", "<cmd>write<CR>", opts)
map("n", "<leader>q", "<cmd>quit<CR>",  opts)
map("n", "<leader>Q", "<cmd>qa!<CR>",   opts)
map("n", "<leader>h", "<cmd>nohlsearch<CR>", opts)

-- UI 토글(충돌 회피: LSP Rename은 <leader>rn, 여기서는 <leader>un)
map("n", "<leader>un", function() vim.wo.relativenumber = not vim.wo.relativenumber end, opts)

-- 창 크기 조절
map("n", "<C-Up>",    "<cmd>resize +2<CR>", opts)
map("n", "<C-Down>",  "<cmd>resize -2<CR>", opts)
map("n", "<C-Left>",  "<cmd>vertical resize -3<CR>", opts)
map("n", "<C-Right>", "<cmd>vertical resize +3<CR>", opts)

-- 라인/블록 이동
map("n", "<A-j>", ":m .+1<CR>==", opts)
map("n", "<A-k>", ":m .-1<CR>==", opts)
map("i", "<A-j>", "<Esc>:m .+1<CR>==gi", opts)
map("i", "<A-k>", "<Esc>:m .-1<CR>==gi", opts)
map("v", "<A-j>", ":m '>+1<CR>gv=gv", opts)
map("v", "<A-k>", ":m '<-2<CR>gv=gv", opts)

5.1 적용 방법

  1. Neovim 설치
    • Ubuntu: sudo apt install neovim
    • macOS(Homebrew): brew install neovim
      Neovim 버전은 0.8 이상을 권장합니다.
  2. 환경 디렉토리 생성
    1
    
    mkdir -p ~/.config/nvim
    

    이후 위의 init.lua 파일을 ~/.config/nvim/init.lua 경로에 저장합니다.

  3. Neovim 실행
    1
    
    nvim
    

    첫 실행 시 init.lua 내 스크립트가 동작하며, lazy.nvim이 없으면 자동으로 clone하여 설치합니다. 이후 지정한 플러그인들이 자동 설치됩니다.

  4. 테마 & 플러그인 동작 확인
    tokyonight 테마가 적용된 UI를 확인하고, Ctrl + t로 Neo-tree를 열어보거나 상태라인(lualine)의 아이콘, 색상이 잘 나오는지 테스트합니다.

궁금하신 점이나 추가해야 할 부분은 댓글이나 아래의 링크를 통해 문의해주세요.
Written with KKamJi

This post is licensed under CC BY 4.0 by the author.