Kungsgeten / yankpad

Paste snippets from an org-mode file
MIT License
215 stars 18 forks source link

+TITLE:yankpad.el [[https://melpa.org/#/yankpad][file:https://melpa.org/packages/yankpad-badge.svg]]

Let's say that you have text snippets that you want to paste, but that [[https://joaotavora.github.io/yasnippet/][yasnippet]] or [[https://www.emacswiki.org/emacs/SkeletonMode][skeleton]] is a bit too much when you do not need a shortcut/abbrev for your snippet. You like org-mode, so why not write your snippets there? Introducing the yankpad:

+BEGIN_SRC org

,* Category 1

,** Snippet title

  Here's a text snippet I want to insert.

,** Snippet with keybinding :last:tag:is:key:o:

  And here's another snippet. This snippet has tags, and the last of these
  tags should be a key. This will bind the snippet to the key (in this case
  "o") when first calling yankpad-map.

,** expandword: Snippet with keyword expansion

  This snippet has a keyword; "expandword" in this case. If this category is
  active, and you type the keyword into a buffer and use the "yankpad-expand"
  command, the keyword will be replaced with this snippet.

,** more:expands: Multiple keywords

  A snippet can have more than one keyword. This has both "more" and
  "expands".

,** Regex expands :props: :PROPERTIES: :YP_EXPAND_REGEX: number([[:digit:]]+) :END:

 If you use the :props: tag the property drawer will not be included in the
 snippet. Instead the snippet can include information used by Yankpad.

 In this case we have set the property YP_EXPAND_REGEX which can be used instead
 of the expand keyword. YP_EXPAND_REGEX should be a regular expression, and when
 you use "yankpad-expand" the regex will be replaced with the snippet.

 The cool thing here is that the entire snippet text is then, before
 expansion, sent into the Emacs "format" function, with the OBJECTS argument
 set to the match groups in the regex. %s

 In this example, the "percent s" at the end of the last paragraph will be
 replaced with the digit matched by the regex. So if I write "number12" and
 use "yankpad-expand" the "percent s" will be replaced with 12.

,* Category 2

Descriptive lists will be treated as snippets. You can set them to be treated
as =abbrev-mode= abbrevs instead, by setting
=yankpad-descriptive-list-treatment= to abbrev. If a heading could be
considered to be a snippet, add the =snippetlist= tag to ignore the snippet
and scan it for descriptive lists instead.

- name :: Erik Sjöstrand
- key :: Typing "key" followed by `yankpad-expand' will insert this snippet.

,** Descriptive list example 2 :snippetlist:

 This heading would normally be considered a snippet, but because of the
 =:snippetlist:= tag, it is scanned for descriptive lists instead.

 - foo :: bar

,** Explaining categories

  This snippet belongs to another category (named =Category 2=). Categories
  are useful if you need several yankpads, for instance if you're a teacher
  (like me) working with different courses.

,** yasnippet magic

  If you have yasnippet installed (not a requirement), the content in each
  snippet is actually executed by yasnippet! This means that you could run
  elisp inside your snippets: `(+ 3 4)` and have handy tab stop fields.

  | Student | Grade |
  |---------+-------|
  | $1      | $2    |

  That's pretty handy!
  $0

,** [[file:my_other_snippets.org]]

 If a heading has a link to another org-file, that file will be scanned for
 snippets. Those snippets are then appended to the category.

,* [[file:misc_snippets::Search]]

 You can specify a specific headline in another file, which you want to be
 searched for snippets. It could be a single snippet, or it could have
 subtrees (in which case all of them will be considered as snippets).

,** [[id:38e4c8d2-5ab0-4e78-8e43-ea4a918e5c02]]

 You can also provide the ID of a specific org-mode headline.

,** Code snippet examples

  You can organize your snippets inside a category by using subtrees, like
  this one. Only headings without children are considered as snippets.

,*** "Litterate programming" snippet :src:

   Tagging a snippet with src says that only the content of source blocks
   should be expanded. All other text (like this paragraph) is ignored.

   ,#+BEGIN_SRC emacs-lisp
   (message "This is part of the snippet")
   ,#+END_SRC

   If you have several source blocks, their content will be concatenated.

   ,#+BEGIN_SRC emacs-lisp
   (message "This is also part of the snippet!!!")
   ,#+END_SRC

,*** The source block below will be executed if tag is func :func: ,#+BEGIN_SRC emacs-lisp ;; Instead of a src-block, the snippet may be named ;; the same as an emacs-lisp function. This will then ;; be executed without arguments (see next example). (elfeed) ,#+END_SRC

,** elfeed :func:e:

,* Kitchen sink category :PROPERTIES: :INCLUDE: Category 1|Category 2 :END:

,** Include other categories

Snippets from Category 1 and Category 2 will be appended to this category. This is done by setting the INCLUDE property of the category. Categories are separated by a pipe.

,* org-mode

,** Major-mode categories

  If you have a category with the same name as a major-mode, that category will be
  activated when switching major-mode. This only affects the local buffer and does
  not modify the global category.

,* my-project

,** project.el based categories

 If you haven a project.el project you can give a category the same name as
 the project.

,** Projectile based categories

 If you have projectile installed (not a requirement) you can give a category
 the same name as one of your projectile projects. That category will be
 activated when using projectile-find-file on a file in the project.

,* Global category :global: ,** Always available

  Snippets in a category with the :global: tag are always available for
  expansion.

,* Default :global: ,** Fallback for major-mode categories

 If you open a file, but have no category named after its major-mode, a
 category named "Default" will be used instead (if you have it defined in your
 Yankpad). It is probably a good idea to make this category global. You can
 change the name of the default category by setting the variable
 yankpad-default-category.

+END_SRC

  1. Install =yankpad= from Melpa, or download =yankpad.el= and add it to your load-path and require it.
  2. The default location for the yankpad file is =yankpad.org= in your =org-directory=. This can be changed by modifying the =yankpad-file= variable.
  3. Optionally bind =yankpad-map=, =yankpad-insert=, and/or =yankpad-expand= to a key.
  4. Optionally install =yasnippet= and/or =projectile= and/or =company-mode=, if you want the additional yankpad features that those package provide.
  5. That's it!

If you want different heading levels for the categories (default 1), change the value of =yankpad-category-heading-level=. You can also change the tag which defines categories as global, by modifying =yankpad-global-tag=. The name of the major-mode fallback category can be changed by modifying =yankpad-default-category=.

At the beginning of your snippet title you may have a list of keywords. These keywords are separated by colons (=:=). For the most part you probably only need one keyword, like =hello: Greetings!=, but you may have several keywords for the same snippet: =hello:hi: Greetings!=. You can change =:= into another string by changing the =yankpad-expand-separator= variable.

Here's an example setup using the excellent [[https://github.com/jwiegley/use-package][use-package]]:

+BEGIN_SRC emacs-lisp

(use-package yankpad :ensure t :defer 10 :init (setq yankpad-file "~/yankpad.org") :config (bind-key "" 'yankpad-map) (bind-key "" 'yankpad-expand) ;; If you want to complete snippets using company-mode (add-to-list 'company-backends #'company-yankpad) ;; If you want to expand snippets with hippie-expand (add-to-list 'hippie-expand-try-functions-list #'yankpad-expand))

+END_SRC

  1. Add snippet entries to your =yankpad-file=. Level 1 headings are considered to be categories (by default). Also descriptive lists are treated as snippets by default (except if they're in a heading without children, in which case the heading needs a =:snippetlist:= if it should be scanned for descriptive lists). A quick way to open your =yankpad-file= is to use =M-x yankpad-edit=. You can also add snippets to the current =yankpad-category= by using =M-x yankpad-capture=, or with =M-x yankpad-aya-persist= if you're a [[https://github.com/abo-abo/auto-yasnippet][auto-yasnippet]] user.
  2. Insert a snippet with =M-x yankpad-insert=. If the snippet has a keyword (it starts with a word followed by a colon), you can write that keyword into the buffer and use =M-x yankpad-expand= instead. It may be useful to bind these commands to some key on your keyboard. You can also use =company-yankpad= to expand a snippet using =company-mode= (thanks [[https://github.com/sid-kurias][sid-kurias]] for contributing). If you want to insert the last snippet again, you can use =M-x yankpad-repeat= (bind that to a key if you're using it frequently).
  3. If you want to change category, use =M-x yankpad-set-category=. If you have a category with the same name as a major-mode (for instance =org-mode=), that category will be locally set when switching major-mode. In the same manner you can name a category to one of your =project.el= or /Projectile/ project names (if Projectile is installed). If both cases are true, the project category becomes active, but the snippets from the major mode are appended as well. If you later change category with =M-x yankpad-set-category=, the major-mode and project snippets will be appended to the chosen category.
  4. If you want to append snippets from another of your categories (basically like having two or more categories active at the same time), use =M-x yankpad-append-category=. If you want one of your categories to /always/ include snippets from another category; set the =INCLUDE= [[https://orgmode.org/manual/Property-syntax.html#Property-syntax][property]] of the category heading (several categories can be included this way, by separating them with =|=, see example at the top of this readme).
  5. To quickly open your =yankpad-file= for editing, run =M-x yankpad-edit=.
  6. Yankpad caches your snippets, making it a bit snappier to insert snippets from the yankpad. If you've edited your =yankpad-file= you might want to use =M-x yankpad-reload= to clear the snippet cache and reload your snippets in the current category.

Since a == at the beginning of a line would specify a new heading, lines can not begin with ==. However, you can write =*= at the beginning of a line, which will be replaced by a =*= when expanding the snippet. If you use this in order to yank snippets into an =org-mode= buffer, the new headings will be automatically indented -- depending on the current level -- by default. This can be changed by setting the variable =yankpad-respect-current-org-level= to =nil=, or by using special tags. Another approach is to encapsulate the snippet text in an /org src block/ and tag the snippet with =src= (see /Special tags/ below).

Sometimes it may be useful to set the category automatically for a specific file. In this case you can add =yankpad-category= as a [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Specifying-File-Variables.html][file variable]], for instance by adding this line at the top of your file:

+BEGIN_SRC

-- yankpad-category: "Category name"; --

+END_SRC

You can also set the =yankpad-category= to =nil= in this way, if you do not want any default category triggered for that file.

There's a macro called =yankpad-map-simulate= which can be used if you want a command which presses a specific key inside =yankpad-map=, for instance if you want a special keybinding for a specific snippet. The macro will create a command named =yankpad-map-press-=. Here's an example of how you could create a command and bind it to a key:

+BEGIN_SRC emacs-lisp

(global-set-key (kbd "") (yankpad-map-simulate "j"))

+END_SRC

Now pressing =f5= would trigger the snippet bound to =j= inside =yankpad-map=.

Snippets in your Yankpad can have tags, and some of these have special meanings:

If a snippet has a property drawer, and the =:props:= tag, the drawer will be removed from the snippet text and the properties will be stored in the snippet. At the moment there's only one property that has an effect on Yankpad's behaviour, but more might be added in the future.

You could add your own special properties using =yankpad-before-snippet-hook=. This hook is run before a snippet is inserted, and the hook functions should take the snippet as their only argument. A snippet is a list with four elements: =(snippet name, a list of tags, content, an alist of properties)=. If you use =setf= on the snippet, you can change it before expansion. Here's an example that would upcase a snippet if it includes the =UPCASE= property:

+BEGIN_SRC emacs-lisp

(defun yp/upcase-snippet (snippet) ;; Check if we have a property named UPCASE ;; (nth 4 snippet) holds all the properties (when (assoc "UPCASE" (nth 4 snippet)) ;; (nth 3 snippet) is the snippet content, let's upcase it! (setf (nth 3 snippet) (upcase (nth 3 snippet)))))

(add-hook 'yankpad-before-snippet-hook 'yp/upcase-snippet)

+END_SRC

If you set =yankpad-descriptive-list-treatment= to ='abbrev=, descriptive lists inside =yankpad= categories will be handled by =abbrev-mode= instead of being considered as snippets.