noctuid / link-hint.el

Pentadactyl-like Link Hinting in Emacs with Avy
GNU General Public License v3.0
162 stars 22 forks source link

[[http://melpa.org/#/link-hint][file:http://melpa.org/packages/link-hint-badge.svg]]

Currently the following types of links are supported:

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 *** Ace-link There is also [[https://github.com/abo-abo/ace-link][ace-link]] which I didn't know about when writing this package. The main functional differences at the time of writing are as follows:

*** Embark There is some overlap with embark since both packages provide multiple actions for different types of things. Here is a comparison:

** 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 [[https://github.com/jwiegley/use-package][use-package]]:

+begin_src emacs-lisp

(use-package link-hint :ensure t :bind ("C-c l o" . link-hint-open-link) ("C-c l c" . link-hint-copy-link))

+end_src

Here is an example configuration for evil:

+begin_src emacs-lisp

(use-package link-hint :ensure t :defer t)

(define-key evil-normal-state-map (kbd "SPC f") 'link-hint-open-link)

+end_src

** 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~:

+begin_src emacs-lisp

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

+end_src

** Provided Commands This package provides the following commands for operating on links:

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

** At Point Fallback Commands :PROPERTIES: :CUSTOM_ID: at-point-fallback-commands :END:

While the main purpose of link-hint is remote link selection with avy, it does provide commands to operate on a link at point (since it already has the necessary code to do so). =link-hint-open-link-at-point=, for example, can be used as a sort of global "act on the point" command. If there is not a link at the point, you can make it fall back to another command by setting =link-hint-action-fallback-commands=. Some potentially useful fallback commands would be =embark-dwim= and =action-key= (hyperbole).

To still get the "no links found" error message when nothing happens, fallback command should return nil if it also fails to do anything.

+begin_src emacs-lisp

(setq link-hint-action-fallback-commands (list :open (lambda () (condition-case _ (progn (embark-dwim) t) (error nil)))))

+end_src

Here is a more complex example that will try to jump to the definition in programming modes where possible before falling back to hyperbole. If you want something like this, you will probably need to tweak this rather than use it as-is.

+begin_src emacs-lisp

(defun noct-open () "Open the thing at point. Try with lsp or smart jump (if in a prog-mode buffer) then with hyperbole." (interactive) (or (when (derived-mode-p 'prog-mode) (cond ((bound-and-true-p lsp-mode) (not (stringp (lsp-find-definition)))) ((fboundp 'smart-jump-go) ;; return nil instead of prompting when there is no definition ;; at point (cl-letf (((symbol-function 'xref--prompt-p) #'ignore)) (smart-jump-go))))) (when (fboundp 'action-key) (action-key))))

(setq link-hint-action-fallback-commands (list :open #'noct-open))

+end_src

** 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~:

+begin_src emacs-lisp

(setq link-hint-avy-style 'pre)

+end_src

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:

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-= (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:

+begin_src emacs-lisp

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

+end_src

All link hint types are defined in this way, so see the source code for more examples.

*** Mandatory Keywords =:next=

=:at-point-p=

*** Predicate Keywords These keywords are used to determine when a type is active. If these keywords are specified, link-hint will only check for the link type if the buffer meets the requirements. These are not strictly necessary but can be used, for example, to help performance (this is usually not an issue except for "overlay button" links currently - woman buttons, dictionary mode buttons, etc.).

=:predicates= should be a list of functions that should each return true if the link type passes/is valid in the current buffer.

=: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=. Custom action keywords can have any name not already used by link-hint, but you may want to give your keywords some unique prefix to ensure they do not clash in case link-hint adds new action types (e.g. =:my-=).

=:= (e.g. =:open=)

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

=:-multiple= should be a boolean value corresponding to whether it makes sense to perform the action on multiple links in a row.

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

+begin_src emacs-lisp

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

+end_src

** 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. [[https://github.com/tmux/tmux][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:

+begin_src shell

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

+end_src

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.