วิธีการใช้เมนูป๊อปอัพคล้ายกับที่ใช้ใน Magit


10

คำถาม

ผมอยากจะสร้างส่วนติดต่อผู้ใช้ในรูปแบบของเมนูป๊อปอัพ , เมนูป๊อปอัพที่คล้ายกันกับที่ใช้ใน Magit

คุณสมบัติ

ความหมายของป๊อปอัพ

ป๊อปอัปในบริบทของคำถามนี้หมายถึงหน้าต่างชั่วคราวขนาดเล็กที่มีการรวบรวมรายการเมนูเพื่อให้ผู้ใช้สามารถเลือกหนึ่งและหนึ่งในรายการเหล่านี้

ตำแหน่งบนหน้าจอ

ป๊อปอัพได้รับอนุญาตให้ปรากฏในส่วนใด ๆ ของหน้าจอ แต่เป็นที่พึงปรารถนาที่ควรจะค่อนข้างชัดเจนและดังนั้นจึงควรปรากฏขึ้นข้างหน้าต่างที่ใช้งานในปัจจุบัน

เนื้อหาของ Popup Buffer

รายการควรจะแสดงในรูปแบบของตารางสวย พริตตี้เป็นบริบทของคำถามหมายถึงการดึงดูดสายตาผลกระทบนี้สามารถทำได้ง่ายที่สุดโดยการวางรายการเมนูลงในแถวตรงดู complete--insert-stringตัวอย่าง ย่อหน้านี้ทำหน้าที่ชี้แจงเพิ่มเติมคุณสามารถทำได้ด้วยวิธีของคุณเองซึ่งจะไม่ทำให้คำตอบของคุณไม่ถูกต้อง

การเลือกรายการเมนู

การเลือกนั้นคาดว่าจะทำได้ด้วยการกดปุ่มเพียงปุ่มเดียวหรือเลือกด้วยเมาส์ (แม้ว่าจะไม่สำคัญนักดังนั้นคำตอบที่มีข้อเสนอที่ไม่สนับสนุนเมาส์นั้นถูกกฎหมาย) หากคุณเสนอวิธีแก้ปัญหาที่รองรับเมาส์โปรดทราบว่าผู้ใช้ควรสามารถเลือกรายการเมนูในวิธีที่ใช้งานง่ายนั่นคือโดยการคลิกปุ่มซ้ายบนตัวเลือกที่ต้องการ

สามารถใช้เม้าส์ NB ได้หลายวิธีและใช้เป็นทางเลือกเพื่อระบุทางเลือกที่ได้รับการต้อนรับ

กำจัดป๊อปอัพ

เมื่อผู้ใช้เลือกรายการเมนูตามวิธีที่อธิบายไว้ข้างต้นบัฟเฟอร์และหน้าต่างควรถูกกำจัดออกจากมุมมองรวมถึงการฆ่า หน้าต่างที่ใช้งานอยู่ก่อนที่จะเรียกใช้เมนูป๊อปอัพควรได้รับการโฟกัส (นั่นคือเปิดใช้งาน) อีกครั้ง

มูลค่าที่คืนและข้อโต้แย้ง

โดยเฉพาะอย่างยิ่งผลลัพธ์ของการดำเนินการนี้ควรส่งผลให้วัตถุ Lisp ถูกส่งคืน วัตถุ Lisp สามารถเป็นได้ทั้ง:

  • nil- สิ่งนี้บ่งชี้ว่าผู้ใช้ยกเลิกเมนูป๊อปอัปโดยกดC-gหรือด้วยวิธีอื่น†

  • string- สตริง (อนุญาตให้ใช้สัญลักษณ์) ควรเป็นstring-equal หนึ่งในสตริงที่ให้กับเมนูป๊อปอัพเป็นการรวบรวมรายการจริง

ทางเลือกอื่นเพื่อให้โปรแกรมส่วนที่เหลือทราบทางเลือกของผู้ใช้หรืออาจเป็นไปได้ว่ายอมรับได้ อย่างไรก็ตามหากยังไม่ชัดเจนว่าจะสามารถปฏิบัติได้อย่างไรฉันจะถามผู้ตอบคำถามทั้งหมดโดยทันทีและไม่ขอให้ฉันชี้แจงเพิ่มเติมในเรื่องนี้

นี่คือทั้งหมดสำหรับค่าส่งคืน สำหรับพารามิเตอร์อินพุตอย่างน้อยพวกเขาควรจะรวมชุดของสตริงที่แสดงถึงตัวเลือกที่เป็นไปได้ (นั่นคือรายการเมนู)

คำตอบที่ยอมรับได้

คำตอบที่คาดหวังอาจอยู่ในรูปแบบต่อไปนี้:

  • ตัวอย่างโค้ดที่เพียงพอที่ช่วยให้ผู้อ่านที่มีการศึกษาสามารถเขียนฟังก์ชันตามที่อธิบายไว้ข้างต้น มันไม่ได้คาดหวังหรือจำเป็นต้องเขียนฟังก์ชั่นการทำงานทั้งหมด อย่างไรก็ตามเพื่อหลีกเลี่ยงความไม่แน่นอน (สามารถตัดส่วนของรหัสจำนวนมากได้หรือไม่) ฉันควรทราบว่าส่วนข้อมูลที่ขาดหายไปของข้อมูลโค้ดควรอธิบายไว้ในองค์ประกอบของคำตอบ

  • ลิงก์ไปยังไลบรารีที่มีอยู่ซึ่งใช้ฟังก์ชันการทำงานที่คล้ายกัน เพื่อหลีกเลี่ยงความไม่แน่นอนฉันควรทราบว่าคล้ายกันในกรณีของเราหมายความว่าห้องสมุดสามารถใช้ในการสร้างป๊อปอัพ (ดูคำนิยามด้านบน) ที่มีคุณสมบัติอย่างน้อย 2 หรือ 3 ที่อธิบายไว้ข้างต้น หากห้องสมุดที่เสนอนั้นแตกต่างจากจุดที่ไม่สามารถบรรลุเงื่อนไขที่ระบุไว้ก่อนหน้านี้แต่ละกรณีจะได้รับการตัดสินอย่างอิสระและจะถูกอัปเดตตลอดเวลาหาก OP เห็นว่าเป็นประโยชน์

  • คำอธิบายของฟังก์ชั่น Emacs ในตัวหรือฟังก์ชั่นของบุคคลที่สามที่สามารถใช้ในการใช้งานคุณสมบัติใด ๆ ที่อธิบายไว้ในส่วน«คุณสมบัติ»ดูด้านบน หลีกเลี่ยงความไม่แน่นอนโปรดรัฐอย่างชัดเจนว่าคำตอบของคุณจะมีประโยชน์สำหรับผู้อ่านในอนาคตที่ต้องการที่จะใช้ป๊อปอัพ , เมนูป๊อปอัพที่คล้ายกันกับที่ใช้ใน Magit


†วิธีอื่นในการยกเลิกเมนูป๊อปอัปอาจรวมถึงสิ่งต่อไปนี้ (แต่ไม่ จำกัด เฉพาะสิ่งเหล่านี้):

  • คลิกนอกหน้าต่างเมนูป๊อปอัพ

  • การฆ่าบัฟเฟอร์ที่มีป๊อปอัพโดยไม่ทำการเลือก

คำตอบ:


8

magit-popupมีคู่มือของตัวเอง ! แต่ถ้าคุณไม่ต้องการตั้งค่าอาร์กิวเมนต์ในป๊อปอัพซึ่งถูกส่งผ่านไปยังการกระทำที่เรียกใช้แล้วคุณน่าจะใช้hydraแทนได้ดีกว่า

magit-popupนอกจากนี้ทราบว่าฉันไม่ได้ตั้งใจที่จะพัฒนา แต่ฉันจะเขียนแพ็คเกจที่คล้ายกันตั้งแต่เริ่มต้น มีความซับซ้อนโดยไม่ตั้งใจมากในการใช้งานปัจจุบัน (แต่นั่นไม่ได้หมายความว่าmagit-popupจะหายไปมันอาจจะไม่เห็นคุณสมบัติใหม่มากมาย แต่ถ้าการใช้งานปัจจุบันทำในสิ่งที่คุณต้องการแล้วทำไมไม่ใช้มัน?)

หรือเนื่องจากคุณต้องการที่จะทำ "ตั้งแต่เริ่มต้น" ลองดูread-char-choiceและไปจากที่นั่น ในการใช้ส่วนที่เป็นภาพให้ดูที่lvซึ่งเป็นส่วนหนึ่งของที่เก็บไฮดรา


สำหรับผู้อ่านอื่น ๆ ทราบว่าได้ดำเนินการตาม @tarsius magit-popupผ่านและเขียนว่าแทน แพคเกจใหม่ที่เรียกว่าและนี่คือสิ่งที่จะใช้ในรุ่นปัจจุบันของtransient magitดูmagit.vc/manual/transientสำหรับเอกสารประกอบ
phils

5

ไฮดรานั้นเขียนง่าย:

(defhydra hydra-launcher (:color blue :columns 2)
   "Launch"
   ("h" man "man")
   ("r" (browse-url "http://www.reddit.com/r/emacs/") "reddit")
   ("w" (browse-url "http://www.emacswiki.org/") "emacswiki")
   ("s" shell "shell")
   ("q" nil "cancel"))

(global-set-key (kbd "C-c r") 'hydra-launcher/body)

ไฮดราเป็นอินเทอร์เฟซที่ใช้แป้นพิมพ์เป็นศูนย์กลางและในรูปแบบพื้นฐานมันไม่ได้ยากกว่าeasy-menu-define(ในตัว) และมันก็ยืดออกได้มากถ้าคุณต้องการทำให้มันซับซ้อนกว่านี้

เพียงดูที่อินเทอร์เฟซตัวสั่นด้วยความตื่นเต้นหน้าต่างด้านล่างเป็นไฮดราแบบกำหนดเองไม่ยากที่จะเขียนมากไปกว่าอินเทอร์เฟซข้างบน:

Hydra-twittering.png

รหัสสำหรับสิ่งนี้มีอยู่ในวิกิรวมถึงตัวอย่างอีกมากมาย


เยี่ยมมากฉันจะหาเวลาเรียนรู้เพิ่มเติมเกี่ยวกับสิ่งนี้อย่างแน่นอน!
Mark Karpov

1

หลังจากการวิจัยบางอย่างฉันพบสำนวนที่สามารถใช้ในการสร้างหน้าต่างที่ด้านล่าง (หรือที่ใดก็ได้) ของหน้าต่างที่ใช้งานในปัจจุบัน สิ่งนี้มีผลกับหน้าต่างเสริมชั่วคราว:

(let ((buffer (get-buffer-create "*Name of Buffer*")))
  (with-current-buffer buffer
    (with-current-buffer-window
     ;; buffer or name
     buffer
     ;; action (for `display-buffer')
     (cons 'display-buffer-below-selected
           '((window-height . fit-window-to-buffer)
             (preserve-size . (nil . t))))
     ;; quit-function
     (lambda (window _value)
       (with-selected-window window
         (unwind-protect
             ;; code that gets user input
           (when (window-live-p window)
             (quit-restore-window window 'kill)))))
     ;; Here we generate the popup buffer.
     (setq cursor-type nil)
     ;; …
     )))

คุณสามารถเล่นกับactionอาร์กิวเมนต์ได้with-current-buffer-windowถ้าคุณต้องการให้ป๊อปอัปปรากฏในส่วนต่าง ๆ ของหน้าจอ

ฟังก์ชันออกจากอธิบายไว้ใน doc-string with-current-buffer-windowซึ่งสามารถใช้เพื่อรับอินพุตจากผู้ใช้ ตามที่ @tarsius แนะนำให้ read-char-choiceเป็นผู้สมัครที่ดีสำหรับการทดสอบ

บัฟเฟอร์ป๊อปอัพตัวเองสามารถสร้างเช่นเดียวกับบัฟเฟอร์อื่น ๆ ฉันยังนึกถึงปุ่มอยู่เพราะผู้ใช้สามารถใช้เมาส์เพื่อเลือกรายการในเมนูป๊อปอัพไม่ใช่แค่แป้นพิมพ์ อย่างไรก็ตามสิ่งนี้ต้องใช้ความพยายามเพิ่มเติมหากคุณต้องการทำมันให้ดี

หากป๊อปอัพของคุณเป็นบิต ad-hoc และคุณไม่จำเป็นต้องใช้มันมากกว่าหนึ่งครั้งคุณสามารถได้รับไปด้วยโซลูชั่นนี้ completion--insert-stringsนอกจากจะดูที่ ฉันพบว่ามันค่อนข้างมีประโยชน์เป็นตัวอย่างของวิธีการสร้างแถวของรายการเมนูคุณสามารถใช้มันไม่เปลี่ยนแปลงถ้าคุณไม่ต้องการอะไรเป็นพิเศษ


4
ฉันคิดว่าบางทีคำถามที่คุณมีในหัวก็ดี คำถามที่คุณเขียนค่อนข้างชัดเจนฉันไม่เห็นว่าใครจะรู้จักคุณหลังจากผ่านคำตอบเช่นนี้
npostavs

1

นี่คือวิธีการแก้ปัญหาของบุคคลที่ 3 โดยใช้Icicles

  • รหัสของคุณจะปรากฏขึ้นที่หน้าต่างโดยผู้สมัครได้รับการจัดวางอย่างเป็นเมนู สำหรับวิธีดูด้านล่าง

  • คุณนำหน้าแต่ละรายการเมนูด้วยอักขระที่ไม่ซ้ำกัน เช่น a, b, c ... หรือ 1, 2, 3 ... ผู้ใช้กดถ่านเพื่อเลือกรายการ

  • completing-readคุณผูกตัวแปรไม่กี่รอบการเรียกไปยัง คุณผ่านรายการของรายการเมนูของคุณ RETคุณเลือกที่จะระบุหนึ่งในรายการที่จะเริ่มต้นได้รับการแต่งตั้งถ้าผู้ใช้เพียงแค่ฮิต

    การผูกตัวแปรบอกcompleting-readให้:

    • แสดงรายการเมนูแต่ละรายการในแถวแยกต่างหาก
    • แสดงเมนูทันที
    • อัปเดตทันทีเมื่อผู้ใช้กดปุ่ม
    • ส่งคืนทันทีหากอินพุตของผู้ใช้ตรงกับหนึ่งรายการ
    • แสดงตัวเลือกเริ่มต้นในพรอมต์
  • คุณสามารถทำให้รายการเมนูเป็นแฟนซีตามที่คุณต้องการ (ไม่แสดง)

    • ใบหน้า
    • ภาพ
    • คำอธิบายประกอบ
    • หลายบรรทัดต่อรายการ
    • mouse-3 เมนูป๊อปอัพที่จะทำอะไรกับรายการ
(defvar my-menu '(("a: Alpha It"   . alpha-action)
                  ("b: Beta It"    . beta-action)
                  ("c: Gamma It"   . gamma-action)
                  ("d: Delta It"   . delta-action)
                  ("e: Epsilon It" . epsilon-action)
                  ("f: Zeta It"    . zeta-action)
                  ("g: Eta It"     . eta-action)
                  ("h: Theta It"   . theta-action)
                  ("i: Iota It"    . iota-action)
                  ("j: Kappa It"   . kappa-action)
                  ("k: Lambda It"  . lambda-action)))

(defun my-popup ()
  "Pop up my menu.
User can hit just the first char of a menu item to choose it.
Or s?he can click it with `mouse-1' or `mouse-2' to select it.
Or s?he can hit RET immediately to select the default item."
  (interactive)
  (let* ((icicle-Completions-max-columns               1)
         (icicle-show-Completions-initially-flag       t)
         (icicle-incremental-completion-delay          0.01)
         (icicle-top-level-when-sole-completion-flag   t)
         (icicle-top-level-when-sole-completion-delay  0.01)
         (icicle-default-value                         t)
         (icicle-show-Completions-help-flag            nil)
         (choice  (completing-read "Choose: " my-menu nil t nil nil
                                   "b: Beta It")) ; The default item.
         (action  (cdr (assoc choice my-menu))))

    ;; Here, you would invoke the ACTION: (funcall action).
    ;; This message just shows which ACTION would be invoked.
    (message "ACTION chosen: %S" action) (sit-for 2)))

คุณยังสามารถสร้างเมนูแบบปรนัยได้อย่างแท้จริงซึ่งก็คือเมนูที่ผู้ใช้สามารถเลือกได้หลายรายการพร้อมกัน (เลือกชุดย่อยของตัวเลือกที่เป็นไปได้)


1

makeyนอกจากนี้คุณอาจจะสนใจในการมองหาที่แพคเกจ มันมีจุดมุ่งหมายเพื่อให้การทำงานที่คล้ายกันmagit-popupและมันเป็นทางแยกของบรรพบุรุษของmagit-popup( magit-key-mode, mentionned ในความคิดเห็น) magitเมื่อมันยังไม่เป็นแพคเกจที่มีอยู่แยกต่างหากจาก

ให้ฉันพูดถึงdiscover: นั่นเป็นตัวอย่างของวิธีการใช้makey(และ raison d'être) แพ็คเกจนั้นมีไว้เพื่อช่วยผู้มาใหม่ค้นพบการผูก emacs


อ้างอิง


2
@ มาร์คฉันไม่แน่ใจว่าทำไมคุณยอมรับคำตอบนี้ มันไม่ได้พูดถึงการสร้างบล็อคขนาดเล็กซึ่งถ้าฉันจำได้อย่างถูกต้องเป็นสิ่งที่คุณหลังจาก มันยังไม่ถูกต้อง: makey ไม่ใช่ทางแยกของ magit-popup มันเป็นทางแยกของโหมด magit-key-บรรพบุรุษรุ่นก่อน มันสืบทอดการขาดดุลส่วนใหญ่ที่ได้รับการแก้ไขใน magit-popup ดังนั้นคุณจะดีกว่ามากในการใช้ magit-popup หรือไฮดรา (หรือเขียนของคุณเอง)
tarsius

@tarsius การตัดสินความถูกต้องของคำตอบนั้นยาก หากคุณรู้ว่ามันไม่ถูกต้องคุณสามารถแก้ไขได้ ฉันทำการแก้ไขฉันยังไม่แน่ใจ 100% ว่าถูกต้องแล้ว ฉันไม่ได้พูดถึง "การขาดดุล" ที่สืบทอดมาเพราะฉันไม่รู้ว่ามันคืออะไร
YoungFrog


0

ตามคำตอบของ @ Drew (Icicles) พร้อมการปรับเปลี่ยนการใช้งานเมนูแยกต่างหากจากข้อมูลเมนู - เพื่อให้สามารถกำหนดเมนูได้หลายเมนูโดยแต่ละรายการมี: (เนื้อหาพร้อมท์ค่าเริ่มต้น)

ตัวเลือกนี้ใช้ ivy (เมื่อมี) ซึ่งมีคุณสมบัติขั้นสูงเช่นความสามารถในการเปิดใช้งานรายการในขณะที่เปิดเมนูอยู่

ป๊อปอัปทั่วไป

(defun custom-popup (my-prompt default-index my-content)
  "Pop up menu
Takes args: my-prompt, default-index, my-content).
Where the content is any number of (string, function) pairs,
each representing a menu item."
  (cond
    ;; Ivy (optional)
    ((fboundp 'ivy-read)
      (ivy-read my-prompt my-content
        :preselect
        (if (= default-index -1) nil default-index)
        :require-match t
        :action
        (lambda (x)
          (pcase-let ((`(,_text . ,action) x))
            (funcall action)))
        :caller #'custom-popup))
    ;; Fallback to completing read.
    (t
      (let ((choice (completing-read
                     my-prompt my-content nil t nil nil
                     (nth default-index my-content))))
        (pcase-let ((`(,_text . ,action) (assoc choice my-content)))
          (funcall action))))))

ตัวอย่างการใช้งานกำหนดการดำเนินการบางอย่างให้กับปุ่ม f12:

(defvar
  my-global-utility-menu-def
  ;; (content, prompt, default_index)
  '(("Emacs REPL" . ielm)
    ("Spell Check" . ispell-buffer)
    ("Delete Trailing Space In Buffer" . delete-trailing-whitespace)
    ("Emacs Edit Init" . (lambda () (find-file user-init-file))))

(defun my-global-utility-popup ()
  "Pop up my menu. Hit RET immediately to select the default item."
  (interactive)
  (custom-popup my-global-utility-menu-def "Select: ", 1))

(global-set-key (kbd "<f12>") 'my-global-utility-popup)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.