ย้ายจาก lsp-mode ไปใช้ Eglot ใน GNU Emacs
(utcc.utoronto.ca)- สรุปประสบการณ์ย้ายจาก สภาพแวดล้อม LSP ที่ใช้ lsp-mode ซึ่งเดิมทำงานได้ดี ไปแทนที่ทั้งหมดด้วย Eglot ซึ่งเป็นโซลูชัน LSP ที่มาพร้อมกับ GNU Emacs
- เมื่อเทียบกับ lsp-mode แล้ว Eglot ให้ อินเทอร์เฟซที่มินิมอลและเงียบกว่า และมีโครงสร้างที่เชื่อมต่อกับแพ็กเกจภายนอกอย่าง Corfu, Consult, Flycheck ผ่านมาตรฐาน Emacs Lisp API
- งานย้ายส่วนใหญ่ไม่ได้อยู่ที่การตั้งค่า Eglot เอง แต่กินเวลาไปกับ การค้นหาแพ็กเกจเสริม การตั้งค่า และการลองผิดลองถูก
- สำหรับ LSP server แต่ละตัวอย่าง pylsp, gopls เป็นต้น วิธีตั้งค่า 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 ใน cross-reference ต้องเชื่อมต่อแพ็กเกจ consult และตั้งค่า
xref-show-xrefs-functionเป็นconsult-xref - หลังจากชั่งใจระหว่าง Flycheck กับ Flymake สุดท้ายเลือก Flycheck
- Flymake ผสานกับ Eglot ได้ดีกว่า แต่โดยรวมแล้วยังชอบ Flycheck มากกว่า
- เนื่องจาก Eglot จะเปิดใช้ flymake-mode อัตโนมัติ จึงเพิ่ม
flymakeในeglot-stay-out-ofเพื่อปิดมัน - global mode ของ flycheck-eglot ทำงานไม่เสถียร จึงใช้วิธีตั้งค่า hook เองโดยตรง
การตั้งค่า key binding
- Eglot ไม่มี key binding พื้นฐานมาให้ จึงต้องตั้งค่าเอง
- ตัวอย่าง binding ที่ใช้อยู่ตอนนี้:
C-c r→eglot-rename,C-c o→eglot-code-action-organize-importsC-c h→eldoc,C-c a→eglot-code-actions,C-c q→eglot-code-action-quickfixC-M-<mouse-2>→eglot-code-actions-at-mouse(เป็น mouse binding เพื่อ เลี่ยงข้อจำกัดด้านการผสานรวม ของ flycheck-eglot)
- ตั้งใจไม่ bind
eglot-format— เพราะใน Go ใช้ gofmt ของ go-mode อยู่แล้ว - หากตั้ง
eglot-extend-to-xrefเป็นtจะสามารถกระโดดไปยังรายการภายนอก แล้วใช้ M-? เพื่อค้นหาการใช้งานอื่นภายในโปรเจ็กต์ ได้
การเริ่มต้น LSP server อัตโนมัติ
- เอกสารทางการของ Eglot แนะนำให้เริ่มต้นแบบแมนนวล แต่มีการตั้งค่าให้ เริ่มอัตโนมัติเฉพาะไฟล์ในเครื่อง
- นิยามฟังก์ชัน
eglot-ensure-local-onlyเพื่อตรวจว่าเป็นไฟล์รีโมตหรือไม่ด้วยfile-remote-pแล้วค่อยเรียกeglot-ensure - ข้อจำกัดของ
eglot-ensureคือเมื่อภาษาหนึ่งมี หลาย LSP server (เช่น Python ที่มีทั้ง pylsp และ ruff) ระบบจะเลือกเฉพาะ server เริ่มต้นโดยอัตโนมัติ และถ้าจะเปลี่ยนต้องปิด server ปัจจุบันก่อนแล้วเรียกeglotแบบแมนนวล - หากต้องการรันหลาย LSP server พร้อมกัน สามารถใช้โปรแกรมมัลติเพล็กเซอร์อย่าง rassumfrassum ได้
การเข้าถึง Code Actions
- แม้ LSP server จะมี code actions ให้จำนวนมาก แต่ใน Eglot เข้าถึงได้ไม่ค่อยสะดวก (ซึ่ง lsp-mode ก็คล้ายกัน)
- LSP server จะส่ง code actions มาให้เฉพาะตอนมีการร้องขอ และขึ้นอยู่กับตำแหน่งเฉพาะ
- Eglot ไม่มีฟังก์ชันกรองรายการ code action ยาว ๆ ที่ server ส่งกลับมา ทำให้ทั้งใน Go และ Python รายการดูรก
การตั้งค่า Workspace ของ pylsp
- หากต้องการปิด linter แบบอิงสไตล์จากการวินิจฉัยปกติใน pylsp (Python LSP server) จำเป็นต้องใช้ eglot-workspace-configuration
- lsp-mode มีตัวควบคุมที่สะดวกสำหรับปิดเครื่องมือวินิจฉัยรายตัวอย่าง mccabe เป็นต้น แต่ใน Eglot ต้อง เขียน workspace configuration รูปแบบ JSON เอง
- ตัวอย่างเช่น ปิด mccabe, pylint, mypy, pycodestyle โดยตั้งเป็น
:enabled :json-false - คีย์ที่เกี่ยวกับ mypy มีการใช้ปะปนกันระหว่างชื่อ
pylsp_mypyกับmypyสองแบบ ซึ่งเป็นรายละเอียดภายในของการทำงาน pylsp - จำเป็นต้องใช้
setq-defaultเท่านั้น และ ใช้setqจะไม่ทำงาน
การตั้งค่ารายโปรเจ็กต์และ .dir-locals.el
- Eglot ไม่มีวิธีที่สะดวก สำหรับตั้งค่าพารามิเตอร์ของ LSP server เป็นการชั่วคราวในระดับโปรเจ็กต์
- หากต้องใช้การตั้งค่าเฉพาะ วิธีที่ง่ายที่สุดคือเขียนลงในไฟล์
.dir-locals.elด้วยรูปแบบที่ถูกต้อง - ใน gopls (Go) และ pylsp (Python) โครงสร้างการตั้งค่าแตกต่างกันโดยสิ้นเชิง จึงต้องเรียนรู้แยกตาม LSP server แต่ละตัว
- หากต้องการเปลี่ยนค่าตั้งระหว่างรันไทม์ จำเป็นต้องเขียน ฟังก์ชันเฉพาะ ที่นิยาม class ใหม่ด้วย
dir-locals-set-class-variablesแล้วเรียกdir-locals-set-directory-classและeglot-signal-didChangeConfiguration - Eglot รัน LSP server หนึ่งตัวต่อหนึ่งโปรเจ็กต์ (ทั้ง directory tree) ดังนั้น ไม่สามารถตั้งค่า LSP แยกเป็นรายไฟล์หรือรายบัฟเฟอร์ได้ และต้องใช้ในระดับโปรเจ็กต์เท่านั้น
- หากตั้ง
eglot-workspace-configurationด้วยวิธีทั่วไป มันจะกลายเป็น ตัวแปรเฉพาะบัฟเฟอร์ และแทบไม่มีประโยชน์ในการใช้งานจริง
ประสบการณ์ Flymake vs Flycheck
- Flymake ผสานกับ Eglot ได้ดีกว่า โดยสามารถแสดง ข้อเสนอการแก้ไขจาก LSP server (quickfix code action) ในเมนูป๊อปอัปของปุ่ม 2 บนโน้ตการวินิจฉัยได้โดยตรง
- Flycheck ทำได้เพียงมาร์กจุดผิดพลาด และมีข้อจำกัดตรงที่ต้องเรียก LSP code actions แยกต่างหาก
- ตอนแรกเปลี่ยนไปใช้ Flymake แต่ภายหลังพบว่า Flycheck มีบางด้านที่ดีกว่า จึง เก็บการตั้งค่าทั้งสองฝั่งไว้
- ผู้สร้าง flycheck-eglot ได้เสนอ วิธีเลี่ยงปัญหา สำหรับประเด็นนี้ จึงกลับมาใช้ Flycheck อีกครั้ง
- Flycheck มี ชุด checker มากกว่า Flymake และสลับระหว่าง checker ได้ง่ายกว่า
- น่าเสียดายกับตัวเลือก 'แสดงผลวินิจฉัยที่ท้ายบรรทัด' ของ Flymake
- flycheck-inline แสดงเฉพาะคำเตือน ณ ตำแหน่งปัจจุบัน และไม่แสดงคำเตือนทั้งหมดเมื่อเลื่อนดู
- Sideline + sideline-flycheck มีข้อจำกัดแบบเดียวกัน แต่ประสบการณ์ UI ดีกว่า
2 ความคิดเห็น
https://web.archive.org/web/20260513001754/…
ความคิดเห็นจาก Lobste.rs
สำหรับบางภาษา การแนะนำให้ Eglot เริ่มทำงานอัตโนมัติอาจเป็น ความคิดที่แย่มากถึงขั้นอันตราย ได้ เซิร์ฟเวอร์ LSP ของหลายภาษานั้นไม่ปลอดภัยพอสำหรับใช้กับโค้ดที่ไม่น่าเชื่อถือ และแค่เปิดไฟล์ในโปรเจ็กต์ Rust หรือ Elixir ที่ผู้โจมตีควบคุม ก็อาจทำให้เครื่องถูกเจาะได้
ถ้าไม่ใช่ภาษาที่มี LSP server ซึ่งเป็นที่รู้กันว่าปลอดภัย ก็ควรหลีกเลี่ยง การเปิดใช้ LSP อัตโนมัติ หลักฐาน: https://rust-analyzer.github.io/book/security.html
git statusก็อาจเป็นพื้นผิวการโจมตีได้: https://github.com/justinsteven/advisories/…ความต่างคือกรณีข้างต้นต้องมี exploit อยู่ในตัวรีโพเอง แต่ฝั่ง LSP ปัญหาอาจมาจาก dependency ได้ด้วย ถึงอย่างนั้น ถ้าชินกับการเปิด LSP ติดเป็นนิสัยแล้ว ก็ดูยากที่จะไม่ค่อย ๆ ด้านชาต่อคำเตือน
rust-analyzerกินแรม 4GB วิ่งอยู่นานหลายนาที และสร้างไดเรกทอรีtarget/debug/ขนาดเกิน 1GB ได้cargo buildไปแล้ว ก็แทบถือว่าจบเหมือนกัน แน่นอนว่า การโหลด LSP อัตโนมัติ กับคำสั่งที่ผู้ใช้รันเองโดยชัดเจนนั้นต่างกันมาก แต่ในการใช้งานจริง ความต่างอาจเล็กกว่าที่คิดlsp-modeแล้วค่อยเพิ่มโปรเจ็กต์เข้า allowlist ถ้ามี hook ที่ “รันอัตโนมัติ” อยู่แล้ว ก็เขียนให้read-from-minibufferถามก่อนว่า “คุณเชื่อถือโฟลเดอร์นี้ไหม?” ได้ในราว 10 บรรทัด และถ้าใช้projectileหรืออะไรคล้ายกันช่วยกำหนดไดเรกทอรีฐาน ก็จะได้ประโยชน์ด้านความปลอดภัยเกือบทั้งหมดการตั้งค่าของฉันใช้ allowlist ของ
lsp-modeแต่ลบทิ้งทุก session เพื่อให้ต้องยืนยันใหม่เป็นรายโปรเจ็กต์ทุกครั้งที่เปิด Emacs ขึ้นมาใหม่ เดิมทีน่าจะทำแบบนี้เพราะเรื่องประสิทธิภาพ และlsp-modeเคยมีจังหวะที่สปินหลายโปรเซสขึ้นมาก่อนจะเปิดบางโปรเจ็กต์เสียอีก ความเสี่ยงด้านความปลอดภัยมีอยู่จริง แต่การทำเวิร์กโฟลว์ที่ใช้งานได้ดีก็ไม่ได้ยากมากนักสิ่งที่น่ารำคาญที่สุดใน Eglot คือมันไม่ค่อยเปิดคำสั่งส่วนใหญ่เป็นฟังก์ชัน แต่ไปนิยามไว้บน อินเทอร์เฟซของ Emacs อย่าง
xrefแทน พอมีทั้งแบ็กเอนด์xrefของCIDERและclojure-lspแบบใน Clojure ก็เลยมักอยากเลือกฝั่งCIDERที่รู้สถานะรันไทม์จริงของโค้ดที่โหลดอยู่มากกว่าการวิเคราะห์แบบสแตติกของ
clojure-lspโดยเฉพาะในเวิร์กโฟลว์ REPL ระยะไกล อาจซิงก์คลาดกันได้ ในlsp-modeคุณยังเรียกฟีเจอร์อย่างกระโดดไปยัง definition เป็นคำสั่งโดยตรง พร้อมกับใช้xrefต่อไปได้ แต่ใน Eglot การตัดแบ็กเอนด์xrefบางตัวออกโดยเฉพาะนั้นค่อนข้างยุ่งยาก คำสั่งอื่น ๆ ที่มีในlsp-modeก็หายไปจาก Eglot ด้วย ทั้งที่จริงแล้วเป็นฟีเจอร์ที่น่าจะให้ผ่านจุดเชื่อมต่อการทำงานกับ Emacs คล้ายxrefได้เคยลองใช้
lsp-modeครั้งหนึ่ง แต่มีป๊อปอัปและการแจ้งเตือนที่ชวนสับสนเยอะเกินไป เลยลบทิ้งทันที Eglot ให้ประสบการณ์ LSP ที่ เงียบกว่าเยอะเปิดทิ้งไว้ แล้วค่อยใช้ฟีเจอร์เมื่อพร้อมก็พอ น่าสนใจดีที่ ~cks ใช้วิธีเข้าหาจากอีกทิศหนึ่งพร้อมพูดถึงทิปและทางเลือกหลายแบบ
lsp-modeเลยใช้งานด้วยอินเทอร์เฟซที่ค่อนข้างเงียบได้ เคยคิดจะย้ายไป Eglot แต่ตอนนั้นดูเหมือนไม่มีการผสานการทำงานที่อยากได้ เลยไม่ได้ลองต่อสิ่งที่อยากได้จริง ๆ คือ LSP server ที่รับมือกับ รีโพขนาดใหญ่มาก ได้ นี่เป็นข้อจำกัดที่เจอบ่อย จนเริ่มคิดว่าควรทำอะไรสักอย่างที่สร้างดัชนีซอร์สโค้ดครั้งเดียวแล้วเอาไปใช้ซ้ำกับงานสไตล์ LSP หลายอย่างได้หรือเปล่า แต่ก็กลัวว่าจะกลายเป็นการพยายามต้มมหาสมุทรอีก
Eglot กับ
lsp-modeเป็น LSP client สำหรับ Emacs ที่ดังที่สุดก็จริง แต่ก็ยังมีทางเลือกอย่าง lspce และ lsp-bridgeฉันใช้ Eglot มาหลายปีอย่างพอใจ แต่ก็มี ข้อจำกัดด้านการออกแบบ ที่อาจกลายเป็นปัญหามากขึ้นในอนาคต มันตั้งสมมติฐานว่า 1 บัฟเฟอร์ต่อ 1 ไคลเอนต์ ซึ่งตอนที่ Eglot ถูกสร้างขึ้นมาถือว่าสมเหตุสมผล แต่ตอนนี้กรณีที่อยากรัน LSP server หลายตัวในบัฟเฟอร์เดียวเริ่มพบได้บ่อยขึ้น คำแนะนำปัจจุบันคือใช้โปรแกรมแยกต่างหากเป็นตัว multiplex LSP
เมื่อ 4 วันก่อนฉันย้ายจาก
lsp-modeไป Eglot สำหรับ Python และพอใจมากตอนนี้เผยแพร่เวอร์ชันขั้นต่ำของการตั้งค่าปัจจุบันไว้ที่นี่: https://discuss.afpy.org/t/configuration-emacs-minimale-en-2026/3001
แต่อย่างไรก็ยังมีจุดน่าหงุดหงิดเรื่องการเติมโค้ดอยู่บ้าง เช่น ถ้ากด
foo<tab><tab>บางครั้งbasedpyrightจะ auto-import ของแปลก ๆ ทั้งที่มีสัญลักษณ์ที่ตรงกับสโคปปัจจุบันอยู่แล้ว และฉันก็ยังหาวิธีให้มันเติมแค่สตริงร่วมที่ยาวที่สุดไม่ได้ นอกนั้นถือว่าค่อนข้างดีอิจฉาคนที่ทำให้ Emacs ใช้งานเหมือน IDE สมัยใหม่ได้ ฉันใช้คีย์ไบน์ดิงของ Emacs อยู่ แต่ต่อให้ลงเวลาไป 6–8 ชั่วโมงก็ยังทำให้ Emacs ทำงานเหมือน IDE ไม่ได้
เมื่อก่อนเคยพยายามปรับให้เข้ากับ FB Flow ซึ่งใช้แทน TypeScript ในสภาพแวดล้อมพัฒนา Linux และ FreeBSD แล้วก็ยอมแพ้ และเมื่อสุดสัปดาห์ที่แล้วก็ลองทำสภาพแวดล้อม Python แบบครบฟีเจอร์บน Windows พร้อม tree-sitter ก่อนจะยอมแพ้อีกครั้ง มีอะไรให้ตั้งค่าเยอะเกินไป แถมยังต้องไปโหลด DLL อย่างตัวแยกวิเคราะห์ tree-sitter อีกหลายตัวแยกต่างหาก สิ่งที่ต้องมีเพื่อให้มันกลายเป็น IDE ที่ดีนั้นเยอะเกิน ตอนนี้เลยไม่อยากทุ่มเวลาแล้ว แต่ก็ยังชอบตรงที่บางครั้งพิมพ์
emacs -nwในเทอร์มินัลไหนก็ได้ แล้วได้สภาพแวดล้อมแก้ไขที่คุ้นเคยขึ้นมาทันทีfido-vertical-mode,which-key-mode,global-completion-preview-mode,yasnippet,eglot-ensure,basedpyrightก็น่าจะเริ่มต้นได้สบายแล้วถ้า
basedpyrightอยู่ใน path ก็จะได้ทั้งการเติมโค้ดและ syntax highlighting ที่ใช้งานได้ดีแม้ไม่มีไวยากรณ์ tree-sitter นี่เป็นเวอร์ชันที่ตัดทอนให้เหลือขั้นต่ำจากการตั้งค่าทั้งหมด และการตั้งค่าเต็มอยู่ที่ my full configdoom emacsก็น่าสนใจ ตั้งค่าง่ายมากและส่วนใหญ่ใช้งานได้ดีตั้งแต่ค่าเริ่มต้น ถ้าไม่ชอบevil-modeก็เปลี่ยนกลับไปใช้ คีย์ไบน์ดิงของ Emacs ได้