Skip to content

licht1stein/context-transient.el

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

66 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

MELPA MELPA Stable

context-transient.el

Easily create context-specific transient menus for Emacs. Context can be anything — buffer name, current git repo, current project etc. See examples.

Example

This is a menu I get when pressing F6 while working on one of my projects:

What the commands do is not important, what's important is how easy it was to make a project-specific transient menu with useful commands.

Usage

Once you have defined your context-specific transients, call them with M-x context-transient RET.

Installing

You can install it from MELPA using M-x package-install RET context-transient RET, or using use-package:

(use-package context-transient
 :defer nil
 :bind ("<f6>" . context-transient))

I recommend binding context-transient to something easily accessible, F6 in the example above.

Defining context transients

Context transients are defined using context-transient-define. You can specify one of the following keys to check current context:

  • :repo - checks if the current git repo name is equal to this
  • :project - checks if the current project name is equal to this (note, this is built-in project.el, not projectile)
  • :buffer - checks if the current buffer name is equal to this
  • :mode- checks if the current major mode is this
  • :context - arbitrary code that will be run to check if the transient should be run

Obviously, it's quite possible to define several transients that would apply to the current context. In this case user will be prompted to choose which one to run.

Git repo context (:repo)

This example defines a transient menu for git repos. Note the usage of :repo function — this is a helper function that returns t if the repo name is equal to it's argument. But context accepts any expression that evaluates to t or nil.

(context-transient-define context-transient-repo
  :doc "Repo specific transient"
  :repo "context-transient.el"
  :menu
  ["Section"
  ["Subsection"
   ("i" "Increase font" text-scale-increase :transient nil)
   ("o" "Decrease font" text-scale-decrease :transient nil)]])

Buffer name context (:buffer)

The following example runs the transient if current buffer name is *scratch*:

(context-transient-define itch
  :doc "Itch a *scratch*"
  :buffer "*scratch*"
  :menu
  [["Test" ("i" "Itch *scratch*" (lambda () (interactive) (message "Itched")))]])

Project context (:project)

This will check if the built-in project.el project name is equal to this. Same as :buffer or :repo — just pass a project name as a string.

Major mode context (:mode)

Checks if the current major-mode is this. Note, you need to provided the major mode as a quoted symbol, and not as a string. Here's an example of a menu for editing subtitles:

(context-transient-define subed-transient
    :doc "Transient for working with subtitles"
    :mode 'subed-srt-mode
    :menu
    ["MPV"
     ("o" "Open video file" subed-mpv-play-from-file)
     ("O" "Open video url" subed-mpv-play-from-url)
     ("s" "Toggle sync subs -> player" subed-toggle-sync-player-to-point)
     ("p" "Toggle sync player -> subs" subed-toggle-sync-point-to-player)])

Any expression context (:context)

You can run any lisp expression in :context. For example, transient only works on Thursdays:

(context-transient-define thursdays-transient
  :doc "Only show this menu on Thursdays"
  :context (equal "Thursday" (format-time-string "%A"))
  :menu
  [["Thursday!"
    ("t" "Once a week" (lambda () (interactive) (message "IT IS THURSDAY!")))]])

Clojure Specific Example

Normally with transient you would need to be a bit more verbose to use it to run interactive CIDER commands while working on a Clojure project:

(context-transient-define my-clj-transient
  :doc "Transient for my-clj-repo"
  :repo "my-clj-repo"
  :menu
  [["REPL"
   ("c" "Connect REPL" (lambda () (interactive) (cider-connect-clj '(:host "localhost" :port 63000))) :transient nil)
   ("d" "Sync deps" (lambda () (interactive) (cider-interactive-eval "(sync-deps)")))]
  ["Debug"
   ("p" "Start portal" (lambda () (interactive) (cider-interactive-eval "(user/portal)")))
   ("P" "Clear portal" (lambda () (interactive) (cider-interactive-eval "(user/portal-clear)")))
   ("S" "Require snitch" (lambda () (interactive) (cider-interactive-eval "(require '[snitch.core :refer [defn* defmethod* *fn *let]])")))]
  ["Systems"
   ("a" "(Re)start main system" (lambda () (interactive) (cider-interactive-eval "(user/restart-sync)")))
   ("A" "Stop main system" (lambda () (interactive) (cider-interactive-eval "(user/restart-sync)")))]])

Better way for Clojure

There's a much nicer way to do this with context-transient. We provide a helper function context-transient-require-defcljthat creates a defclj macro and allows rewriting the above example like in the code below.

Note, that commands created by defclj are interactive and can also be used from the M-x menu or bound to hotkeys. Because of this, defclj also accepts an optional docstring:

(defclj my-sync-deps (sync-deps) "Sync project deps")

Here's the rewritten menu from above:

(context-transient-require-defclj)

(defclj my-sync-deps (sync-deps) "Sync project deps")
(defclj my-portal (user/portal))
(defclj my-portal-clear (user/portal-clear))
(defclj my-require-snitch (require '[snitch.core :refer [defn* defmethod* *fn *let]]))
(defclj my-restart-sync (user/restart-sync))
(defclj my-stop-sync (user/stop-sync))

(context-transient-define
 my-clj-transient
 :doc "Transient for my-clj repo"
 :repo "my-clj-repo"
 :menu
 [["REPL"
   ("c" "Connect REPL" (lambda () (interactive) (cider-connect-clj '(:host "localhost" :port 63000))) :transient nil)
   ("d" "Sync deps" my-sync-deps)]
  ["Debug"
   ("p" "Start portal" my-portal)
   ("P" "Clear portal" my-portal-clear)
   ("S" "Require snitch" my-require-snitch)]
  ["Systems"
   ("a" "(Re)start main system" my-restart-sync)
   ("A" "Stop main system" my-stop-sync)]])

Note, that the second argument to defclj is unquoted Clojure code, not elisp.

Clearing context-transients

If for some reason a previously defined transient misbehaves, you can clear all context transients by running M-x context-transient-clear RET

Thanks

This library started as a variation on my repo-hydra.el library. But thanks to the help from Nicholas Vollmer (@progfolio) it became a much more useful tool.