เขียนการเติมคำอัตโนมัติด้วยแท็บแบบง่ายใน Bash และ Zsh
(mill-build.org)- แนะนำวิธีสร้างฟีเจอร์เติมคำอัตโนมัติด้วยแท็บใน Bash และ Zsh ที่แสดงคำอธิบายได้แม้กับคำที่เติมครบแล้ว
- Bash และ Zsh ใช้ API สำหรับ tab completion คนละแบบ และมีเพียง Zsh ที่รองรับ การแสดงคำอธิบายในรายการเติมคำอัตโนมัติ มาให้โดยพื้นฐาน
- ใช้
_generate_foo_completionsเพื่อสร้างตัวเลือก และส่งกลับผ่านCOMPREPLYใน Bash, และcompaddใน Zsh - เพื่อให้ Bash แสดงคำอธิบายแบบเดียวกับ Zsh จึงใช้วิธี ใส่คำอธิบายไว้ในสตริงของตัวเลือก และลบคำอธิบายออกเฉพาะตอนที่มีตัวเลือกเดียว
- แม้ในกรณีที่มีตัวเลือกเดียว ก็ยังปรับปรุงให้แสดงคำอธิบายเมื่อกด
<TAB>ได้ โดย จงใจเพิ่มความกำกวม เข้าไป - สคริปต์สุดท้ายให้ประสบการณ์ใช้งานแบบเดียวกันในทั้งสองเชลล์ และรองรับครบทั้ง การเติมบางส่วน การเติมทั้งหมด และการแสดงคำอธิบายของตัวเลือก
ที่มาของปัญหา
- การเติมคำอัตโนมัติด้วยแท็บ (tab-completion) มีประโยชน์มากเวลาไล่ดูคำสั่งหรือแฟล็ก โดยเฉพาะเมื่อเพิ่งเริ่มใช้ API หรือเครื่องมือ CLI
- โดยปกติ Zsh จะแสดง คำอธิบายเมื่อมีหลายตัวเลือกเท่านั้น ส่วน Bash ต้องอาศัย การตั้งค่าเพิ่มเติม
- แต่สำหรับ คำที่เติมครบอยู่แล้ว ทั้งสองเชลล์ จะไม่แสดงคำอธิบาย
- ทำให้ผู้ใช้ต้องผ่านขั้นตอนที่ยุ่งยากดังนี้เพื่อดูคำอธิบาย
- ลบตัวอักษรบางส่วนออกเพื่อให้ตรงกับหลายตัวเลือก
- กดปุ่ม
<TAB>เพื่อดูรายการตัวเลือก - มองหาและแยกคำอธิบายที่ต้องการด้วยสายตา
- พิมพ์ตัวอักษรที่ลบไปกลับเข้าไปอีกครั้งเพื่อรันคำสั่ง
ภาพรวมของวิธีแก้
- เมื่อมีตัวเลือกเดียว ก็ เพิ่มตัวเลือกหลอก (dummy completion) เข้าไปเพื่อทำให้เกิดความกำกวมในรายการตัวเลือก
- วิธีนี้ทำให้ ทั้ง Bash และ Zsh แสดงรายการตัวเลือกพร้อมคำอธิบาย ได้
- อย่างไรก็ตาม ต้องระวังไม่ให้ข้อความคำอธิบายถูกแทรกเข้าไปในคำสั่งจริง
แนวคิดพื้นฐาน
- การเติมคำอัตโนมัติด้วยแท็บจะทำงานโดยรับคำปัจจุบันและตำแหน่งเคอร์เซอร์เมื่อกด
<TAB>แล้วคืนรายการตัวเลือกที่เป็นไปได้ - Bash: กำหนดตัวเลือกลงในอาร์เรย์
COMPREPLY - Zsh: ลงทะเบียนตัวเลือกด้วยคำสั่ง
compadd - ฟังก์ชัน
_generate_foo_completionsจะพิมพ์สตริงของตัวเลือกออกมา และในการใช้งานจริงสามารถสร้างแบบไดนามิกตามสถานะของ CLI ได้
รองรับ Bash และ Zsh พร้อมกัน
- แยกเขียนตามเชลล์ด้วยฟังก์ชัน
_complete_foo_bashและ_complete_foo_zsh - ใช้
if [ -n "${ZSH_VERSION:-}" ]; then ... elif [ -n "${BASH_VERSION:-}" ]; then ... fiเพื่อแยกกรณี - ผู้ใช้สามารถนำสคริปต์ไปลงใน
.bashrcหรือ.zshrcแล้วใช้งานได้
การแสดงคำอธิบายใน Zsh
- ใช้รูปแบบ
ชื่อ: คำอธิบายกับสตริงของตัวเลือก - Zsh: ส่งชื่อและคำอธิบายด้วยอาร์เรย์ขนานผ่าน
compadd -d raw -- $trimmed - Bash: ส่งเข้า
COMPREPLYเฉพาะตัวเลือกที่ตัดส่วนคำอธิบายออกแล้วเท่านั้น (เพราะปกติไม่รองรับคำอธิบาย)
ทำให้ Bash แสดงคำอธิบาย
- ถ้ามีหลายตัวเลือก ให้แสดงสตริงที่มีคำอธิบายติดมาด้วยตรง ๆ
- ถ้ามีตัวเลือกเดียว ให้ตัดคำอธิบายออกเฉพาะกรณีนั้น
- อาศัยพฤติกรรมของการเติมคำอัตโนมัติใน Bash ที่จะแทรกเฉพาะคำนำหน้าร่วมกัน เพื่อไม่ให้คำอธิบายถูกใส่ลงไปในอินพุตจริง
แสดงคำอธิบายได้แม้มีตัวเลือกเดียว
- เพื่อให้กด
<TAB>แล้วเห็นคำอธิบายได้แม้คำจะเติมครบแล้ว จึง เพิ่มตัวเลือกหลอก เพื่อสร้างความกำกวมโดยตั้งใจ - Zsh: เพิ่มตัวเลือกหลอกลงในทั้งสองอาร์เรย์ขนาน (
raw,trimmed) - Bash: ถ้ามีตัวเลือกเดียว ให้เพิ่มเฉพาะชื่อเข้าไปใน
trimmed
ผลลัพธ์สุดท้าย
- เมื่อมีหลายตัวเลือก จะแสดงทั้งชื่อและคำอธิบาย
- เมื่อมีตัวเลือกเดียว ก็ยังกด
<TAB>เพื่อดูคำอธิบายได้ - ทั้ง Bash และ Zsh ให้ประสบการณ์ใช้งานแบบเดียวกัน
- ตัวอย่างการใช้งาน:
$ foo <TAB> apple: a common fruit banana: starchy and high in potassium apricot: sour fruit... cherry: small and sweet...
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
fish_update_completions~/.cache/fish/generated_completions/curlของ-L→ 'Follow redirects',-O→ 'Write output to file named as remote file'car TABขยายเป็นblkdiscardได้ จนเกิดอาการเพี้ยนแม้ cargo จะไม่ได้อยู่ใน PATH--helpก็สงสัยว่า fish มีอะไรคล้าย_gnu_genericของ zsh หรือcomplete -F _longoptของ bash ไหมzypper search fish-completionแล้วมีแพ็กเกจโผล่มาเกิน 200 ตัว เลยรู้สึกว่ามีอะไรแปลก ๆcomplete-filenameที่ bind กับ M-/ ซึ่งจะเติมเฉพาะชื่อไฟล์โดยไม่สนบริบทlsก่อนแล้วค่อยเปลี่ยนคำสั่งcomplete -rแล้วทำงานได้ตามต้องการ ก็ดูเหมือนว่าปัญหาอยู่ที่สคริปต์ bash-completion$fpathมันจะถูกแคชไว้ ทำให้ ความเร็วตอนเริ่มต้น ดีขึ้น_gnu_genericเพื่อทำ completion แบบง่ายจาก--helpได้--generateของ ripgrep, หรือการสร้าง completion ตอนรันไทม์ของ PHP Symfony--completionที่ parser library ทั่วไปช่วยทำให้ได้อัตโนมัติก็คงดีcomplete_kill_1ก็จะเติมอาร์กิวเมนต์ตัวแรกของคำสั่งkillได้set-java-homeใช้_describeเพื่อเติมรายการ~/apps/java/*เป็นเวอร์ชัน