Skip to content
Pentadactyl-like Link Hinting in Emacs with Avy
Emacs Lisp
Branch: master
Clone or download
jumper047 and noctuid Add windows support
Emacs shows windows paths as "d:/some/dir". Now regexp matches them too.
Latest commit 0d9cabc Feb 5, 2020
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
LICENSE Initial commit Sep 3, 2015
README.org Add support for nov.el links May 19, 2018
link-hint.el Add windows support Feb 5, 2020

README.org

http://melpa.org/packages/link-hint-badge.svg

Demonstration

Using this package to install this package: http://noctuid.github.io/link-hint.el/assets/paradox_demonstration.gif

About

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
  • Other button links (e.g. WoMan links, ag mode, etc.)

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.

Similar

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 Setup

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)

Browser Choice

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

Provided Commands

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’s g;).
  • 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 and select-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.

Overriding Avy Settings

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

Messaging

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”=).

Point/Window Restoration

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.

Defining New Link Types and Actions

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.

Mandatory Keywords

: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.

Predicate Keywords

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

Action Keywords

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.

Action Modifier Keywords

: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.

Creating New Commands

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

Using for Links in Terminal with Tmux

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.

You can’t perform that action at this time.