Using this package to install this package:
link-hint.el
is inspired by the link hinting functionality in vim-like browsers and browser plugins such as pentadactyl. It provides commands for using avy to open, copy, or take a user-defined action on “links.”
Currently the following types of links are supported:
- Plain text urls (e.g.
http://github.com
; includes irc urls and mailto) - File paths
- Shr urls (e.g. elfeed links, links in html mu4e and gnus messages, eww urls, etc.)
- Org mode urls (includes
mailto:
links) - Markdown links (including “wiki links”; see
markdown-enable-wiki-links
) - Mu4e links (urls and mailto addresses)
- Mu4e attachments
- Gnus html mail rendered with gnus-w3m or emacs-w3m (instead of shr)
- Help mode links
- Info mode links
- Package menu links (describe package, install package, keyword buttons, etc.)
- Compilation mode links
- W3m links (urls, email addresses, etc.)
- Customize links
- Nov.el links
- notmuch-hello links
- Deadgrep matches
- Other button links (e.g. WoMan/Man/Dictionary links, ag mode, etc.)
- Completion List candidates (better put it before link-hint-file-link for project.el)
- Dired filenames
- Org Agenda items
- Xref items
Feel free to request support for any useful link type I may have missed. Also, if you think it would be beneficial to have a more specific link type split from a more generic link type, feel free to make an issue. For example, there may be some specific type of button you want to ignore or use in a custom command without affecting other buttons.
There is also ace-link which I didn’t know about when writing this package. The main functional differences at the time of writing are as follows:
- link-hint supports more link types
- link-hint supports operating on multiple different types of links with the same command in the same buffer; types are optionally tied to major modes, but generic link support is also provided (e.g. buttons and file paths)
- link-hint supports easily adding more link types and actions
- link-hint supports operating on multiple links at a time
Basic usage of this package only requires making key bindings for link-hint-open-link
or other commands. Here is an example configuration using use-package:
(use-package link-hint
:ensure t
:bind
("C-c l o" . link-hint-open-link)
("C-c l c" . link-hint-copy-link))
Here is an example configuration for evil:
(use-package link-hint
:ensure t
:defer t)
(define-key evil-normal-state-map (kbd "SPC f") 'link-hint-open-link)
browse-url
is used for opening urls, so in the case that the desired browser is not being used by default, the user can set browse-url-browser-function
:
;; Use chromium to open urls
(setq browse-url-browser-function 'browse-url-chromium)
;; Use firefox to open urls
(setq browse-url-browser-function 'browse-url-firefox)
;; Use qutebrowser to open urls
(setq browse-url-browser-function 'browse-url-generic)
(setq browse-url-generic-program "qutebrowser")
;; Open urls in a new tab instead of window; can also be set in the config file
(setq browse-url-generic-args '("--target" "tab"))
This package provides the following commands for operating on links:
link-hint-open-link-at-point
- Open the link at point.link-hint-copy-link-at-point
- Copy the link at point to the kill ring (and optionally to the clipboard/primary).link-hint-open-link
- Use avy to select and open a single visible link. If only one link is currently visible, it will be automatically opened without the need for selection.link-hint-open-multiple-links
- Use avy to select multiple visible links and open them as soon as a key that does not correspond to a link (a key not in the avy overlay) is pressed (like pentadactyl’sg;
).link-hint-open-all-links
- Opens all links visible in the buffer.link-hint-copy-link
- Use avy to select and copy a single visible link to the kill ring.select-enable-clipboard
andselect-enable-clipboard
can each be set to a non-nil value to also use the clipboard and/or primary.
link-hint-copy-multiple-links
and link-hint-copy-all-links
also exist, but they may not be useful very often.
This package does not bind any commands by default.
link-hint.el
supports overriding avy’s settings. For example, if you want to use a different avy style just for link hinting, you can set link-hint-avy-style
:
(setq link-hint-avy-style 'pre)
This will cause the overlays to be displayed before the links (and not cover them). Note that using the post
style will not put the overlay at the end of links. I don’t think this style makes much sense for links, but feel free to open an issue if you would like this style to be supported.
Here is the full list of settings:
link-hint-avy-style
link-hint-avy-keys
link-hint-avy-all-windows
link-hint-avy-all-windows-alt
link-hint-avy-background
link-hint-avy-ignored-modes
By default, these variables are not bound, and avy’s corresponding settings are used. avy-styles-alist
and avy-keys-alist
are also supported for the provided commands (as well as avy-resume
).
By default, link-hint will print a message in the echo area when an action is performed. link-hint-message
can be set to nil
to disable this behavior. It can also be set to a custom message function such as lv-message
.
link-hint-action-messages
is a plist that is used for the default description of each action keyword (e.g. :open "Opened"
).
Link hint will move the point (and sometimes the window; see avy-all-windows
) when acting on a link. When link-hint-restore
is a non-nil value, link-hint will automatically restore the point and window when the link action does not intentionally change the point/window. For example, if link-hint-avy-all-windows
is a non-nil value, and the user copies a link in a different window, the point will stay the same in the buffer containing the link, and the selected window will stay the same. On the other hand, if the user opens a url in eww
in a new window, the eww
window will be selected, but the point in the link buffer will be restored. Similarly, if the user opens an org link to a local (same buffer) heading, the point and window will not be restored.
link-hint-define-type
is the helper function used to define new link types. link-hint-define-type
is just simple helper to alter the symbol plist of link-hint-<type>
(though it is recommended to use it directly in case the implementation changes). For example, here is how shr-url
could be defined if it did not already exist:
(link-hint-define-type 'shr-url
:next #'link-hint--next-shr-url
:at-point-p #'link-hint--shr-url-at-point-p
:open #'browse-url
:copy #'kill-new)
(push 'link-hint-shr-url link-hint-types)
All link hint types are defined in this way, so see the source code for more examples.
:next
should be a function that returns the position of the next link after the point (i.e. if there is a link at the point, it should not return the point). It should take one argument that corresponds to the end bound for searching. Also, it should not move the point.
:at-point-p
should be a function that returns a non-nil value if there is a link at the point. Its return value can be used in the action functions.
These keywords are used to determine when a type is active. These are not strictly necessary but can be used, for example, to help performance (this is usually not an issue except for WoMan links currently).
:predicates
should be a list of functions that should each return true if the link type passes.
:vars
should be a list of variables and/or major modes. If at least one of them is bound and true or the current major mode, the link type passes.
:not-vars
should be a list of variables and/or major modes. If any of them are bound and true or the current major mode, the link type does not pass.
All of these checks must pass for the link type to be considered active. It is also possible to create commands that only operate on specific link types by binding link-hint-types
(e.g. (let ((link-hint-types ...)))
).
The main actions supported by default are :open
and :copy
. Action keywords can have any name not already used by link-hint. In a type definition, each action keyword should be specified with a function that will perform that action. These functions are not required to take a specific number of arguments. If an action function does not take any arguments, it should operate on the link at point. Otherwise, the return value of :at-point-p
will either be used as a list of arguments for the action function (i.e. apply
) or a single argument for the action function (i.e. funcall
).
Link types are not required to support all action keywords. If a link type does not support a particular action keyword, it will just be ignored for that action.
:parse
should be a function that takes two arguments: the return value of the link type’s :at-point-p
function and the action keyword. It should return a valid input for the action function. This can be useful, for example, if the at-point-p
function returns a plist, struct, etc. and each action function only needs part of it (see the definition of package-link
for a concrete example).
:<action>-multiple
should be a boolean value corresponding to whether it makes sense to perform the action on multiple links in a row.
:<action>-message
should be a string that will be used instead of the normal message string. For example, :open-message "Installed"
is specified for the package-install-link
type.
:describe
should be a function that returns a string representation of the link to be used when messaging. If not set, the return value of the :at-point-p
function is used directly.
The user can create new commands to do something other than copy or open a link using the link-hint--one
, link-hint--multiple
, and link-hint--all
helper functions. Each takes a single action keyword as an argument.
Here is an example of adding a command that downloads a url:
;; `link-hint-define-type' can be used to add new keywords
(link-hint-define-type 'text-url
:download #'w3m-download)
(link-hint-define-type 'w3m-link
:download #'w3m-download)
...
(defun link-hint-download-link ()
"Use avy to select and download a text URL with download-region.el."
(interactive)
(avy-with link-hint-download-link
(link-hint--one :download)))
This may seem like a strange use for this package, but I’ve been doing this due to lack of a better alternative. Unfortunately, most of the methods for generically opening urls in a terminal running tmux (e.g. urlscan, urlview, w3m, terminal keybindings, tmux-urlview, and tmux-open) aren’t very quick or customizable. tmux-fingers looks more promising but currently only supports copying, doesn’t allow for customizable hint keys, and is slow for me.
I’ve started using this keybinding on the rare occasion that I need to open a url from somewhere other than emacs:
bind-key f capture-pane \; save-buffer /tmp/tmux-buffer \; \
new-window 'emacsclient -t -e "(find-file \"/tmp/tmux-buffer\")" -e "(goto-address-mode)" -e "(link-hint-open-link)" -e "(kill-this-buffer)" -e "(delete-frame)"'
I kill the buffer to ensure that emacs won’t prompt to revert the file on later invocations in the case that auto-revert-mode is off.
One downside (shared by most other methods) is that it may be a bit disorienting to have the positions of links moved when opening a new tmux window. In this regard, having link-opening functionality directly in a terminal is nice.