1 điểm bởi GN⁺ 2026-05-13 | 2 bình luận | Chia sẻ qua WhatsApp
  • Tổng hợp trải nghiệm di chuyển thực tế khi thay thế hoàn toàn môi trường LSP dựa trên lsp-mode vốn đang hoạt động tốt bằng Eglot, giải pháp LSP tích hợp sẵn của GNU Emacs
  • So với lsp-mode, Eglot cung cấp giao diện tối giản và yên tĩnh hơn, với cấu trúc tích hợp cùng các gói ngoài như Corfu, Consult, Flycheck thông qua API Emacs Lisp tiêu chuẩn
  • Phần lớn công việc chuyển đổi không nằm ở cấu hình Eglot mà tốn vào việc tìm hiểu, cấu hình và thử sai với các gói phụ trợ
  • Tùy theo từng máy chủ LSP như pylsp, gopls mà cách cấu hình workspace khác nhau, và với cấu hình theo dự án thì cần dùng .dir-locals.el
  • Vì Eglot được tích hợp sẵn trong GNU Emacs, về lâu dài đây là lựa chọn đáng để người dùng Emacs cân nhắc chuyển sang

Động lực chuyển đổi và ấn tượng tổng thể

  • Không có lý do quá mạnh mẽ nào; sau khi chuyển sang Corfu, tác giả dùng thử Eglot rồi tiếp tục luôn
  • lsp-mode + lsp-ui tạo ra giao diện bận rộn (busy) với nhiều thông tin hiển thị đồng thời, nên tác giả chuyển sang để có trải nghiệm LSP yên tĩnh hơn
  • Eglot tối giản hơn lsp-mode, nhưng để có trải nghiệm đầy đủ thì vẫn cần bổ sung tính năng bằng các gói phụ
  • Kết quả nhìn chung là hài lòng, và trong chế độ Go và Python các tính năng như 'tự động hoàn thành theo common prefix' hoạt động tốt hơn
  • Dù vẫn có thể tinh chỉnh thêm cấu hình của lsp-ui, việc chuyển sang Eglot đã giải quyết mọi vấn đề cùng lúc

Tích hợp với các gói phụ trợ

  • Corfu hoạt động cho tự động hoàn thành với nguyên cấu hình cũ, không cần thiết lập riêng
  • Để có phần xem trước kiểu lsp-ui trong cross-reference, cần kết nối gói consult và đặt xref-show-xrefs-function thành consult-xref
  • Sau khi cân nhắc giữa FlycheckFlymake, tác giả chọn Flycheck
    • Flymake tích hợp với Eglot tốt hơn, nhưng nhìn chung tác giả vẫn thích Flycheck hơn
    • Vì Eglot tự động bật flymake-mode, cần thêm flymake vào eglot-stay-out-of để vô hiệu hóa
    • Do global mode của flycheck-eglot không hoạt động ổn định, tác giả dùng cách tự thiết lập hook

Thiết lập key binding

  • Eglot không cung cấp key binding mặc định, nên cần tự cấu hình
  • Ví dụ các binding đang dùng hiện tại:
    • 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 (binding chuột để vượt qua giới hạn tích hợp của flycheck-eglot)
  • Tác giả cố ý không gán eglot-format — trong Go đã dùng gofmt của go-mode
  • Khi đặt eglot-extend-to-xref thành t, sau khi nhảy tới mục bên ngoài có thể dùng M-? để tìm các chỗ sử dụng khác trong dự án

Tự động khởi chạy máy chủ LSP

  • Tài liệu chính thức của Eglot khuyến nghị khởi chạy thủ công, nhưng tác giả cấu hình để chỉ tự động khởi chạy với file cục bộ
  • Định nghĩa hàm eglot-ensure-local-only để kiểm tra file từ xa bằng file-remote-p, rồi mới gọi eglot-ensure
  • Hạn chế của eglot-ensure: khi một ngôn ngữ có nhiều máy chủ LSP (ví dụ Python có pylsp và ruff), nó chỉ tự chọn máy chủ mặc định; nếu muốn đổi thì phải dừng máy chủ hiện tại rồi gọi eglot thủ công
  • Muốn chạy đồng thời nhiều máy chủ LSP thì có thể dùng trình multiplex như rassumfrassum

Khả năng truy cập Code Actions

  • Dù máy chủ LSP cung cấp nhiều code actions, trong Eglot việc truy cập chúng vẫn chưa thật tiện (lsp-mode cũng tương tự)
  • Máy chủ LSP chỉ trả về code actions khi có yêu cầu và chúng phụ thuộc vào vị trí cụ thể
  • Eglot không có chức năng lọc trong danh sách code action dài do máy chủ gửi về, khiến danh sách trong cả Go và Python trở nên rối rắm

Cấu hình workspace cho pylsp

  • Với pylsp (Python LSP server), để vô hiệu hóa các linter dựa trên style trong chẩn đoán thường trực thì cần dùng eglot-workspace-configuration
  • lsp-mode cung cấp các điều khiển tiện lợi để tắt từng công cụ chẩn đoán riêng lẻ (như mccabe), còn trong Eglot thì phải tự viết workspace configuration theo định dạng JSON
  • Ví dụ: vô hiệu hóa mccabe, pylint, mypy, pycodestyle bằng :enabled :json-false
  • Các khóa liên quan đến mypy bị dùng lẫn giữa hai tên pylsp_mypymypy, đây là chi tiết trong phần triển khai nội bộ của pylsp
  • Bắt buộc phải dùng setq-default; setq sẽ không hoạt động

Cấu hình theo dự án và .dir-locals.el

  • Eglot không có cách tiện lợi để tạm thời đặt tham số máy chủ LSP theo từng dự án
  • Nếu cần cấu hình riêng, cách dễ nhất là viết đúng định dạng trong file .dir-locals.el
  • Với gopls (Go) và pylsp (Python), cấu trúc cấu hình hoàn toàn khác nhau, nên cần tự tìm hiểu riêng cho từng máy chủ LSP
  • Muốn thay đổi cấu hình khi đang chạy thì cần tự viết hàm chuyên dụng, trong đó định nghĩa class mới bằng dir-locals-set-class-variables, rồi gọi dir-locals-set-directory-classeglot-signal-didChangeConfiguration
  • Vì Eglot chạy một máy chủ LSP cho toàn bộ dự án (cây thư mục), nên không thể cấu hình LSP theo file hay buffer, mà bắt buộc phải áp dụng theo dự án
  • Nếu đặt eglot-workspace-configuration theo cách thông thường thì nó sẽ trở thành biến cục bộ theo buffer, nên hầu như vô dụng

Trải nghiệm Flymake vs Flycheck

  • Flymake tích hợp tốt hơn với Eglot, có thể hiển thị trực tiếp đề xuất sửa lỗi từ máy chủ LSP (quickfix code action) trong menu bật lên của nút chuột 2 trên ghi chú chẩn đoán
  • Flycheck chỉ đánh dấu lỗi, và có hạn chế là phải kích hoạt riêng các LSP code action
  • Ban đầu tác giả chuyển sang Flymake, nhưng vì Flycheck có những điểm tốt hơn nên giữ lại cấu hình cho cả hai bên
  • Tác giả của flycheck-eglot đã đề xuất một cách lách vấn đề này, nên tác giả quay lại với Flycheck
  • So với Flymake, Flycheck có bộ checker lớn hơn và chuyển đổi giữa các checker cũng dễ hơn
  • Tùy chọn 'hiển thị chẩn đoán ở cuối dòng' của Flymake là điểm đáng tiếc
    • flycheck-inline chỉ hiển thị cảnh báo tại vị trí hiện tại, và khi cuộn không thể hiện toàn bộ cảnh báo
    • Sideline + sideline-flycheck cũng có cùng hạn chế, nhưng trải nghiệm UI tốt hơn

2 bình luận

 
GN⁺ 2026-05-13
Ý kiến trên Lobste.rs
  • Tùy ngôn ngữ, lời khuyên tự động khởi động Eglot có thể cực kỳ tệ hại. Nhiều máy chủ LSP không an toàn để dùng với mã không đáng tin cậy, và chỉ cần mở file trong một dự án Rust hoặc Elixir do kẻ tấn công kiểm soát cũng có thể khiến máy bị xâm nhập
    Trừ khi đó là ngôn ngữ có máy chủ LSP được biết là an toàn, nên tránh tự động kích hoạt LSP. Nguồn: https://rust-analyzer.github.io/book/security.html

    • Nếu phải xem mã có thể mang tính thù địch, cuối cùng bạn nên làm toàn bộ công việc bên trong một ranh giới bảo mật thực sự. Ngay cả git status cũng có thể là bề mặt tấn công: https://github.com/justinsteven/advisories/…
      Khác biệt là ở ví dụ trên, bản thân kho chứa phải có exploit, còn với LSP thì vấn đề cũng có thể phát sinh từ phía dependency. Dù vậy, nếu đã quen bật LSP theo thói quen thì có vẻ khó tránh việc trở nên chai lì trước các cảnh báo
    • Một lý do khác để tránh tự động khởi động là yêu cầu tài nguyên của một số máy chủ ngôn ngữ rất lớn. Chỉ cần mở thoáng qua một file trong dự án Rust cỡ trung bình cũng có thể làm tiến trình rust-analyzer 4GB chạy hàng phút, và tạo ra thư mục target/debug/ hơn 1GB
    • Dù sao thì ngay khi chạy cargo build coi như cũng đã xong rồi. Tất nhiên, giữa tự động nạp LSP và lệnh do người dùng chủ động chạy vẫn có khác biệt lớn, nhưng trong sử dụng thực tế có khi khác biệt lại nhỏ hơn tưởng tượng
    • Nếu muốn tự động hóa, cách tốt hơn là hỏi trước khi kích hoạt như lsp-mode, rồi thêm dự án vào danh sách cho phép. Nếu đã có hook “chạy tự động” thì chỉ cần khoảng 10 dòng với read-from-minibuffer để hỏi trước “bạn có tin cậy thư mục này không?”, và dùng thứ như projectile để lấy thư mục gốc thì có thể đạt được phần lớn lợi ích bảo mật
      Cấu hình của tôi dùng danh sách cho phép của lsp-mode nhưng xóa nó sau mỗi phiên, để mỗi lần mở lại Emacs thì phải đồng ý lại theo từng dự án. Hình như ban đầu tôi làm vậy vì hiệu năng, và có lúc lsp-mode khởi chạy nhiều tiến trình trước cả khi mở một dự án cụ thể. Rủi ro bảo mật là có thật, nhưng để tạo ra một workflow ổn thì không quá khó
  • Điều khó chịu nhất ở Eglot là nó không phơi bày phần lớn lệnh dưới dạng hàm, mà định nghĩa chúng trên các giao diện Emacs như xref. Khi có cả backend xref của CIDER lẫn clojure-lsp như trong Clojure, tôi thường thích phía CIDER hơn vì nó biết trạng thái runtime thực của đoạn mã đã nạp
    Phân tích tĩnh của clojure-lsp đặc biệt dễ lệch đồng bộ, nhất là trong workflow REPL từ xa. Với lsp-mode, bạn vẫn có thể gọi trực tiếp các chức năng như đi tới định nghĩa dưới dạng lệnh mà vẫn tiếp tục dùng xref, nhưng trong Eglot thì việc loại riêng một backend xref cụ thể khá phiền. Một số lệnh khác có trong lsp-mode cũng thiếu ở Eglot, dù thực ra chúng có thể được cung cấp qua các điểm tích hợp Emacs tương tự như xref

  • Tôi đã thử lsp-mode một lần, nhưng quá nhiều popup và thông báo gây rối nên xóa ngay. Eglot cho trải nghiệm LSP yên tĩnh hơn nhiều
    Cứ bật đó, đến khi sẵn sàng thì dùng tính năng. Cách ~cks tiếp cận từ hướng ngược lại và nhắc đến nhiều mẹo cùng lựa chọn thay thế khá thú vị

    • Tôi đang dùng lsp-mode với khá nhiều tính năng đã tắt đi nên giao diện cũng khá yên tĩnh. Tôi từng định chuyển sang Eglot nhưng lúc đó có vẻ nó thiếu các tích hợp mà tôi muốn nên không thử thêm nữa
      Điều tôi thực sự muốn tìm là một máy chủ LSP có thể xử lý kho mã rất lớn. Đây thường là giới hạn lớn nhất, và tôi từng nghĩ có lẽ phải tự làm thứ gì đó lập chỉ mục mã nguồn gần như một lần rồi tái sử dụng cho nhiều thao tác kiểu LSP, nhưng lại lo mình đang định đun sôi thêm một đại dương nữa
  • Trong các client LSP cho Emacs, Eglot và lsp-mode là nổi tiếng nhất, nhưng cũng có các lựa chọn khác như lspcelsp-bridge
    Tôi đã dùng Eglot hài lòng nhiều năm, nhưng nó có một giới hạn thiết kế có thể sẽ thành vấn đề lớn hơn về sau. Nó giả định mỗi buffer chỉ có một client, điều này hợp lý vào thời điểm Eglot ra đời, nhưng giờ ngày càng thường gặp các trường hợp muốn chạy nhiều máy chủ LSP trong một buffer. [Khuyến nghị] hiện tại là dùng một chương trình riêng làm bộ ghép kênh LSP

  • Tôi đã chuyển từ lsp-mode sang Eglot cho Python cách đây 4 ngày và khá hài lòng
    Tôi đã công khai phiên bản tối thiểu của cấu hình hiện tại ở đây: https://discuss.afpy.org/t/configuration-emacs-minimale-en-2026/3001

    • Tôi đã chuyển từ elpy sang eglot + basedpyright gần một năm trước, và tôi cũng khá hài lòng
      Tuy vậy vẫn có chút bất tiện về hoàn thành. Ví dụ, khi nhấn foo<tab><tab>, đôi khi basedpyright lại tự import thứ gì đó kỳ quặc dù có symbol phù hợp ngay trong scope hiện tại, và tôi vẫn chưa tìm ra cách chỉ hoàn thành đến chuỗi chung dài nhất. Ngoài ra thì khá ổn
  • Tôi ghen tị với những người biến Emacs thành một IDE hiện đại. Tôi dùng phím tắt Emacs, nhưng dù bỏ ra 6–8 tiếng tôi vẫn không làm được để Emacs hoạt động như IDE
    Trước đây tôi từng cố thiết lập theo FB Flow mà mình dùng thay TypeScript trong môi trường phát triển Linux và FreeBSD rồi bỏ cuộc, và cuối tuần trước lại bỏ cuộc khi cố dựng một môi trường Python đầy đủ tính năng trên Windows kèm tree-sitter. Có quá nhiều thứ phải cấu hình, lại còn phải tải riêng quá nhiều DLL như parser tree-sitter, nên để biến nó thành một IDE tử tế thì cần quá nhiều thứ. Giờ tôi không còn muốn đầu tư thời gian nữa, nhưng thỉnh thoảng gõ emacs -nw ở bất kỳ terminal nào và có ngay môi trường soạn thảo quen thuộc vẫn rất thích

    • Nếu là Python thì chỉ cần cấu hình tối thiểu với fido-vertical-mode, which-key-mode, global-completion-preview-mode, yasnippet, eglot-ensure, basedpyright là đã đủ để bắt đầu
      Nếu basedpyright có trong PATH thì bạn vẫn có hoàn thành và tô sáng cú pháp tử tế mà không cần ngữ pháp tree-sitter. Đây là bản rút gọn tối thiểu từ cấu hình đầy đủ của tôi, còn cấu hình đầy đủ ở my full config
    • Có thể thử doom emacs. Việc cấu hình rất dễ và ngay ở trạng thái mặc định thì hầu hết mọi thứ đã hoạt động tốt. Nếu không thích evil-mode thì cũng có thể quay lại phím tắt Emacs