Cách triển khai Menu Popup Tương tự như được sử dụng trong Magit


10

Câu hỏi

Tôi muốn tạo giao diện người dùng ở dạng menu bật lên , menu bật lên tương tự như được sử dụng trong Magit .

Đặc trưng

Định nghĩa của Popup

Popup trong ngữ cảnh của câu hỏi này có nghĩa là cửa sổ nhỏ tạm thời chứa bộ sưu tập các mục menu để người dùng có thể chọn một và chỉ một trong những mục này.

Vị trí trên màn hình

Cửa sổ bật lên được phép xuất hiện ở bất kỳ phần nào của màn hình, nhưng điều mong muốn là nó phải khá rõ ràng và do đó nó sẽ xuất hiện bên cạnh cửa sổ hiện đang hoạt động.

Nội dung của bộ đệm Popup

Các mục nên được hiển thị dưới dạng bảng đẹp. Khá là bối cảnh của câu hỏi có nghĩa là hấp dẫn trực quan, hiệu ứng này có thể dễ dàng đạt được nhất bằng cách đặt các mục menu thành các hàng thẳng, xem complete--insert-stringví dụ. Đoạn này phục vụ để làm rõ thêm, bạn có thể làm theo cách riêng của mình, điều này sẽ không khiến câu trả lời của bạn không chính xác.

Lựa chọn các mục menu

Việc lựa chọn dự kiến ​​sẽ được thực hiện bằng một lần bấm phím hoặc tùy chọn bằng chuột (mặc dù điều đó không quan trọng nên câu trả lời có chứa các đề xuất không hỗ trợ chuột là hợp pháp). Nếu bạn đề xuất giải pháp hỗ trợ chuột, xin lưu ý rằng người dùng sẽ có thể chọn một mục menu theo cách trực quan, nghĩa là, bằng cách nhấp chuột trái vào lựa chọn mong muốn.

Chuột NB có thể được sử dụng theo nhiều cách và các cách khác nhau để chỉ ra lựa chọn cũng được hoan nghênh.

Loại bỏ Popup

Khi người dùng đã chọn một mục menu theo cách được mô tả ở trên, bộ đệm và do đó, cửa sổ của nó sẽ bị loại khỏi chế độ xem cũng như bị giết. Cửa sổ đã được kích hoạt trước khi gọi menu bật lên sẽ lấy lại tiêu điểm (nghĩa là trở nên hoạt động).

Giá trị trả về và đối số

Tốt hơn là, hậu quả của các hành động này sẽ dẫn đến một đối tượng Lisp được trả về. Đối tượng Lisp có thể là:

  • nil- điều này cho thấy rằng người dùng đã hủy bỏ menu bật lên bằng cách nhấn C-ghoặc theo một cách khác.

  • string- chuỗi (được phép sử dụng ký hiệu) phải là string-equal một trong các chuỗi được cung cấp cho menu bật lên dưới dạng tập hợp các mục thực tế.

Các cách khác để cho phần còn lại của chương trình biết lựa chọn của người dùng, hoặc, có thể, sự vắng mặt của nó, được chấp nhận. Tuy nhiên, nếu không rõ nó có thể được thực hiện như thế nào thì tôi yêu cầu tất cả những người trả lời ứng biến và đừng hỏi tôi để làm rõ hơn về khía cạnh này.

Đây là tất cả cho giá trị trả lại. Đối với các tham số đầu vào, ít nhất chúng phải bao gồm tập hợp các chuỗi đại diện cho các lựa chọn có thể (nghĩa là các mục menu).

Câu trả lời chấp nhận được

Câu trả lời dự kiến ​​có thể có các dạng sau:

  • Đoạn mã đủ cho phép người đọc có giáo dục viết chức năng như mô tả ở trên; nó không được mong đợi hoặc không cần thiết để viết toàn bộ chức năng làm việc. Tuy nhiên, để tránh sự không chắc chắn (có thể bỏ qua các phần đáng kể của mã không?), Tôi cần lưu ý rằng các phần còn thiếu của đoạn mã cần được mô tả trong thành phần văn bản của câu trả lời.

  • Một liên kết đến thư viện hiện có thực hiện chức năng tương tự. Để tránh sự không chắc chắn, tôi nên lưu ý rằng tương tự trong trường hợp của chúng tôi có nghĩa là thư viện có thể được sử dụng để tạo cửa sổ bật lên (xem định nghĩa ở trên) có ít nhất 2 hoặc 3 tính năng được mô tả ở trên. Nếu thư viện đề xuất khác với điểm không thể đáp ứng điều kiện đã nêu trước đó, mỗi trường hợp như vậy sẽ được đánh giá độc lập và sẽ luôn được nâng cấp nếu OP thấy nó hữu ích.

  • Mô tả các chức năng Emacs tích hợp hoặc các chức năng của bên thứ ba có thể được sử dụng để triển khai bất kỳ tính năng nào được mô tả trong phần «Tính năng», xem bên trên. Để tránh sự không chắc chắn, vui lòng nêu rõ cách trả lời của bạn có thể hữu ích cho những độc giả tương lai muốn thực hiện cửa sổ bật lên , menu bật lên tương tự như được sử dụng trong Magit .


Các cách khác để hủy bỏ menu bật lên có thể bao gồm những điều sau đây (nhưng không giới hạn ở những cách này):

  • nhấp vào bên ngoài cửa sổ menu bật lên;

  • giết chết bộ đệm chứa cửa sổ bật lên mà không có sự lựa chọn.

Câu trả lời:


8

magit-popuphướng dẫn sử dụng riêng ! Nhưng trừ khi bạn thực sự muốn đặt đối số trong cửa sổ bật lên sau đó được chuyển sang hành động được gọi, bạn có thể sử dụng hydrathay thế tốt hơn .

Cũng lưu ý rằng tôi không có ý định phát triển hơn nữa magit-popup. Thay vào đó tôi sẽ viết một gói tương tự từ đầu. Có rất nhiều sự phức tạp tình cờ trong việc thực hiện hiện tại. (Nhưng điều đó không có nghĩa là nó magit-popupsẽ biến mất. Nó có thể sẽ không thấy nhiều tính năng mới, nhưng nếu việc triển khai hiện tại làm những gì bạn muốn, thì tại sao không sử dụng nó?)

Hoặc vì bạn cũng muốn làm điều đó "từ đầu", hãy xem read-char-choicevà đi từ đó. Để thực hiện phần trực quan, hãy xem lvđó là một phần của kho hydra.


Đối với những người đọc khác: Lưu ý rằng @tarsius đã theo dõi và viết thay thế cho magit-popup. Gói mới được gọi transientvà đây là gói được sử dụng trong các phiên bản hiện tại của magit. Xem magit.vc/manual/transient để biết tài liệu.
phils

5

Hydras khá đơn giản để viết:

(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)

Hydra là một giao diện tập trung vào bàn phím và ở dạng cơ bản, nó không khó hơn easy-menu-define(tích hợp). Và nó khá mở rộng nếu bạn muốn làm cho nó làm những việc phức tạp hơn.

Chỉ cần nhìn vào giao diện twitter này, cửa sổ phía dưới là một Hydra tùy chỉnh, không khó viết hơn nhiều so với giao diện trên:

hydra-twittering.png

Mã cho điều này có sẵn trên wiki , cùng với rất nhiều ví dụ khác.


Tuyệt vời, tôi chắc chắn sẽ tìm thấy thời gian để tìm hiểu thêm về điều này!
Đánh dấu Karpov

1

Sau một số nghiên cứu, tôi đã tìm thấy một thành ngữ có thể được sử dụng để tạo một cửa sổ ở phía dưới (hoặc thực sự là bất cứ nơi nào) của cửa sổ hiện đang hoạt động. Điều này tự nó có hiệu lực của cửa sổ phụ, tạm thời:

(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)
     ;; …
     )))

Bạn có thể chơi một chút với actionlập luận with-current-buffer-windownếu bạn muốn cửa sổ bật lên xuất hiện ở phần khác của màn hình.

Hàm thoát được mô tả trong chuỗi doc của with-current-buffer-window, nó có thể được sử dụng để nhận đầu vào từ người dùng. Như @tarsius đề xuất, read-char-choicelà một ứng cử viên tốt để thử nghiệm.

Bộ đệm bật lên có thể được tạo giống như bất kỳ bộ đệm khác. Tôi vẫn đang nghĩ về các nút ở đó, bởi vì người dùng có thể sử dụng chuột để chọn một mục trong menu bật lên, không chỉ bàn phím. Tuy nhiên, điều này đòi hỏi nỗ lực bổ sung nếu bạn muốn làm tốt.

Nếu cửa sổ bật lên của bạn hơi đặc biệt và bạn sẽ không cần nó nhiều hơn một lần, bạn có thể thoát khỏi giải pháp này. Ngoài ra, hãy xem completion--insert-strings. Tôi thấy nó khá hữu ích như một ví dụ về cách tạo ra các hàng menu đẹp, thậm chí bạn có thể sử dụng nó không thay đổi nếu bạn không cần thứ gì đó đặc biệt.


4
Tôi nghĩ có lẽ câu hỏi bạn có trong đầu là tốt; câu hỏi bạn viết ra khá không rõ ràng, tôi không thấy bất cứ ai có thể biết bạn sau câu trả lời như thế này.
npostavs

1

Đây là giải pháp của bên thứ 3, sử dụng Icicles .

  • Mã của bạn bật lên một cửa sổ với các ứng cử viên được sắp xếp gọn gàng như một menu. Để biết làm thế nào, xem bên dưới.

  • Bạn tiền tố mỗi mục menu với một ký tự duy nhất. Ví dụ: a, b, c ... hoặc 1, 2, 3 ... Người dùng nhấn char để chọn vật phẩm.

  • Bạn liên kết một vài biến xung quanh một cuộc gọi đến completing-read. Bạn vượt qua nó một danh sách các mục menu của bạn. Bạn tùy ý chỉ định một trong các mục là mặc định, được chọn nếu người dùng chỉ cần nhấn RET.

    Các ràng buộc biến nói completing-readvới:

    • Hiển thị từng mục menu trên một hàng riêng biệt
    • Hiển thị menu ngay lập tức
    • Cập nhật nó ngay lập tức khi người dùng nhấn phím
    • Trả lại ngay lập tức, nếu đầu vào của người dùng chỉ khớp với một mục
    • Hiển thị lựa chọn mặc định trong lời nhắc.
  • Bạn có thể làm cho các mục menu là lạ mắt như bạn muốn (không hiển thị).

    • khuôn mặt
    • hình ảnh
    • chú thích
    • nhiều dòng trên mỗi mục
    • mouse-3 menu bật lên, để làm bất cứ điều gì với các mục
(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)))

Bạn cũng có thể tạo các menu thực sự nhiều lựa chọn , nghĩa là các menu mà người dùng có thể chọn nhiều mục cùng một lúc (chọn một tập hợp con của các lựa chọn có thể).


1

Bạn cũng có thể quan tâm đến việc xem xét các gói makey. Nó được dự định cung cấp chức năng tương tự như magit-popup, và nó là một nhánh của tiền thân của magit-popup( magit-key-mode, được đề cập trong bình luận) từ khi nó chưa phải là một gói có sẵn riêng biệt magit.

Tôi cũng đề cập đến discover: đó là một ví dụ về cách sử dụng makey(và cũng là raison d'être). Gói đó là để giúp người mới khám phá các ràng buộc emacs.


Người giới thiệu


2
@Mark Tôi không chắc tại sao bạn lại chấp nhận câu trả lời này. Nó hoàn toàn không đề cập đến các khối xây dựng nhỏ, mà nếu tôi nhớ chính xác, đó là những gì bạn đã theo đuổi. Điều đó cũng không đúng: makey không phải là một nhánh của cửa sổ bật lên, đó là một nhánh của chế độ khóa ma thuật tiền nhiệm. Nó thừa hưởng hầu hết các thâm hụt đã được giải quyết trong magit-popup. Vì vậy, bạn tốt hơn nhiều khi sử dụng magit-popup hoặc hydra (hoặc viết của riêng bạn).
tarsius

@tarsius Đánh giá tính đúng đắn của một câu trả lời có thể khó. Nếu bạn biết nó không chính xác, vui lòng chỉnh sửa nó. Tôi đã thực hiện một chỉnh sửa, tôi vẫn không chắc chắn 100% là chính xác. Tôi cũng không đề cập đến "thâm hụt" mà nó thừa hưởng vì tôi không biết chúng là gì.
YoungFrog

0

Guide-Key hoạt động tốt đối với tôi đối với tất cả các tiền tố được cấu hình.


0

Dựa trên câu trả lời của @ Drew (Icicles), với các sửa đổi để tách thực hiện menu khỏi dữ liệu menu - vì vậy có thể xác định nhiều menu, mỗi menu có một: (nội dung, lời nhắc, mặc định).

Tùy chọn này sử dụng ivy (khi có sẵn), có các tính năng nâng cao hơn như có thể kích hoạt các mục trong khi vẫn mở menu.

Cửa sổ bật lên chung chung.

(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))))))

Ví dụ sử dụng, gán một số hành động cho phím 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)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.