completion 엔진이 왜 필요하냐면
native LSP는 데이터를 줘. "여기 쓸 수 있는 심볼들이고, 시그니처는 이렇다"까지야. 그 데이터를 쓸 만한 팝업으로 바꿔주는 게 completion 엔진이고. 후보에 순위를 매기고, 스니펫을 펼치고, buffer 단어랑 파일 경로를 같이 섞고, Tab이 어떻게 동작할지를 정해줘. 2026년 Neovim에는 좋은 선택지가 둘 있어.
- nvim-cmp — 오래된 기본값이야. 성숙했고, 생태계가 크고, source plugin도 많아.
- blink.cmp — 더 새로워 (2024년 이후). Rust 백엔드에 더 빠르고 단순하게 설계했고, 기능 차이도 빠르게 좁히고 있어. 시작 속도가 중요하면 써볼 만해.
이 레슨은 nvim-cmp로 갈게. 한 번 잡아두고 잊고 싶다면 이쪽이 더 안전하거든. 나중에 blink.cmp로 옮기는 것도 어렵지 않아. spec 모양이 비슷해.
뭘 연결해야 하냐면
- completion 엔진 본체.
- source — LSP, buffer 단어, 파일 경로, 스니펫.
- 스니펫을 펼쳐주는 엔진 (
LuaSnip). - 팝업 안에서 쓸 키 (Tab으로 이동, Enter로 확정, Esc로 닫기).
- 클라이언트가 뭘 지원하는지 LSP 서버에 알려주는 capabilities 객체. 스니펫 completion이나 자동 import 같은 걸 켜줘.
머릿속 그림
completion은 source마다 받은 후보를 정렬하고, 거르고, 순위 매겨 합친 결과야. nvim-cmp가 source마다 물어보고, 후보를 받고, 순위 규칙을 적용하고, 팝업을 띄워. source끼리는 서로 독립적이라 Markdown 파일에서는 buffer 단어를 끄고 Python 파일에서는 LSP를 위로 올리는 식으로 따로 조절할 수 있어.
LSP source를 먼저 믿어. buffer 단어 completion은 산문에는 좋지만 코드에서는 잡음이 많아. LSP가 주는 후보는 타입과 범위와 import를 알고 있거든. 소스 파일에서는 LSP를 buffer보다 위에 놓고, Markdown이나 commit message에서는 반대로 놔. nvim-cmp의 파일 종류별 설정이 이걸 담는 자리야.