So implementieren Sie ein Popup-Menü ähnlich dem in Magit verwendeten

10

Frage

Ich möchte eine Benutzeroberfläche in Form eines Popup-Menüs erstellen , ein Popup-Menü ähnlich dem in Magit verwendeten .

Eigenschaften

Definition von Popup

Popup im Kontext dieser Frage bedeutet ein kleines temporäres Fenster, das eine Sammlung von Menüelementen enthält, sodass der Benutzer nur eines dieser Elemente auswählen kann.

Position auf dem Bildschirm

Das Popup darf in jedem Teil des Bildschirms angezeigt werden, es ist jedoch wünschenswert, dass es ganz offensichtlich ist und daher neben dem derzeit aktiven Fenster angezeigt wird.

Inhalt des Popup-Puffers

Elemente sollten in Form einer hübschen Tabelle angezeigt werden. Hübsch ist der Kontext der Frage bedeutet optisch ansprechend, dieser Effekt kann am einfachsten erzielt werden, indem Menüpunkte in gerade Reihen gesetzt werden, siehe complete--insert-stringzum Beispiel. Dieser Absatz dient der zusätzlichen Klarstellung. Sie können dies auf Ihre eigene Weise tun. Dadurch wird Ihre Antwort nicht falsch.

Auswahl des Menüpunktes

Die Auswahl wird voraussichtlich durch einmaliges Drücken einer Taste oder optional mit einer Maus durchgeführt (obwohl dies nicht so wichtig ist, sind Antworten mit Aussagen, die keine Maus unterstützen, legal). Wenn Sie eine Lösung vorschlagen, die Maus unterstützt, beachten Sie bitte, dass der Benutzer in der Lage sein sollte, einen Menüpunkt auf intuitive Weise auszuwählen, dh indem Sie mit der linken Maustaste auf die gewünschte Auswahl klicken.

NB Maus kann auf viele Arten verwendet werden und alternative Möglichkeiten, um eine Auswahl anzuzeigen, sind ebenfalls willkommen.

Beseitigung von Popup

Sobald der Benutzer einen Menüpunkt auf die oben beschriebene Weise ausgewählt hat, sollte der Puffer und damit sein Fenster nicht mehr angezeigt und nicht mehr angezeigt werden. Das Fenster, das vor dem Aufrufen des Popup-Menüs aktiv war, sollte wieder fokussiert werden (dh aktiv werden).

Rückgabewert und Argumente

Vorzugsweise sollte diese Konsequenz von Aktionen dazu führen, dass ein Lisp-Objekt zurückgegeben wird. Das Lisp-Objekt kann entweder:

  • nil- Dies zeigt an, dass der Benutzer das Popup-Menü entweder durch Drücken C-goder auf andere Weise abgebrochen hat †.

  • string- Zeichenfolge (es darf ein Symbol verwendet werden) sollte string-equal eine der Zeichenfolgen sein, die im Popup-Menü als Sammlung der tatsächlichen Elemente angegeben sind.

Alternative Möglichkeiten, den Rest des Programms über die Wahl des Benutzers oder möglicherweise über dessen Abwesenheit zu informieren, sind akzeptabel. Wenn jedoch nicht klar ist, wie es sonst durchgeführt werden kann, bitte ich alle Antwortenden, zu improvisieren, und bitte mich nicht um weitere Klärung dieses Aspekts.

Dies ist alles für den zurückgegebenen Wert. Die Eingabeparameter sollten mindestens eine Sammlung von Zeichenfolgen enthalten, die mögliche Auswahlmöglichkeiten darstellen (dh Menüelemente).

Akzeptable Antworten

Die erwartete Antwort kann folgende Formen annehmen:

  • Ausreichendes Code-Snippet, mit dem gebildete Leser Funktionen wie die oben beschriebene schreiben können; Es ist nicht zu erwarten oder notwendig, die gesamte Arbeitsfunktion zu schreiben. Um jedoch Unsicherheiten zu vermeiden (können erhebliche Teile des Codes weggelassen werden?), Sollte ich beachten, dass fehlende Teile des Snippets in der Textkomponente der Antwort beschrieben werden sollten.

  • Ein Link zu einer vorhandenen Bibliothek, die ähnliche Funktionen implementiert. Um Unsicherheiten zu vermeiden, sollte ich beachten, dass in unserem Fall ähnlich bedeutet, dass die Bibliothek zum Erstellen von Popups (siehe Definition oben) verwendet werden kann, die mindestens zwei oder drei oben beschriebene Funktionen aufweisen. Wenn sich die vorgeschlagene Bibliothek von dem Punkt unterscheidet, an dem die zuvor angegebene Bedingung nicht erfüllt werden kann, wird jeder dieser Fälle unabhängig beurteilt und immer bewertet, wenn OP dies für nützlich hält.

  • Beschreibung der integrierten Emacs-Funktionen oder Funktionen von Drittanbietern, mit denen alle im Abschnitt «Funktionen» beschriebenen Funktionen implementiert werden können, siehe oben. Um zu vermeiden , Unsicherheit, bitte angeben klar , wie Ihre Antwort kann für zukünftige Leser nützlich sein , die implementieren möchten Popup , Popup - Menü ähnlich dem in Magit verwendet .


† Alternative Möglichkeiten zum Abbrechen des Popup-Menüs können Folgendes sein (sind jedoch nicht darauf beschränkt):

  • Klicken außerhalb des Popup-Menüfensters;

  • Töten des Puffers, der das Popup enthält, ohne eine Wahl zu treffen.

Mark Karpov
quelle

Antworten:

8

magit-popuphat ein eigenes Handbuch ! Wenn Sie jedoch keine Argumente im Popup festlegen möchten, die dann an die aufgerufene Aktion übergeben werden, ist es wahrscheinlich besser, hydrastattdessen zu verwenden.

Beachten Sie auch, dass ich nicht beabsichtige, mich weiterzuentwickeln magit-popup. Stattdessen werde ich ein ähnliches Paket von Grund auf neu schreiben. Die aktuelle Implementierung ist einfach zu komplex. (Das heißt aber nicht, dass magit-popupdas einfach verschwindet. Es werden wahrscheinlich nicht viele neue Funktionen angezeigt. Wenn die aktuelle Implementierung jedoch das tut, was Sie möchten, warum dann nicht?)

Oder da auch Sie es "von Grund auf neu" machen wollen, schauen Sie es sich an read-char-choiceund gehen Sie von dort aus. Um den visuellen Teil zu implementieren, schauen Sie sich an, lvwelcher Teil des Hydra-Repository ist.

Tarsius
quelle
Für andere Leser: Beachten Sie, dass @tarsius diesen Ersatz für gefolgt und geschrieben hat magit-popup. Das neue Paket heißt transientund wird in aktuellen Versionen von verwendet magit. Dokumentation finden Sie unter magit.vc/manual/transient .
Phils
5

Hydras sind ziemlich einfach zu schreiben:

(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 ist eine tastaturzentrierte Oberfläche und in ihrer Grundform nicht schwieriger als easy-menu-define(eingebaut). Und es ist ziemlich erweiterbar, wenn Sie es komplexer machen wollen.

Schauen Sie sich diese Twitter-Oberfläche an. Das untere Fenster ist eine benutzerdefinierte Hydra, die nicht viel schwieriger zu schreiben ist als die oben genannte:

hydra-twittering.png

Der Code dafür ist im Wiki verfügbar , zusammen mit vielen weiteren Beispielen.

abo-abo
quelle
Großartig, ich werde auf jeden Fall Zeit finden, mehr über diese Sache zu erfahren!
Mark Karpov
1

Nach einigen Recherchen habe ich eine Redewendung gefunden, mit der ein Fenster am unteren Rand (oder an einer beliebigen Stelle) des derzeit aktiven Fensters erstellt werden kann. Dies selbst hat die Wirkung eines temporären Hilfsfensters:

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

Sie können ein wenig mit dem actionArgument spielen, with-current-buffer-windowob das Popup in einem anderen Teil des Bildschirms angezeigt werden soll.

Die Beendigungsfunktion wird in der Dokumentzeichenfolge von beschrieben with-current-buffer-window. Sie kann verwendet werden, um Eingaben vom Benutzer abzurufen. Wie @tarsius vorgeschlagen hat, read-char-choiceist ein guter Kandidat zum Experimentieren.

Der Popup-Puffer selbst kann wie jeder andere Puffer generiert werden. Ich denke immer noch an Schaltflächen, weil der Benutzer mit der Maus einen Eintrag im Popup-Menü auswählen kann, nicht nur die Tastatur. Dies erfordert jedoch zusätzlichen Aufwand, wenn Sie es gut machen möchten.

Wenn Ihr Popup etwas ad-hoc ist und Sie es nicht mehr als einmal benötigen, können Sie mit dieser Lösung davonkommen. Schauen Sie sich auch an completion--insert-strings. Ich fand es sehr nützlich als Beispiel dafür, wie man hübsche Reihen von Menüelementen generiert. Sie können es sogar unverändert verwenden, wenn Sie nichts Besonderes benötigen.

Mark Karpov
quelle
4
Ich denke, vielleicht ist die Frage, die Sie in Ihrem Kopf haben, gut. Die Frage, die Sie aufgeschrieben haben, ist ziemlich unklar. Ich verstehe nicht, wie jemand hätte wissen können, dass Sie nach einer solchen Antwort waren.
Npostavs
1

Hier ist eine Lösung von Drittanbietern, die Eiszapfen verwendet .

  • Ihr Code öffnet ein Fenster mit den Kandidaten, die übersichtlich als Menü angeordnet sind. Wie, siehe unten.

  • Sie stellen jedem Menüpunkt ein eindeutiges Zeichen voran. ZB a, b, c ... oder 1, 2, 3 ... Der Benutzer drückt das Zeichen, um den Gegenstand auszuwählen.

  • Sie binden einige Variablen um einen Aufruf an completing-read. Sie übergeben ihm eine Liste Ihrer Menüpunkte. Sie können optional eines der Elemente als Standard festlegen, das ausgewählt wird, wenn der Benutzer nur trifft RET.

    Die variablen Bindungen sagen completing-read:

    • Zeigen Sie jeden Menüpunkt in einer separaten Zeile an
    • Zeigen Sie das Menü sofort
    • Aktualisieren Sie es sofort, wenn ein Benutzer eine Taste drückt
    • Kehren Sie sofort zurück, wenn die Benutzereingabe nur einem Element entspricht
    • Zeigen Sie die Standardauswahl in der Eingabeaufforderung an.
  • Sie können die Menüpunkte so ausgefallen gestalten, wie Sie möchten (nicht gezeigt).

    • Gesichter
    • Bilder
    • Anmerkungen
    • mehrere Zeilen pro Artikel
    • mouse-3 Popup-Menü, um etwas mit dem Element zu tun
(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)))

Sie können auch wirklich Multiple-Choice-Menüs erstellen, dh Menüs , in denen ein Benutzer mehrere Elemente gleichzeitig auswählen kann (wählen Sie eine Teilmenge der möglichen Auswahlmöglichkeiten).

Drew
quelle
1

Vielleicht interessiert Sie auch das Paket makey. Es soll ähnliche Funktionen bieten wie magit-popup, und es ist eine Abzweigung des Vorgängers von magit-popup( magit-key-mode, im Kommentar erwähnt) aus der Zeit, als es noch kein separat erhältliches Paket war magit.

Lassen Sie mich auch erwähnen discover: Das ist ein Beispiel für die Verwendung makey(und auch die Existenzberechtigung). Dieses Paket soll Neulingen helfen, Emacs-Bindungen zu entdecken.


Verweise

Junger Frosch
quelle
2
@ Mark Ich bin mir nicht ganz sicher, warum Sie diese Antwort akzeptiert haben. Es werden überhaupt keine kleinen Bausteine ​​erwähnt, nach denen Sie, wenn ich mich richtig erinnere, gesucht haben. Es ist auch nicht falsch: makey ist keine Gabel des Magit-Popups, es ist eine Gabel des Magit-Key-Modus seines Vorgängers. Es erbt die meisten Defizite, die seitdem im Magit-Popup behoben wurden. Sie sind also viel besser darin, Magit-Popup oder Hydra zu verwenden (oder Ihre eigenen zu schreiben).
Tarsius
@tarsius Die Richtigkeit einer Antwort zu beurteilen kann schwierig sein. Wenn Sie wissen, dass es nicht korrekt ist, können Sie es jederzeit bearbeiten. Ich habe eine Bearbeitung vorgenommen, bin mir aber immer noch nicht 100% sicher, ob sie jetzt korrekt ist. Ich habe auch die "Defizite", die es erbt, nicht erwähnt, weil ich nicht weiß, was sie sind.
YoungFrog
0

Guide-Key funktioniert für mich gut für alle konfigurierten Präfixe.

Netsu
quelle
0

Basierend auf der Antwort von @ Drew (Eiszapfen) mit Änderungen, um die Menüimplementierung von den Menüdaten zu trennen, können mehrere Menüs mit jeweils eigenen definiert werden: (Inhalt, Eingabeaufforderung, Standard).

Hierbei wird optional Efeu verwendet (sofern verfügbar), das über erweiterte Funktionen verfügt, z. B. die Möglichkeit, Elemente zu aktivieren, während das Menü geöffnet bleibt.

Generisches Popup.

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

Beispiel: Weisen Sie der Taste f12 einige Aktionen zu:

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