1 คะแนน โดย GN⁺ 2024-05-13 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • การจัดการหน้าต่าง ของ Emacs อยู่บนโครงสร้างที่ยืดหยุ่นซึ่งแยกบัฟเฟอร์กับหน้าต่างออกจากกัน แต่มีการใช้งานพื้นฐานที่ค่อนข้างเทอะทะครอบอยู่ ผู้ใช้จึงต้องผสมผสานเครื่องมือกับความเคยชินของตนเองเพื่อสร้างเวิร์กโฟลว์
  • จุดเน้นของบทความนี้ไม่ได้อยู่ที่กฎ display-buffer หรือ workspace แบบแท็บ แต่เน้นไปที่การใช้งานด้วยมือภายใน Emacs frame เดียว เช่น การย้ายโฟกัส, การจัดวางบัฟเฟอร์, การแบ่ง/ลบหน้าต่าง, และงานชั่วคราว
  • other-window, windmove, winum, ace-window, เมาส์, transpose-frame, window-prefix-map, other-window-prefix, winner-mode ต่างแบ่งหน้าที่กันในด้านการย้ายแบบวนรอบ, การย้ายตามทิศทาง, การเลือกตามหมายเลข, การเลือกด้วย hint, การแปลง layout, และการกู้คืน
  • หากใช้ ace-window ร่วมกับ Avy คุณสามารถเลือกหน้าต่างที่ต้องการแล้วค่อยสั่งคำสั่ง หรือกระโดดไปยังตำแหน่งข้อความได้ทันทีเพื่อลดความจำเป็นในการ สลับหน้าต่างโดยตรง
  • การจัดการหน้าต่างของ Emacs ไม่ได้เป็นระบบตายตัวที่ซับซ้อน แต่ใกล้เคียงกับ ปัญหาที่ยังเปิดอยู่ มากกว่า โดย display-buffer-alist, Popper·Popwin, การจัดการ window tree, และการผสานกับ tiling WM นั้นทรงพลัง แต่ก็มาพร้อมต้นทุนด้านการตั้งค่าและช่องโหว่ของมันเอง

สิ่งที่ Emacs เรียกว่า window management

  • window ของ Emacs คือ viewport หรือ panel ภายใน frame ส่วน buffer คือก้อนข้อความต่อเนื่องที่อาจเป็นเนื้อหาไฟล์หรือไม่ใช่ก็ได้
  • IDE และเอดิเตอร์จำนวนมากแทบจะรวมแนวคิดเรื่อง window กับ buffer เข้าด้วยกันเพื่อลดภาระในการทำความเข้าใจ แต่ Emacs แยกสองสิ่งนี้ออกจากกันเพื่อให้จัดวางได้ยืดหยุ่นกว่า
    • การเปิดดูไฟล์เดียวกันสองครั้งทำได้ง่าย
    • สามารถจัดการบัฟเฟอร์ที่ไม่ได้ตรงกับไฟล์โดยตรงได้อย่างเป็นธรรมชาติ
    • indirect buffers ของ Emacs ใกล้เคียงกับสำเนาแบบ “live” ของทั้งบัฟเฟอร์
  • โครงสร้างแบบแยกนี้มีต้นทุนสำหรับผู้ใช้ใหม่
    • ต้องวางหน้าต่างให้อยู่ในตำแหน่งที่ต้องการภายใน frame
    • ต้องใส่บัฟเฟอร์ที่ต้องการลงในหน้าต่างที่ต้องการ
    • ในช่วงแรก ภาระในการควบคุมมักเด่นชัดกว่าข้อดีของโครงสร้างแบบแยกนี้

สิ่งที่บทความนี้ไม่ได้ครอบคลุม

  • กฎการแสดงบัฟเฟอร์อัตโนมัติ ที่ควบคุม display-buffer อย่างสมบูรณ์ไม่ใช่หัวข้อหลัก
  • แท็บ, workspace, การแยกบัฟเฟอร์, และการคงอยู่ของเซสชัน ก็อยู่นอกขอบเขตหลักเช่นกัน
  • ทางเลือกแบบสุดโต่งที่ทำงานเสมือนเป็น window manager แยกต่างหากภายใน Emacs ก็ไม่ถูกรวมไว้ในเนื้อหาหลัก
    • Edwina บังคับใช้ layout แบบ master-and-stack auto-tiling สไตล์ DWM
    • HyControl มีแผงควบคุม action สำหรับ layout ของหน้าต่าง และความสามารถอย่างการแสดงผลแบบกริดเท่ากัน
  • จุดสนใจที่แท้จริงคือ การใช้งานด้วยมือในชีวิตประจำวัน
    • การสลับโฟกัสหน้าต่าง
    • การย้ายบัฟเฟอร์ระหว่างหน้าต่าง
    • การแบ่งและลบหน้าต่าง
    • การควบคุมหน้าต่างปลายทางที่ใช้แสดงผลระหว่างรันคำสั่ง

การย้ายพื้นฐาน: other-window และ “หน้าต่างถัดไป”

  • other-window คือคำสั่งพื้นฐานสำหรับสลับหน้าต่างที่เรียนกันในบทสอน Emacs โดยคีย์เริ่มต้นคือ C-x o
  • ลำดับการเลือกโดยทั่วไปจะวนตามเข็มนาฬิกาภายใน frame
    • เมื่อมีหน้าต่างน้อย ก็เรียบง่ายและใช้งานได้ดีพอ
    • เมื่อหน้าต่างมากขึ้น ก็ต้องเรียกหลายครั้งกว่าจะไปถึงตำแหน่งที่ต้องการ
  • ยังมีวิธีปรับให้ใช้งานได้จริงมากขึ้น
    • สามารถ bind ไปที่คีย์ที่กดง่ายกว่า เช่น M-o
    • ใช้ numeric argument เพื่อข้ามหลายช่องหรือย้ายย้อนทิศได้
    • ถ้าเปิด repeat-mode จะกดซ้ำได้แบบ C-x o o o... หรือ M-o o o...
  • หากตั้งค่า window parameter no-other-window ไว้ other-window จะข้ามบางหน้าต่าง
    • นี่คือเหตุผลที่หน้าต่างด้านข้างแบบ file manager อย่าง dired-sidebar หรือ dirvish-side ไม่ถูกรวมอยู่ในเป้าหมายของ other-window
  • แนวคิดเรื่อง “หน้าต่างถัดไป” ยังเป็นเป้าหมายเริ่มต้นของคำสั่งที่ทำงานกับอีกหน้าต่างหนึ่ง เช่น scroll-other-window

การย้ายตามทิศทาง: windmove

  • windmove เป็น ไลบรารีที่มีมาในตัว สำหรับย้ายโฟกัสหน้าต่างตามทิศทาง
    • ใกล้เคียงกับการย้ายซ้าย/ขวา/ขึ้น/ลงที่ผู้ใช้ Vim คาดหวัง
    • ผู้ใช้ evil-mode ก็ใช้ Windmove อยู่ภายในเช่นกัน
  • สามารถผูก windmove-left, windmove-right, windmove-up, windmove-down เข้ากับ WASD, HJKL, ปุ่มลูกศร ฯลฯ ได้
  • คำสั่ง windmove-swap-states-left/right/up/down ใช้ สลับบัฟเฟอร์ของหน้าต่าง ตามทิศทาง
    • ในกรณีนี้ โฟกัสจะย้ายไปพร้อมกับบัฟเฟอร์
  • ตระกูลคำสั่ง windmove-delete-* ก็ใช้ลบหน้าต่างในทิศที่กำหนดได้ แต่ในบทความจะพูดถึงวิธีอื่นเพิ่มเติมด้านล่าง
  • ในสภาพแวดล้อม tiling WM สามารถผสานให้ใช้คีย์เดียวกันในการย้ายระหว่างหน้าต่างภายใน Emacs กับหน้าต่างของ OS ได้
    • ตัวอย่างการผสาน Emacs+i3wm ดูได้ที่ i3 integration
    • ตัวอย่างการตั้งค่าสำหรับ qtile ดูได้ใน emacs-wm.el

ปล่อยให้ OS จัดการ Emacs window แทน

  • frames-only-mode จะเปิดทุกบัฟเฟอร์เป็น frame ใหม่แทน Emacs window เพื่อให้ OS window manager เป็นผู้จัดการ
  • วิธีนี้ทำให้บัฟเฟอร์ของ Emacs ถูกจัดการในระดับเดียวกับหน้าต่างของ OS จึงสามารถใช้คีย์เดียวกันควบคุมทั้งสองฝั่งได้
  • เครื่องมืออย่าง Avy, winum, ace-window, scroll-other-window สามารถใช้ได้ไม่เฉพาะกับหน้าต่าง แต่กับทั้ง frame ด้วย
  • คำสั่งบางอย่างของ Emacs สมมุติว่ามันสามารถแบ่ง frame ได้ตามต้องการ จึงอาจเกิดกรณียกเว้นขึ้น
    • โดยเฉพาะคำสั่งใน org-mode มักมีแนวโน้มเช่นนั้น
  • ยังไม่มีประสบการณ์ใช้งาน frames-only-mode บน Wayland compositor

เลือกได้ทันทีด้วยหมายเลขและคำใบ้

  • winum แสดงหมายเลขหน้าต่างบน mode line และให้เลือกหน้าต่างด้วยหมายเลขได้
    • ถ้า other-window ใกล้เคียงกับ O(n) และ windmove เป็นวิธีที่อาศัยการจัดวางเชิงพื้นที่ winum จะใกล้เคียงกับการเข้าถึงแบบ O(1) เมื่อรู้หมายเลข
    • ถ้าเรียกคำสั่งเลือกพร้อม prefix argument ติดลบ จะลบหน้าต่างนั้น
    • เมื่อ minibuffer ทำงานอยู่ จะได้รับหมายเลข 0 เสมอ
    • ทำงานข้าม Emacs frame ได้
  • คีย์เริ่มต้น C-x w <n> อาจยาว จึงมีตัวอย่างการตั้งค่าให้ใช้ M-0 ถึง M-9 สำหรับเลือกหน้าต่าง
  • ace-window ถูกยกให้เป็นเครื่องมือทรงพลังสำหรับควบคุมหน้าต่าง Emacs ด้วยคีย์บอร์ด
    • แสดงคำใบ้บนแต่ละหน้าต่าง แล้วกดคีย์นั้นเพื่อเลือก
    • ถ้าเปิด ace-window-display-mode ก็จะแสดงคำใบ้บน mode line ตลอดได้เหมือน winum
    • กด ? เพื่อเปิดเมนู dispatch
  • จุดแข็งของ ace-window คือไม่ได้มีแค่การเลือก แต่เป็นการ เลือกหน้าต่างแล้วค่อยรันแอ็กชัน
    • ลบหน้าต่าง
    • ย้ายและสลับหน้าต่าง
    • แบ่งหน้าต่าง
    • แสดงบัฟเฟอร์ที่ต้องการ
    • ใช้งานกับหน้าต่างอื่นโดยไม่ต้องออกจากหน้าต่างที่เลือกอยู่

เมาส์และการแปลงเลย์เอาต์

  • ใน Emacs เมาส์เข้ากับการเลือกหน้าต่าง ปรับขนาด เมนูตามบริบท และ drag and drop ได้อย่างเป็นธรรมชาติ
    • context-menu-mode และแถบเมนูช่วยให้ค้นพบฟีเจอร์ได้ง่ายขึ้น
  • ถ้ามือออกจากคีย์บอร์ดไปแล้ว การควบคุม Emacs ด้วยเมาส์อาจเป็นเส้นทางที่ฝืนน้อยที่สุด
  • ยังมีงานที่ทำได้ด้วย mouse gesture
    • แบ่ง frame แนวตั้ง·แนวนอน
    • ลบหน้าต่าง
    • วนบัฟเฟอร์ภายในหน้าต่าง
    • สลับหน้าต่างไปทางขวา·ซ้าย
    • สลับสองบัฟเฟอร์ล่าสุดในหน้าต่างเดียว
  • ตั้งค่า mouse-autoselect-window เพื่อเปิดพฤติกรรมแบบ focus-follows-mouse ได้
  • transpose-frame มีคำสั่งสำหรับหมุนหรือพลิกเลย์เอาต์หน้าต่างของ frame
    • rotate-frame, flip-frame, flop-frame ถูกยกให้เป็นคำสั่งที่น่าใช้บ่อย
    • ส่วน transpose-frame เองเป็นการ transpose ตามแนวทแยงหลัก จึงถูกมองว่าใช้งานจริงได้น้อย

window-prefix-map และต้นไม้ของหน้าต่าง

  • window-prefix-map คือชุดคำสั่งจัดการหน้าต่างที่มากับ Emacs และผูกไว้กับ C-x w
  • split-root-window-right และ split-root-window-below ใช้แบ่ง root window ของ frame
    • ผูกไว้กับ C-x w 3, C-x w 2 ตามลำดับ
    • ต่างจากคำสั่งแบ่งปกติที่ซอยหน้าต่างปัจจุบันลงไปอีก จึงมีประโยชน์เมื่ออยากสร้าง workspace แยก
  • หน้าต่างใน Emacs ถูกจัดวางเป็นโครงสร้างแบบต้นไม้
    • หน้าต่างจริงคือโหนดใบ
    • การแบ่งจะเปลี่ยน leaf ให้กลายเป็นโหนดพาเรนต์ของสองหน้าต่าง
    • คล้ายการจัดวางของตัวจัดการหน้าต่างแบบ manual tiling อย่าง i3 หรือ bspwm
  • tab-window-detach และ tear-off-window ย้ายหน้าต่างปัจจุบันไปเป็นแท็บใหม่หรือ frame ใหม่
    • คีย์เริ่มต้นคือ C-x w ^ t, C-x w ^ f ซึ่งถูกมองว่ายาว
    • ใช้แอ็กชัน dispatch ของ ace-window หรือการ bind กับเมาส์แทนได้

other-window-prefix: เปลี่ยนตำแหน่งแสดงผลของคำสั่งถัดไป

  • other-window-prefix เป็นคำสั่งในตัวที่ผูกกับ C-x 4 4 และทำให้บัฟเฟอร์ที่คำสั่งถัดไปแสดง ไปปรากฏใน หน้าต่างถัดไป
  • คำสั่งนี้แยกคำสั่งที่ใช้แสดงบัฟเฟอร์ เช่น เปิดไฟล์ สลับบัฟเฟอร์ หรือกระโดดไป bookmark ออกจากการเลือกหน้าต่างปลายทาง
    • ทำให้ไม่ต้องคอยจำชุดคำสั่งอย่าง find-file-other-window, find-file-other-tab, find-file-other-frame ทุกครั้ง
    • สามารถใส่ prefix ไว้หน้าคำสั่งพื้นฐานอย่าง find-file, find-file-read-only, switch-to-buffer เพื่อเปลี่ยนเฉพาะตำแหน่งแสดงผลได้
  • ในตัวอย่างของ Forge ปัญหาที่กด RET จากรายการ issue แล้วหน้าต่างปัจจุบันถูกเขียนทับ สามารถหลีกเลี่ยงได้ด้วย other-window-prefix
  • ยังให้พฤติกรรมที่สม่ำเสมอได้เมื่อแต่ละแพ็กเกจ เช่น Magit, Org mode, Notmuch, Elfeed, EWW มีวิธี “เปิดในหน้าต่างอื่น” ไม่เหมือนกัน
  • ยังมี prefix ที่เกี่ยวข้องด้วย
    • same-window-prefix บังคับให้บัฟเฟอร์ของคำสั่งถัดไปเปิดในหน้าต่างปัจจุบัน
    • other-frame-prefix บังคับให้เปิดใน frame ใหม่
    • other-tab-prefix บังคับให้เปิดในแท็บใหม่

บันทึกและย้อนคืนการจัดหน้าต่าง

  • window-configuration-to-register เป็นคำสั่งพื้นฐานสำหรับบันทึกการจัดหน้าต่างปัจจุบันลง register โดยคีย์เริ่มต้นคือ C-x r w
  • jump-to-register ใช้กู้คืนการจัดที่บันทึกไว้ โดยคีย์เริ่มต้นคือ C-x r j
  • ในระดับ Elisp สามารถเก็บผลลัพธ์ของ current-window-configuration ไว้ในตัวแปร แล้วนำไปใช้ด้วย set-window-configuration ได้
    • ถ้าบันทึกลงดิสก์ด้วยวิธีอย่าง prin1, persist, multisession ก็จะเป็นจุดเริ่มต้นของความสามารถในการกู้คืนข้ามเซสชัน
    • แต่การที่ตำแหน่งเคอร์เซอร์ของแต่ละหน้าต่างถูกกู้คืนด้วย อาจเป็นพฤติกรรมที่ปกติแล้วไม่ต้องการ
  • ยังมีตัวเลือกแบบ “oops” ที่คอยสะสมการจัดก่อนหน้าให้อัตโนมัติ
    • winner-mode: ถ้าไม่ได้ใช้แท็บ สามารถใช้ winner-undo, winner-redo เพื่อย้อนหรือทำซ้ำการเปลี่ยนแปลงการจัดหน้าต่าง
    • tab-bar-history-mode: มี history stack แยกตามแต่ละแท็บ
    • undelete-frame-mode และ tab-undo: กู้คืน frame หรือแท็บที่ปิดไปโดยไม่ตั้งใจ
  • การใช้ winner-undo เป็นวิธีคอยแก้สถานการณ์ทุกครั้งหลัง Emacs เปิดบัฟเฟอร์ไปผิดที่ เป็นเพียงทางออกชั่วคราวมากกว่า
    • ปัญหาที่แท้จริงคือกติกาการแสดงบัฟเฟอร์ยังไม่ได้ตั้งให้ตรงตามที่ต้องการ

ปรับให้เหมาะกับสองหน้าต่างที่สลับไปมาบ่อย

  • ในการทำงานจริง แม้บนหน้าจอจะมีหลายหน้าต่าง แต่ส่วนใหญ่จะสลับบ่อยอยู่ ระหว่างสองหน้าต่าง
    • Code & REPL
    • Code & Grep
    • Prose & Notes
    • Listing & Item
  • หน้าต่างอื่นอาจเป็นพื้นที่อ้างอิงที่ดูบ่อยแต่แทบไม่สลับไปมา เช่น เอกสาร ข้อมูลดีบัก ล็อก ผลลัพธ์คำสั่ง สารบัญ ตัวสำรวจไฟล์ หรือพรีวิว
  • มีตัวอย่าง other-window-mru ที่ใช้ get-mru-window เพื่อสลับไปยังหน้าต่างที่ใช้งานล่าสุด
    • ไม่ว่าจะเลือกหน้าต่างที่สองด้วยเมาส์, ace-window, winum หรือวิธีใด หลังจากนั้นก็สามารถสลับไปกลับได้อย่างรวดเร็ว
  • ยังมีแนวทางปรับปรุง other-window เองด้วย
    • ถ้ามีหน้าต่างเดียวก็ให้แบ่งอัตโนมัติ
    • วนตามลำดับการใช้งานล่าสุดแบบ switchy-window
    • หรือแบบ other-window-alternating ที่กลับทิศทางทุกครั้งเมื่อไม่ได้เรียกต่อเนื่อง เพื่อให้การสลับไปมาระหว่างสองหน้าต่างเป็นธรรมชาติ

ขยาย ace-window ให้เป็นฐานสำหรับการรันคำสั่ง

  • ace-window สามารถใช้เฉพาะขั้นตอนเลือกหน้าต่างอย่างเดียวได้ผ่าน aw-select
  • ถ้านำหน้าต่างที่ aw-select ส่งคืนมาไปใช้รันงานที่ต้องการ ก็จะสร้างแพตเทิร์น “เลือกหน้าต่าง → รันงาน → คงหน้าต่างเดิมไว้” ได้
  • คำสั่งตัวอย่างมีดังนี้
    • ace-tear-off-window: แยกหน้าต่างที่เลือกด้วย ace-window ออกไปเป็น frame ใหม่
    • ace-tab-window-detach: ย้ายหน้าต่างที่เลือกไปเป็นแท็บใหม่
  • ace-window-one-command ทำให้รันคำสั่งเดี่ยวใดก็ได้ในหน้าต่างที่เลือก
    • ก่อนอื่นเลือกหน้าต่างด้วย ace-window
    • จากนั้นอ่าน key sequence แล้วรันคำสั่งนั้น
    • จึงสามารถสั่งงานหน้าต่างอื่นได้โดยไม่เปลี่ยนหน้าต่างที่เลือกเดิม
  • ace-window-prefix ทำงานคล้ายเวอร์ชันที่ตรงไปตรงมาของ other-window-prefix
    • เลือกหน้าต่างปลายทางที่บัฟเฟอร์ของคำสั่งถัดไปจะไปแสดง ด้วย ace-window โดยตรง
    • ถ้าจำเป็นก็สร้างหน้าต่างใหม่ขึ้นมาทันทีด้วยแอ็กชันของ ace-window แล้วแสดงที่นั่นได้
    • คีย์ลัดที่เสนอคือ C-x 4 o

จำเป็นต้องสลับหน้าต่างเสมอไปหรือไม่

  • เป้าหมายของการสลับหน้าต่างแบ่งได้ใหญ่ ๆ เป็นสองแบบ
    • Switch and stay: แก้ไขหรือทำงานต่อในหน้าต่างปลายทาง
    • Switch and return: ทำงานชั่วคราวอย่างการเลื่อน ค้นหา คัดลอก หรือลบ แล้วกลับไปยังตำแหน่งเดิม
  • ในกรณีแรก Avy สามารถรวมการสลับหน้าต่างกับการย้ายเคอร์เซอร์ให้เป็นอย่างเดียวได้
    • avy-goto-char-timer มอง Emacs window และ frame หลายอันที่มองเห็นบนหน้าจอเป็นกลุ่มผู้สมัครสำหรับการกระโดดชุดเดียว
    • กระโดดไปยังสตริงที่กำหนดหรือตำแหน่งของอักขระ พร้อมสลับหน้าต่างให้อัตโนมัติ
    • ถ้า Avy ไม่ข้ามระหว่าง window และ frame ควรตรวจสอบการตั้งค่า avy-all-windows
  • pop-global-mark เป็นวิธีสำหรับย้อนกลับไปยังตำแหน่งก่อนหน้า และหากจำเป็นก็อาจมีการสลับหน้าต่างร่วมด้วย
    • โดยปกติจะสลับบัฟเฟอร์ในหน้าต่างปัจจุบัน จึงมี advice ที่ทำให้ใช้ pop-to-buffer
  • ในกรณีที่สอง สามารถห่อขั้นตอน “สลับ → ลงมือทำ → กลับคืน” ไว้ในคำสั่งเพื่อทำงานอัตโนมัติได้
    • ace-window-one-command เป็นวิธีทั่วไปแบบหนึ่ง
    • สำหรับงานเฉพาะ สามารถทำเป็นคำสั่งหรือมาโครเฉพาะได้

การเลื่อน ค้นหา และสลับบัฟเฟอร์ในหน้าต่างอื่น

  • scroll-other-window และ scroll-other-window-down จะเลื่อนหน้าต่างอื่นโดยไม่ออกจากหน้าต่างปัจจุบัน
    • เป้าหมายเริ่มต้นคือ “หน้าต่างถัดไป” ตามเข็มนาฬิกาจากหน้าต่างปัจจุบัน
    • หากมีหน้าต่างตั้งแต่สามบานขึ้นไป อาจเลื่อนไปยังหน้าต่างอื่นที่ไม่ใช่หน้าต่างอ้างอิงที่คาดไว้
  • สามารถตั้งค่า other-window-scroll-default เพื่อเปลี่ยนกฎการเลือกหน้าต่างที่จะเลื่อนได้
    • ถ้าใช้ get-lru-window จะเลื่อนหน้าต่างที่ถูกใช้งานล่าสุดน้อยที่สุด
    • ถ้าใช้ get-mru-window ก็สามารถกำหนดให้เลื่อนหน้าต่างที่ใช้งานล่าสุดได้
  • หากตั้งค่า other-window-scroll-buffer ก็สามารถระบุให้หน้าต่างที่แสดงบัฟเฟอร์เฉพาะเป็นเป้าหมายการเลื่อนได้
    • โดยมากถือเป็นตัวเลือกที่มีประโยชน์กับผู้เขียนแพ็กเกจ
  • ตัวอย่าง isearch-other-window คือการเริ่มค้นหาในหน้าต่างเป้าหมายสำหรับการเลื่อน แล้วกลับมายังหน้าต่างเดิมเมื่อค้นหาเสร็จ
    • มีตัวอย่างการใช้งานร่วมกันของบัฟเฟอร์ shell และ Man
  • next-buffer, previous-buffer, switch-to-buffer ก็สามารถขยายให้ทำงานในหน้าต่างอื่นได้ด้วยการใช้ prefix argument ร่วมกับ other-window-for-scrolling
    • หากสร้าง keymap สำหรับ repeat-mode ก็จะสามารถใช้ n, p, b เพื่อวนหรือเลือกบัฟเฟอร์ของหน้าต่างอื่นต่อเนื่องได้

master-mode, scroll-all-mode, with-other-window

  • master-mode กำหนดบัฟเฟอร์ปัจจุบันเป็น “master” และบัฟเฟอร์อื่นเป็น “slave” เพื่อส่ง action ไปยังบัฟเฟอร์ slave ได้โดยไม่ต้องออกจากตำแหน่งปัจจุบัน
    • โดยทั่วไปถูกมองว่าเป็นทางเลือกที่โปร่งใสน้อยกว่าวิธี other-window-scroll-default ข้างต้น
    • ผ่าน master-says สามารถสร้างคีย์สำหรับสั่ง action ใดก็ได้ เช่น recenter ในบัฟเฟอร์ slave
  • scroll-all-mode จะผูกพฤติกรรมการเลื่อนของทุกหน้าต่างภายใน frame เข้าด้วยกัน
    • มีประโยชน์เมื่ออยากดูหลายมุมมองของหน้าต่างแบบซิงก์กัน
  • มาโคร with-other-window เป็นตัวช่วย Elisp ที่รันโค้ดเนื้อหาในหน้าต่างที่ other-window-for-scrolling เลือกไว้
    • ช่วยสร้างคำสั่งอย่าง isearch-other-window, isearch-other-window-backwards ได้อย่างง่ายดาย
    • ใช้ได้เหมือนเป็นคู่ Elisp ของ ace-window-one-command แบบโต้ตอบ

กลยุทธ์ลดจำนวนหน้าต่าง

  • UI ของเอดิเตอร์สมัยใหม่โดยมากมักลงตัวที่หน้าต่างหลักหนึ่งบาน แถบแท็บด้านบน แถบด้านข้างซ้ายสำหรับไดเรกทอรีหรือสารบัญ แผงด้านขวาแบบเลือกได้ และเทอร์มินัลด้านล่าง
  • ใน Emacs ก็สร้างเลย์เอาต์แบบนั้นได้ แต่ก็สามารถใช้วิธีโฟกัสหน้าจอกับบัฟเฟอร์เดียว แล้วแทนการสลับหน้าต่างด้วยการสลับบัฟเฟอร์ได้เช่นกัน
  • หากผ่อนให้มีได้แค่สองหน้าต่าง ก็ยังคงความเรียบง่ายไว้ได้เกือบทั้งหมด พร้อมใช้หน้าต่างที่สองเป็นข้อมูลอ้างอิงแบบสด
    • การตั้งค่าเริ่มต้นของ Emacs รองรับแพตเทิร์นนี้ได้ค่อนข้างดีผ่านคำสั่งอย่าง scroll-other-window
  • ยังมีแนวทางที่มองหน้าต่างเป็นเพียงคอนเทนเนอร์ของข้อความแล้วไม่สนใจมัน
    • ใช้ Avy กระโดดตรงไปยังตำแหน่งข้อความบนหน้าจอ
    • ใช้ mark-ring, global-mark-ring เพื่อตามรอยตำแหน่งก่อนหน้า
    • dogears อาจให้ UI สำหรับการย้อนรอยที่ละเอียดกว่านี้
  • สำหรับการตรึงตำแหน่งด้วยมือ สามารถใช้ point-to-register และ jump-to-register ได้ และสำหรับการบันทึกที่ถาวรกว่านั้นสามารถใช้ bookmark-set และ bookmark-jump

กฎการแสดงผลอัตโนมัติและการจัดการป๊อปอัป

  • display-buffer-alist เป็นตัวแปรที่จับคู่บัฟเฟอร์กับกฎ เพื่อกำหนดวิธีแสดงผลเมื่อโค้ด Elisp พยายามแสดงบัฟเฟอร์
  • ในทางทฤษฎี หากสร้างกฎเรื่องขนาด ตำแหน่ง บทบาท และโฟกัส สำหรับประเภทบัฟเฟอร์ที่ใช้ทุกวันแต่ละแบบ ก็จะแก้ปัญหาการจัดการหน้าต่างไปได้มาก
  • ปัญหาคือ ต้นทุนในการตั้งค่า
    • predicate ของบัฟเฟอร์และโหมด
    • ประเภทหน้าต่างและสล็อต
    • ฟังก์ชัน action ของ display-buffer
    • พารามิเตอร์ของหน้าต่าง
    • เจตนาง่าย ๆ อย่าง “อย่ามายุ่งกับการจัดวางหน้าต่างของฉัน” กลับแสดงออกได้ยาก
  • ยังมีเครื่องมือเสริมอยู่
    • Shackle ช่วยซ่อนความซับซ้อนของ display-buffer-alist และให้อินเทอร์เฟซ Elisp ที่เรียบง่าย
    • Doom Emacs มีคำสั่งอำนวยความสะดวกอย่าง set-popup-rule!
  • Popwin และ Popper เริ่มต้นจากข้อสังเกตที่ว่าบัฟเฟอร์ไม่ได้เท่ากันทั้งหมด
    • แยกระหว่างบัฟเฟอร์งานหลักกับบัฟเฟอร์ป๊อปอัปชั่วคราว
    • แสดงเอกสาร shell สถานะงาน ผลคอมไพล์ ผลการค้นหา ข้อความ ฯลฯ เป็นหน้าต่างเสริมขนาดเล็กที่ปิดหรือวนดูได้ง่าย
    • Popper เน้นการยกขึ้นและซ่อนลงของบัฟเฟอร์ป๊อปอัปที่กำหนดไว้ด้วยคีย์เดียว
    • Popwin เป็น implementation ที่เก่ากว่าและครอบคลุมกว่า แต่รวมการตั้งค่า display-buffer ของตัวเองมาด้วย
    • หากเพียงต้องการเปิดปิด shell อย่างรวดเร็ว shell-pop หรือ vterm-toggle ก็อาจเพียงพอ

ชิ้นส่วนที่ยังขาด: การจัดการ window tree

  • Emacs แทนหน้าต่างภายใน frame เป็น tree แต่คำสั่งสำหรับผู้ใช้ส่วนใหญ่กลับทำงานโดยดูจากตำแหน่งเชิงพื้นที่ ไม่ใช่โครงสร้าง tree
  • ความไม่สอดคล้องนี้ทำให้การ split และ delete บางครั้งให้ผลต่างจากที่คาด หรือเกิดข้อจำกัดกับโครงสร้างการแบ่งที่สร้างได้
  • หากมีแพ็กเกจสมมติอย่าง window-tree ก็อาจทำให้จัดการในระดับโหนดภายในได้
    • split, transpose, mirror เฉพาะบางส่วนของ frame
    • เลือกหลายหน้าต่างเป็นองค์ประกอบย่อยบางส่วน
    • ส่งองค์ประกอบที่เลือกไปยังแท็บหรือ frame
    • โคลนหรือบันทึกองค์ประกอบย่อย
    • ปกป้อง branch ของ tree บางส่วนจากคำสั่งตระกูล display-buffer
  • มีองค์ประกอบ Elisp ที่มีอยู่แล้วเช่นกัน
    • window-tree จะคืนค่า tree เอง
    • frame-root-window จะคืนค่า root
    • มี window-parent, window-child, window-*-sibling
    • สามารถวนสำรวจได้ด้วย walk-window-tree, walk-windows
  • จุดที่ยังขาดก็ชัดเจน
    • นอกจาก split/delete ทั่วไปแล้ว ไม่มีฟังก์ชันพื้นฐานสำหรับแปลง tree
    • ไม่มีแนวคิดเรื่องการเลือกหน้าต่างภายใน จึงต้องจำลองผ่าน UI

ชิ้นส่วนที่ขาดหายไป: การผสานรวมกับ tiling WM

  • โมเดลต้นไม้ของหน้าต่างใน Emacs คล้ายกับตัวจัดการหน้าต่างแบบ tiling ที่ควบคุมด้วยตนเองอย่าง i3 หรือ bspwm มาก
  • หากใช้ Emacs ภายใน i3, bspwm หรือ tmux ก็ย่อมอยากสลับไปมาระหว่างหน้าต่างภายในและภายนอก Emacs ด้วยคีย์ไบน์ดชุดเดียวกันอย่างเป็นธรรมชาติ
  • ตัวอย่างที่มีอยู่แล้วคือ i3-integration ของ Pavel Korytov และ emacs-wm.el สำหรับ qtile
  • อินเทอร์เฟซการผสานรวมที่สะอาดกว่านี้ควรมีองค์ประกอบต่อไปนี้
    • ตัวจัดการหน้าต่างต้องสามารถระบุคลาสของหน้าต่างที่กำลังทำงานอยู่ และต้องมีการย้าย/จัดการหน้าต่างแบบโปรแกรมได้
    • วิธีสื่อสารอาจเป็นคำสั่ง shell, socket, IPC แบบอิงเซิร์ฟเวอร์ หรือ D-Bus ของ Linux
    • ฝั่ง Emacs ต้องมีอินเทอร์เฟซที่ไม่ขึ้นกับวิธีสื่อสาร ซึ่งเลียนแบบการจัดการแบบตัวจัดการหน้าต่าง
    • เมื่อต้องสลับหน้าต่างของ OS ให้ตรวจสอบก่อนว่าหน้าต่างที่กำลังใช้งานคือ Emacs หรือไม่ และหากจำเป็น Emacs จะจัดการการควบคุมภายในเฟรมของตัวเอง

บทสรุป

  • การจัดการหน้าต่างของ Emacs มีตัวเลือกหลายชั้น ตั้งแต่ other-window ไปจนถึง ace-window, Avy, Popper และ display-buffer-alist
  • ไม่จำเป็นต้องใช้ทุกวิธี เพราะมีทางเลือกมากมายสำหรับแก้ปัญหาเดียวกัน จึงเลือกใช้เพียงอย่างหนึ่งและมองข้ามที่เหลือได้
  • เครื่องมือพื้นฐานที่มากับระบบมีแนวโน้มจะได้รับการดูแลต่อเนื่องยาวนาน แต่แพ็กเกจจากภายนอกอาจมีสถานะแตกต่างกันไปตามการพัฒนา การถูกปล่อยทิ้ง หรือการกลายเป็นโครงการกำพร้า
  • การจัดการหน้าต่างของ Emacs ไม่ได้ใกล้เคียงกับระบบตายตัวที่ซับซ้อน แต่คล้ายโครงสร้างแบบเปิดที่มอบวัตถุดิบและสูตรอาหารมากกว่า
  • ต่อให้ตั้งค่าเพียงเล็กน้อยก็ใช้งานพื้นฐานได้ และด้วยการผสมผสานเพิ่มอีกนิดพร้อมการปรับ Elisp ก็สามารถสร้างเวิร์กโฟลว์ที่ปรับให้เหมาะกับตนเองได้มากขึ้น

1 ความคิดเห็น

 
GN⁺ 2024-05-13
ความคิดเห็นจาก Hacker News
  • บทความดี และคำอธิบายกับแนวทางก็ยอดเยี่ยม บางครั้งหน้าต่างที่แบ่งเท่า ๆ กันอย่างเดียวก็ไม่พอ เลยใส่ฟังก์ชันด้านล่างไว้ใน init.el แล้ว bind กับ C-x 7 เพื่อปรับหน้าต่างปัจจุบันกับหน้าต่างคู่กันให้มีขนาดแนวตั้ง 70%/30%
    แนวนอนก็จัดการแบบเดียวกันด้วยการ bind กับ C-x 8

    (defun partial-size-window ()
    "Set the two split windows to 70% and 30% vertically."
    (interactive)
    (let ((size (- (truncate (* .70 (frame-height))) (window-height))))
    (if (> size 0)
    (enlarge-window size))))

    (defun partial-size-window-h ()
    "Set the two split windows to 70% and 30% horizontally."
    (interactive)
    (let ((size (- (truncate (* .70 (frame-width))) (window-width))))
    (if (> size 0)
    (enlarge-window-horizontally size))))

    • golden-ratio ก็ใช้เพื่อจุดประสงค์แบบนี้ได้เช่นกัน
  • เป็นการสำรวจที่ดี ตอนนี้อยู่บนมือถือเลยยังลองทันทีไม่ได้ แต่ก็อยากลองใช้ ace-window, ace-window-display-mode, frames-only-mode เร็ว ๆ
    ยอมรับตามตรงว่าใช้ Emacs มาราว 40 ปีแล้ว แต่ส่วนใหญ่ก็ใช้แค่ next-window, สร้างหน้าต่างใหม่ด้านล่างหรือด้านข้าง และพึ่ง tmux เวลาทำงานบนเซิร์ฟเวอร์ระยะไกลเท่านั้น

    • ผมก็คล้ายกัน ตลอด 30 ปีแทบใช้แค่ C-x 1/2/3, C-x o
      แต่จริง ๆ แล้วมันก็ทรงพลังพอสมควรและพอสำหรับการใช้งานของผม เลยรู้สึกว่า tiling window manager หลายตัวก็น่าจะพอแค่นั้นเหมือนกัน
    • ผมก็คล้ายกัน C-x o ค่อนข้างยุ่งยาก เลยตั้งค่าด้านล่างไว้ แต่ก็ไม่ได้รู้สึกว่าแพ็กเกจเสริมพวกนี้จำเป็นขนาดนั้น
      (global-set-key (kbd "M-o") #'other-window)
      ถึงอย่างนั้นการมีบทความอ้างอิงที่ลงลึกแบบนี้ก็ดี และงานเขียนของ Karthik ก็มักให้ความรู้และน่าสนใจเสมอ
  • ขอเสริมจากบันทึกของ Karthink เรื่อง windmove-mode ว่าสามารถตั้งค่า windmove-default-keybindings เพื่อย้ายระหว่างหน้าต่างด้วย Ctrl-<ปุ่มลูกศร> ได้
    (windmove-default-keybindings 'control)
    ผมใส่ไว้ใน config ของตัวเองด้วย และรวมไว้ใน starter kit แบบมินิมัลด้วย ซึ่งเป็น game changer จริง ๆ
    https://codeberg.org/ashton314/emacs-bedrock

    • พอเช็ก .emacs ของผมแล้วพบว่าใช้เหมือนกันเป๊ะ ชอบแบบนี้มากกว่า C-x O เพราะกดน้อยลงหนึ่งคีย์และยืดหยุ่นกว่า
  • ตั้งค่าไว้ไม่ให้ Emacs แบ่ง frame ออกเป็นหลายหน้าต่าง คล้ายกับที่เว็บเบราว์เซอร์ไม่ได้แบ่งหน้าต่างเบราว์เซอร์ออกเป็น viewport หลายอัน
    Emacs ของผมไม่สร้าง frame ที่สองด้วย วิธีทำเปลี่ยนไปทุกปี เพราะวิธีเก่าเลิกทำงานไปเรื่อย ๆ
    ด้วยวิธีปัจจุบัน ในบางสถานการณ์ frame จะถูกแบ่ง เช่นตอนที่ Emacs แสดงบัฟเฟอร์ *Completions* แต่เท่าที่จำได้ ผมไม่เคยต้องเรียก delete-window เองเพื่อปิดหน้าต่างที่สอง และมันจะหายไปเองเสมอ

    • เว็บเบราว์เซอร์ก็แบ่งหน้าต่างแบบนั้นได้จริง ถ้าเป็นเบราว์เซอร์อย่าง Vivaldi ที่ให้ดูหลายหน้าได้พร้อมกัน
    • สงสัยว่าการตั้งค่าไม่ให้แบ่ง frame เป็นหลายหน้าต่างนั้นทำเพื่อแก้ปัญหาเฉพาะอะไรหรือเปล่า
      ผมใช้ frame แบบหลายหน้าต่างค่อนข้างเยอะ บน macOS รุ่นใหม่กับ Emacs รุ่นใหม่ บางครั้ง frame ที่มีหลายหน้าต่างดูเหมือนค้างและไม่ตอบสนองต่อคีย์บอร์ดหรือแทร็กแพด แม้กด Ctrl-G หลายครั้งก็ยังไม่หาย
      แต่การสร้าง frame ใหม่ด้วย C-x 5 2 ยังทำงานได้ และเมื่อกลับไปยัง frame ที่ค้าง ก็สามารถปิด frame นั้นด้วย C-x 5 0 ได้
  • บทความลงลึกมาก และชอบจริง ๆ ที่ผู้เขียนใช้เวลาในการวาด แผนภาพ ดี ๆ

  • เป็นบทความที่ดี และอยากแชร์แพ็กเกจยอดเยี่ยมที่ใช้คู่กันได้ สำหรับผม zygospore เป็นเครื่องมือที่ขาดไม่ได้
    “zygospore ทำให้สามารถกด C-x 1 (delete-other-window) แล้วกด C-x 1 อีกครั้งเพื่อย้อนกลับได้”
    https://github.com/LouisKottmann/zygospore.el

    • ถ้าต้องการทำซ้ำหรือย้อนกลับการเปลี่ยนแปลงการจัดวางหน้าต่างทั้งหมด ก็น่าดู winner mode
      https://www.emacswiki.org/emacs/WinnerMode
    • ตามอุดมคติแล้วมันควรเป็น undo ไม่ใช่หรือ
      ถึงจะแปลกหน่อย แต่ถ้าเป็น C-u C-x 1 ก็ดูสอดคล้องดี
  • ถ้าผู้เขียนเห็นสิ่งนี้ อยากบอกว่าดีใจที่มีการกล่าวถึง Acme editor นิดหน่อย

  • เวลาเรียก hledger-balance-sheet อยากให้มันเปิดทางขวาด้วย ความกว้าง 30% โดยไม่แทนที่ .hledger.journal เดิม
    และอยากให้ .hledger.journal ยังคงเปิดอยู่ทางซ้ายและยังเป็นหน้าต่างที่ถูกเลือกอยู่