1P by GN⁺ | ★ favorite | 댓글 2개
  • 기존에 잘 작동하던 lsp-mode 기반 LSP 환경을 GNU Emacs 내장 LSP 솔루션인 Eglot으로 전면 교체한 실제 마이그레이션 경험 정리
  • Eglot은 lsp-mode에 비해 미니멀하고 조용한 인터페이스를 제공하며, Corfu·Consult·Flycheck 등 외부 패키지와 표준 Emacs Lisp API로 연동하는 구조
  • 전환 작업의 대부분은 Eglot 자체 설정이 아니라 보조 패키지 탐색·설정·시행착오에 소요
  • pylsp, gopls 등 LSP 서버별로 workspace 설정 방식이 상이하며, 프로젝트 단위 설정에는 .dir-locals.el 활용 필요
  • Eglot은 GNU Emacs에 내장된 만큼 장기적으로 Emacs 사용자라면 전환을 고려할 가치가 있음

전환 동기와 전체 인상

  • 특별히 강력한 이유 없이, Corfu 전환 이후 Eglot을 시험적으로 사용하다가 계속 진행한 경우
  • lsp-mode + lsp-ui는 다양한 정보가 동시에 표시되는 바쁜(busy) 인터페이스이며, 보다 조용한 LSP 경험을 원해 전환
  • Eglot은 lsp-mode보다 미니멀하지만, 추가 패키지로 기능을 보강해야 완전한 경험 가능
  • 결과적으로 만족스러우며, Go와 Python 모드에서 'common prefix로 자동완성' 같은 기능이 더 잘 동작
  • lsp-ui의 설정을 추가로 조정할 수도 있었지만, Eglot 전환이 모든 문제를 한 번에 해결

보조 패키지 연동

  • Corfu는 별도 설정 없이 기존 설정 그대로 자동완성에 동작
  • 크로스 레퍼런스에서 lsp-ui 스타일의 미리보기를 얻으려면 consult 패키지를 연결하고 xref-show-xrefs-functionconsult-xref로 설정 필요
  • FlycheckFlymake 사이에서 고민 끝에 Flycheck를 선택
    • Flymake는 Eglot과 더 잘 통합되지만, 전반적으로 Flycheck를 선호
    • Eglot이 자동으로 flymake-mode를 활성화하므로, eglot-stay-out-offlymake를 추가하여 비활성화
    • flycheck-eglot의 글로벌 모드가 안정적으로 동작하지 않아, hook을 직접 설정하는 방식 사용

키 바인딩 설정

  • Eglot은 기본 키 바인딩을 제공하지 않으므로 직접 설정 필요
  • 현재 사용 중인 바인딩 예시:
    • C-c reglot-rename, C-c oeglot-code-action-organize-imports
    • C-c heldoc, C-c aeglot-code-actions, C-c qeglot-code-action-quickfix
    • C-M-<mouse-2>eglot-code-actions-at-mouse (flycheck-eglot의 통합 한계를 우회하기 위한 마우스 바인딩)
  • eglot-format은 의도적으로 바인딩하지 않음 — Go에서는 이미 go-mode의 gofmt 사용 중
  • eglot-extend-to-xreft로 설정하면, 외부 항목으로 점프한 뒤 M-?로 프로젝트 내 다른 사용처 검색 가능

LSP 서버 자동 시작

  • Eglot 공식 문서는 수동 시작을 권장하지만, 로컬 파일에 한해 자동 시작하도록 설정
  • eglot-ensure-local-only 함수를 정의하여 file-remote-p로 원격 파일 여부를 확인 후 eglot-ensure 호출
  • eglot-ensure의 제한 사항: 한 언어에 여러 LSP 서버(예: Python의 pylsp와 ruff)가 있을 때 기본 서버만 자동 선택되며, 변경하려면 현재 서버를 종료하고 eglot을 수동 호출해야 함
  • 여러 LSP 서버를 동시에 실행하려면 rassumfrassum 같은 멀티플렉서 프로그램 활용 가능

Code Actions 접근성

  • LSP 서버가 제공하는 code actions가 많지만, Eglot에서 편리하게 접근하기 어려움 (lsp-mode도 마찬가지)
  • LSP 서버는 요청 시에만 code actions를 제공하며, 특정 위치에 종속적
  • Eglot은 서버가 보내는 긴 code action 목록에서 필터링 기능을 제공하지 않아, Go와 Python 모두에서 목록이 번잡해지는 문제

pylsp Workspace 설정

  • pylsp(Python LSP 서버)에서 스타일 기반 린터를 상시 진단에서 비활성화하려면 eglot-workspace-configuration 사용 필요
  • lsp-mode는 개별 진단 도구(mccabe 등)를 끄는 편리한 컨트롤을 제공하지만, Eglot에서는 JSON 형식의 workspace configuration을 직접 작성해야 함
  • 예시: mccabe, pylint, mypy, pycodestyle 등을 :enabled :json-false로 비활성화
  • mypy 관련 키가 pylsp_mypymypy 두 가지 이름으로 혼용되는데, 이는 pylsp 내부 구현의 세부 사항
  • 반드시 setq-default를 사용해야 하며, setq로는 동작하지 않음

프로젝트별 설정과 .dir-locals.el

  • 프로젝트별 LSP 서버 파라미터를 임시로 설정하는 편리한 방법이 Eglot에는 없음
  • 특정 설정이 필요하면 .dir-locals.el 파일에 올바른 형식으로 작성하는 것이 가장 쉬운 방법
  • gopls(Go)와 pylsp(Python)에서 설정 구조가 완전히 다르며, LSP 서버마다 개별 학습 필요
  • 설정을 런타임에 변경하려면 dir-locals-set-class-variables로 새 클래스를 정의한 뒤, dir-locals-set-directory-classeglot-signal-didChangeConfiguration을 호출하는 전용 함수 작성 필요
  • Eglot은 프로젝트(디렉터리 트리) 전체에 하나의 LSP 서버를 실행하므로, 파일·버퍼 단위의 LSP 설정은 불가능하며 반드시 프로젝트 단위로 적용해야 함
  • eglot-workspace-configuration을 일반 수단으로 설정하면 버퍼 로컬 변수가 되어 실질적으로 무용

Flymake vs Flycheck 경험

  • Flymake는 Eglot과 더 잘 통합되어, 진단 노트에 직접 LSP 서버 기반 수정 제안(quickfix code action)을 버튼 2 팝업 메뉴로 표시
  • Flycheck는 에러 마킹만 하며, LSP code actions를 별도로 트리거해야 하는 한계
  • 처음 Flymake로 전환했다가, Flycheck가 더 나은 부분이 있어 양쪽 설정을 모두 유지
  • flycheck-eglot 제작자가 해당 이슈에 대한 우회 방법을 제시하여 Flycheck로 복귀
  • Flycheck는 Flymake보다 체커 컬렉션이 더 크고 체커 간 전환이 더 용이
  • Flymake의 '줄 끝에 진단 표시' 옵션이 아쉬움
    • flycheck-inline은 현재 위치의 경고만 표시하고, 스크롤 시 전체 경고를 보여주지 않음
    • Sideline + sideline-flycheck는 같은 제한이 있지만 UI 경험은 더 나음

댓글과 토론

Lobste.rs 의견들
  • 언어에 따라서는 Eglot을 자동 시작하는 조언이 치명적으로 나쁠 수 있음. 많은 언어의 LSP 서버는 신뢰할 수 없는 코드에서 안전하게 쓸 수 없고, 공격자가 제어하는 Rust나 Elixir 프로젝트의 파일을 여는 것만으로도 머신이 침해될 수 있음
    안전하다고 알려진 LSP 서버가 있는 언어가 아니라면 LSP 자동 활성화는 피해야 함. 근거: https://rust-analyzer.github.io/book/security.html

    • 적대적일 수 있는 코드를 본다면 결국 전체 작업을 실제 보안 경계 안에서 해야 함. git status조차 공격 표면이 될 수 있음: https://github.com/justinsteven/advisories/…
      차이는 위 사례는 저장소 자체에 익스플로잇이 있어야 하지만, LSP는 의존성 쪽에서도 문제가 생길 수 있다는 점임. 그래도 습관적으로 LSP를 켜는 데 익숙해지면 경고에 둔감해지는 걸 막기 어려워 보임
    • 자동 시작을 피해야 하는 또 다른 이유는 일부 언어 서버의 자원 요구량이 크기 때문임. 중간 규모 Rust 프로젝트에서 파일을 잠깐 열기만 해도 4GB짜리 rust-analyzer 프로세스가 몇 분 동안 돌고, 1GB 넘는 target/debug/ 디렉터리가 생길 수 있음
    • 어차피 cargo build를 실행한 순간 이미 끝난 셈이긴 함. 물론 LSP 자동 로드와 사용자가 명시적으로 실행한 명령은 큰 차이가 있지만, 실제 사용에서는 생각보다 차이가 작을 수도 있음
    • 자동화를 원한다면 lsp-mode처럼 활성화 전에 묻고, 프로젝트를 허용 목록에 추가하는 방식이 낫다. 이미 “자동으로 실행”하는 훅이 있다면 read-from-minibuffer로 먼저 “이 폴더를 신뢰하나요?”를 묻게 만드는 건 10줄 정도면 가능하고, projectile 같은 것으로 기준 디렉터리를 잡으면 보안 이점을 대부분 얻을 수 있음
      내 설정은 lsp-mode의 허용 목록을 쓰되 세션마다 지워서, Emacs를 다시 켤 때마다 프로젝트별로 다시 동의해야 함. 원래는 성능 때문에 이렇게 했던 것 같고, lsp-mode가 특정 프로젝트를 열기도 전에 여러 프로세스를 띄우는 경우가 있었음. 보안 위험은 실제지만, 괜찮은 워크플로를 만드는 게 아주 어렵지는 않음
  • Eglot에서 가장 거슬리는 점은 대부분의 명령을 함수로 노출하지 않고, xref 같은 Emacs 인터페이스 위에 정의한다는 것임. Clojure처럼 CIDERclojure-lsp 양쪽 xref 백엔드가 있을 때는, 로드된 코드의 실제 런타임 상태를 아는 CIDER 쪽을 선호하게 됨
    clojure-lsp의 정적 분석은 특히 원격 REPL 워크플로에서 동기화가 어긋날 수 있음. lsp-mode에서는 정의로 이동 같은 기능을 명령으로 직접 호출하면서 xref도 계속 쓸 수 있지만, Eglot에서는 특정 xref 백엔드만 빼는 일이 꽤 번거롭다. lsp-mode에 있는 다른 명령들도 Eglot에는 빠져 있는데, 사실 xref와 비슷한 Emacs 통합 지점을 통해 제공 가능한 기능들임

  • lsp-mode를 한 번 써봤는데, 헷갈리는 팝업과 알림이 너무 많이 떠서 바로 지웠음. Eglot은 훨씬 조용한 LSP 경험을 줌
    켜두고 있다가 준비됐을 때 기능을 쓰면 됨. ~cks가 반대 방향에서 접근하면서 여러 팁과 대안을 언급하는 방식이 흥미로움

    • lsp-mode에서 많은 기능을 꺼서 꽤 조용한 인터페이스로 쓰고 있음. Eglot으로 갈아타려 했지만 원하는 통합 기능이 없어 보여서 그때는 더 시도하지 않았음
      정말 찾고 싶은 건 아주 큰 저장소를 감당할 수 있는 LSP 서버임. 이게 자주 한계로 다가왔고, 소스 색인을 한 번에 대부분 처리한 뒤 여러 LSP식 작업에 재사용하는 걸 만들어야 하나 싶지만, 또 다른 바다를 끓이려는 건 아닌지 걱정됨
  • Emacs용 LSP 클라이언트로 Eglot과 lsp-mode가 가장 유명하지만, lspcelsp-bridge 같은 대안도 있음
    Eglot을 몇 년 동안 만족스럽게 써왔지만, 앞으로 더 문제가 될 수 있는 설계상 한계가 있음. 버퍼당 클라이언트 하나를 가정하는데, Eglot이 만들어질 당시에는 타당했지만 이제는 한 버퍼에서 여러 LSP 서버를 돌리고 싶은 경우가 점점 흔해지고 있음. 현재 recommendation은 별도 프로그램을 LSP 멀티플렉서처럼 쓰는 것임

    • 그 용도로는 this가 있음
  • 4일 전에 Python용으로 lsp-mode에서 Eglot으로 옮겼고 만족 중임
    현재 설정의 최소 버전을 여기에 공개했음: https://discuss.afpy.org/t/configuration-emacs-minimale-en-2026/3001

    • 거의 1년 전에 elpy에서 eglot + basedpyright로 전환했고, 나도 꽤 만족하고 있음
      다만 완성 관련 불편은 있음. 예를 들어 foo<tab><tab>을 누르면 현재 스코프에 맞는 심볼이 있는데도 basedpyright가 이상한 것을 자동 임포트할 때가 있고, 가장 긴 공통 문자열까지만 완성하는 방법은 아직 못 찾았음. 그 외에는 꽤 좋음
  • Emacs를 현대적인 IDE처럼 쓰게 만드는 사람들이 부러움. Emacs 키 바인딩은 쓰지만, 6~8시간을 들여도 Emacs를 IDE처럼 작동하게 만들 수가 없었음
    예전에 Linux와 FreeBSD 개발 환경에서 TypeScript 대신 쓰던 FB Flow로 맞춰보려다 포기했고, 지난 주말에는 Windows에서 tree-sitter를 곁들인 풀기능 Python 환경을 만들려다 또 포기했음. 설정할 게 너무 많고, tree-sitter 파서 같은 DLL도 따로 너무 많이 받아야 해서, 제대로 된 IDE처럼 만들기까지 필요한 것이 과함. 이제는 시간을 투자하고 싶지 않지만, 가끔 아무 터미널에서 emacs -nw를 치면 익숙한 편집 환경이 뜨는 건 좋음

    • Python이라면 fido-vertical-mode, which-key-mode, global-completion-preview-mode, yasnippet, eglot-ensure, basedpyright 정도의 최소 설정으로 충분히 시작할 수 있음
      basedpyright가 경로에 잡혀 있으면 tree-sitter 문법 없이도 제대로 된 완성과 구문 강조가 됨. 전체 설정에서 최소한으로 줄인 버전이고, 전체 설정은 my full config에 있음
    • doom emacs를 써볼 만함. 설정이 매우 쉽고 기본 상태에서도 대부분 잘 작동함. evil-mode가 마음에 들지 않으면 Emacs 키 바인딩으로 되돌릴 수도 있음