Magit에서 사용되는 것과 유사한 팝업 메뉴를 구현하는 방법


10

질문

Magit에서 사용되는 것과 비슷한 팝업 메뉴 , 팝업 메뉴의 형태로 사용자 인터페이스를 만들고 싶습니다 .

풍모

팝업의 정의

이 질문의 맥락에서 팝업 은 사용자가 이러한 항목 중 하나만 선택할 수 있도록 메뉴 항목 모음이 포함 된 임시 창을 거의 의미하지 않습니다.

화면상의 위치

팝업은 화면의 어느 부분 에나 표시 될 수 있지만, 명확해야하고 현재 활성화 된 창 옆에 표시되는 것이 바람직합니다.

팝업 버퍼의 내용

항목은 예쁜 테이블 형태로 표시되어야합니다. 시각적 호소력 질문 수단의 상황이며, 이러한 효과는 쉽게 직선 열로 메뉴 항목을 넣어 달성 할 수있다 참조 complete--insert-string, 예를 들어. 이 단락은 추가 설명을 위해 제공되며, 자신의 방식으로 할 수 있으므로 대답이 잘못되지 않습니다.

메뉴 항목 선택

선택은 단일 키 누르기 또는 선택적으로 마우스를 사용하여 수행 될 것으로 예상됩니다 (중요하지는 않지만 마우스를 지원하지 않는 제안을 포함하는 답변은 합법적입니다). 마우스를 지원하는 솔루션을 제안하는 경우 사용자는 직관적 인 방법으로, 즉 원하는 선택을 마우스 왼쪽 버튼으로 클릭하여 메뉴 항목을 선택할 수 있어야합니다.

NB 마우스는 다양한 방법으로 사용될 수 있으며 선택을 나타내는 다른 방법도 환영합니다.

팝업 제거

사용자가 위에서 설명한 방식으로 메뉴 항목을 선택하면 버퍼와 해당 창을보기에서 제거하고 종료해야합니다. 팝업 메뉴를 호출하기 전에 활성화 된 창에 다시 포커스가 있어야합니다 (즉, 활성화됩니다).

반환 값과 인수

바람직하게는, 이러한 동작의 결과는 Lisp 객체가 반환되게해야한다. Lisp 오브젝트는 다음 중 하나 일 수 있습니다.

  • nil— 이것은 사용자가 C-g또는 †를 눌러 팝업 메뉴를 중단했음을 나타냅니다 .

  • string— string (기호를 사용할 수 있음)은 string-equal 실제 항목의 모음으로 팝업 메뉴에 제공된 문자열 중 하나에 있어야 합니다.

나머지 프로그램에 사용자의 선택 또는 부재 여부를 알리는 대체 방법이 허용됩니다. 그러나 다른 방법으로 수행 할 수 있는지 확실하지 않은 경우 모든 응답자에게 즉흥적으로 요청 하고이 측면에 대한 추가 설명을 요구하지 않습니다.

이것은 모두 반환 값입니다. 입력 매개 변수의 경우 가능한 선택 항목 (메뉴 항목)을 나타내는 문자열 모음을 적어도 포함해야합니다.

허용되는 답변

예상 답변은 다음과 같은 형식 일 수 있습니다.

  • 교육받은 독자가 위에서 설명한 것과 같은 기능을 작성할 수있는 충분한 코드 스 니펫; 전체 작업 기능을 작성해야 할 필요는 없습니다. 그러나 불확실성을 피하려면 (코드의 상당 부분을 생략 할 수 있습니까?) 스 니펫의 누락 된 부분은 텍스트의 답변 구성 요소로 설명해야합니다.

  • 유사한 기능을 구현하는 기존 라이브러리에 대한 링크입니다. 불확실성을 피하기 위해 필자 의 경우 와 유사 하다는 것은 라이브러리가 위에서 설명한 2 개 또는 3 개의 기능을 가진 팝업 을 생성하는 데 사용될 수 있음을 의미 한다는 점에 유의해야합니다 . 제안 된 도서관이 이전에 언급 된 조건을 충족 할 수없는 지점과 다른 경우, 그러한 각 사례는 독립적으로 판단되며 OP가 유용하다고 판단되면 항상 찬성됩니다.

  • 내장 기능 Emacs 기능 또는 타사 기능에 대한 설명은«기능»섹션에 설명 된 기능을 구현하는 데 사용할 수 있습니다 (위 참조). 피하기 불확실성, 당신의 대답은 구현하려는 미래의 독자를 위해 유용 할 수 있습니다 명확하게하는 방법하시기 바랍니다 상태 팝업 , Magit에 사용되는 것과 유사한 팝업 메뉴를 .


† 팝업 메뉴를 중단하는 다른 방법은 다음과 같습니다 (이에 국한되지는 않음).

  • 팝업 메뉴 창의 외부를 클릭;

  • 선택하지 않고 팝업이 포함 된 버퍼를 종료합니다.

답변:


8

magit-popup그것의가 자신의 매뉴얼을 ! 그러나 실제로 팝업에서 인수를 설정하여 호출 된 작업으로 전달하지 않는 한 hydra대신 사용하는 것이 좋습니다.

또한 더 발전시킬 의도는 없습니다 magit-popup. 대신 처음부터 비슷한 패키지를 작성하겠습니다. 현재 구현에는 우연히 많은 복잡성이 있습니다. (그렇다고해서 그것이 magit-popup단순히 사라지는 것은 아닙니다 . 많은 새로운 기능을 볼 수는 없지만 현재 구현이 원하는 것을 수행한다면 왜 사용하지 않습니까?)

또는 "처음부터"수행하고 싶기 때문에 살펴보고 read-char-choice거기서 나가십시오. 시각적 부분을 구현하려면 lvhydra 저장소의 일부를 살펴보십시오 .


다른 독자들을 위해 : @tarsius가 그 대체물을 읽고 작성했습니다 magit-popup. 새 패키지는 transient이며 현재 버전의에서 사용됩니다 magit. 설명서는 magit.vc/manual/transient 를 참조하십시오 .

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)

Hydra는 키보드 중심 인터페이스이며 기본 형태로는 easy-menu-define(내장) 보다 어렵지 않습니다 . 더 복잡한 작업을 수행하려는 경우 확장 성이 뛰어납니다.

이 트위터 인터페이스를 보시면, 하단 창은 맞춤형 Hydra이며 위의 것보다 작성하기가 어렵지 않습니다.

hydra-twittering.png

이에 대한 코드 는 wiki 에서 더 많은 예제와 함께 사용할 수 있습니다 .


좋습니다, 확실히이 일에 대해 더 많이 배울 시간을 찾을 것입니다!
마크 카르 포프

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 of에 설명되어 with-current-buffer-window있으며 사용자로부터 입력을 얻는 데 사용할 수 있습니다. @tarsius가 제안했듯이 read-char-choice실험하기에 좋은 후보입니다.

팝업 버퍼 자체는 다른 버퍼와 마찬가지로 생성 될 수 있습니다. 사용자가 키보드뿐만 아니라 팝업 메뉴에서 마우스를 사용하여 항목을 선택할 수 있기 때문에 여전히 버튼을 생각하고 있습니다. 그러나 잘하려면 추가 노력이 필요합니다.

팝업이 약간 임시적이며 두 번 이상 필요하지 않은 경우이 솔루션으로 벗어날 수 있습니다. 또한을 살펴보십시오 completion--insert-strings. 메뉴 항목의 예쁜 행을 생성하는 방법의 예로서 꽤 유용하다는 것을 알았습니다. 특별한 것이 필요하지 않은 경우에도 변경하지 않고 사용할 수 있습니다.


4
아마 당신의 머릿속에있는 질문이 좋을 것 같습니다. 당신이 적어 놓은 질문은 다소 불분명합니다. 나는 당신이 이런 대답을 한 후에 당신을 어떻게 알 수 있었는지 알 수 없습니다.
npostavs

1

다음은 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이 별도로 아직 사용할 수있는 패키지되지 않았을 때의 코멘트에 했나요,) magit.

또한 언급하겠습니다 discover: 그것은 사용 방법 makey(및 그것의 raison d' être)의 예입니다. 이 패키지는 이민자들이 emacs 바인딩을 발견 할 수 있도록 돕기위한 것입니다.


참고 문헌


2
@Mark 왜이 답변을 수락했는지 잘 모르겠습니다. 그것은 내가 정확하게 기억한다면, 당신이 쫓아 온 작은 빌딩 블록을 언급하지는 않습니다. makey는 magit-popup의 포크가 아니며, 이전의 magit-key-mode의 포크입니다. 그것은 이후 magit-popup에서 해결 된 대부분의 적자를 상속받습니다. 따라서 magit-popup 또는 hydra (또는 직접 작성)를 사용하는 것이 훨씬 좋습니다.
tarsius

@tarsius 답변의 정확성을 판단하는 것은 어려울 수 있습니다. 올바르지 않다는 것을 알고 있으면 자유롭게 편집하십시오. 편집을했는데 여전히 100 % 확실하지 않습니다. 또한 그들이 무엇인지 모르기 때문에 상속되는 "적자"에 대해서는 언급하지 않았습니다.
YoungFrog


0

@Drew의 답변 (차가워 요)을 기반으로 메뉴 데이터와 메뉴 구현을 분리하도록 수정되었습니다. 따라서 각각 자체 메뉴를 사용하여 여러 메뉴를 정의 할 수 있습니다 (콘텐츠, 프롬프트, 기본값).

선택적으로 메뉴를 열어 둔 상태에서 항목을 활성화 할 수있는 등의 고급 기능이있는 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.