Skip to content

misohena/org-link-completion

Repository files navigation

In-buffer completion for org-mode links

(This document was machine translated from README-ja.org)

Overview

This Emacs Lisp library allows completion at various points in a link in org-mode using the completion-at-point (M-TAB, C-M-i, or ESC TAB) command.

You can complete almost anywhere within a link. By default, the following locations can be completed:

  • [[ /link-type/ :
  • [[ /searchtarget/
  • [[# /custom-id/
  • [[# custom-id ][ /description/
  • [[* /heading/
  • [[* heading ][ /description/
  • [[( /coderef/ )
  • [[( coderef )][ /description/
  • [[ /search target/
  • [[ search target ][ /description/
  • [[ ./dir/file
  • [[ ../dir/file
  • [[ /dir/file
  • [[ ~/dir/file
  • [[ ~USER/dir/file
  • [[ c:/dir/file
  • [[ \dir\file
  • [[/dir/file:: /search target/ (Include ./ ../ / ~ c:/ \)
  • [[/dir/file::# /custom-id/
  • [[/dir/file::* /heading/
  • [[/dir/file::( /coderef/
  • [[/dir/file][ /description/
  • [[file: /file/
  • [[file+sys: /file/
  • [[file+emacs: /file/
  • [[file: file:: /search target/ (Include file+sys: file+emacs:)
  • [[file: file::# /custom-id/
  • [[file: file::* /heading/
  • [[file: file::( /coderef/
  • [[file: file ][ /description/
  • [[id: /id/
  • [[id: id ][ /description/
  • [[help: /function-or-variable/
  • [[help: function-or-variable ][ /description/
  • [[elisp: /expression/
  • [[elisp: expression ][ /description/
  • [[info: /infofile/
  • [[info: infofile # /nodename/
  • [[info: infofile # nodename ][ /description/
  • [[ unknown-type : /path/
  • [[ unknown-type : path ][ /description/

Completion candidates are collected from the link destination and its surroundings, other similar links, syntactic constraints, the path part of the link (when completing the description part), links added to favorites, etc.

Completion functions can be changed and added to all of these parts.

You can freely add completion functions even for unsupported file types.

Another library that uses this library to provide completion functions is:

org-elisp-link.el
Define link types that refer to definition of Emacs Lisp language elements ( elisp-library:, elisp-function:, elisp-variable:, elisp-face:). In addition to standard properties such as :follow, :export, :store, :activate-func, :complete, this library also defines :capf-path and :capf-desc properties.

Also, as a usage example, I’ll show you a function I use later that completes links to blog posts.

examples/links.org is provided so that you can try out various link completions.

Setup

Place org-link-completion.el in load-path and add the following code to init.el.

(autoload 'org-link-completion-setup "org-link-completion" nil t)
(with-eval-after-load "org"
  (org-link-completion-setup))

You can also look at the contents of the org-link-completion-setup function and extract and set up only the necessary elements. For example:

(with-eval-after-load "org"
  ;; Add a completion function to the org-mode buffer.
  (add-hook 'org-mode-hook
            (lambda ()
              (add-hook 'completion-at-point-functions
                        'org-link-completion-at-point nil t)))

  ;; Add completion for the path and description parts of file:, file+sys:, file+desc.
  (dolist (type '("file" "file+sys" "file+emacs"))
    (org-link-set-parameters
     type
     :capf-path 'org-link-completion-path-file
     :capf-desc 'org-link-completion-desc-file)))

Functions called by org-link-completion-at-point function

The org-link-completion-at-point function is called first when completing a link.

This function first analyzes the area around the point and determines the location of each part of the link and which part the point points to.

In this library, each part of the link is classified as follows.

[[<type>:<path>][<description>

Determine the position of each of these parts within the buffer and where the point is located. (This information is retained in the org-link-completion-pos variable during analysis)

Then, based on this information, determine and call a function that performs more specific processing as shown below.

  • Point is in <type> => call org-link-completion-type-function variable (default: org-link-completion-type function)
  • <type> is empty:
    • Point is in <path> => call org-link-completion-path-untyped-function variable (default: org-link-completion-path-untyped function)
    • Point is in <desc> => call org-link-completion-desc-untyped-function variable (default: org-link-completion-desc-untyped function)
  • <type> is a valid link type (defined in org-link-parameters variable): Call the function set to the following properties of the org-link-parameters variable:
    • Point is in <path> => :capf-path property of link type <type>
    • Point is in <desc> => :capf-desc property of link type <type>
    • If the above properties are missing => :completino-at-point property (The function set for this property must change its behavior depending on the part where the point is.)
  • No completion function found for <type>:
    • point is on <path> => call org-link-completion-path-unknown-type-function variable (default: org-link-completion-path-unknown-type function)
    • point is on <desc> => call org-link-completion-desc-unknown-type-function variable (default: org-link-completion-desc-unknown-type function)

No arguments are passed to functions called from the org-link-completion-at-point function. However, it caches the parsed information in the org-link-completion-pos variable before calling it. The called function can refer to that information or ignore it and re-analyze it as needed.

The called function must return the same format as the function registered with completion-at-point-functions. Please refer to the elisp manual for details.

Completion in Buffers (GNU Emacs Lisp Reference Manual) (ayatakesi’s Japanese translation (29.2))

Parsing links and getting results

Link analysis is performed by the org-link-completion-parse-at-point function.

This function takes no arguments, looks before and after the point, and returns the region where the point is and the range of each region before the point.

(WHERE TYPE-BEG TYPE-END [ PATH-BEG PATH-END [ DESC-BEG DESC-END ] ])
;; WHERE ::= type | path | desc

Functions called from the org-link-completion-at-point usually do not need to call this function directly. The cached result is stored in the org-link-completion-pos variable, so you can retrieve it from there. However, it is possible to write code for the case where there are no cached values.

(when-let ((pos (or org-link-completion-pos
                    ;; If there is no cache, analyze it yourself
                    (org-link-completion-parse-at-point))))
  ;; Processing that returns completion candidates
  )

Use dedicated accessor macros to retrieve each element of the analysis result.

(when-let ((pos (or org-link-completion-pos
                    (org-link-completion-parse-at-point))))
  (let ((where (org-link-completion-pos-ref pos where)) ;; Expands to (nth 0 pos)
        (path-beg (org-link-completion-pos-ref pos path-beg)) ;; Expands to (nth 3 pos)
        (path-end (org-link-completion-pos-ref pos path-end))) ;; Expands to (nth 4 pos)
    (when (eq where 'path)
      (list
       path-beg path-end
       ;; Write a list of suggestions here
       ))))

Macros are also available to make writing these processes easier. The following code is equivalent to the above.

(org-link-completion-parse-let :path (path-beg path-end)
  (list
   path-beg path-end
   ;; Write a list of suggestions here
   ))

Example of creating a link type for your own blog

I’m using Org2blog to write a blog, and I’ve defined a special link type to represent links to blog posts. Using this, I can write the following in an org-mode file.

I previously wrote an article called [[blog:2024-02-23-org-link-completion-at-point][Completion in buffer in link part of org-mode]].

Pressing C-c C-o on this link will jump to that org file, and exporting will output the URL on the web. It also supports storing links with C-c l, as well as completing paths and generating default values for descriptions when using C-c C-l.

However, it did not support completion within the buffer, that is, completion-at-point. So I’ll try to accommodate that.

Blogs are managed in the following list:

(defvar my-blog-list
  '((:link-type "blog"
                :post-url "https://example.com/blog/%s.html"
                :local-dir "~/org/blog/"
                :title "My Main Blog")
    (:link-type "subblog"
                :post-url "https://example.com/subblog/%s.html"
                :local-dir "~/org/subblog/"
                :title "My Sub Blog")))

(defun my-blog-from-link-type (link-type)
  "Return blog information from link type in org-mode."
  (when (stringp link-type)
    (seq-find (lambda (blog)
                (string= (plist-get blog :link-type) link-type))
              my-blog-list)))

Since there are multiple blogs, multiple blogs can be defined in my-blog-list. One uses the link type blog: and the other uses the link type subblog: (:link-type property).

The original blog files are written in org-mode, and are all stored under a specific directory (:local_dir property) with file names that include the permalink name with an extension (.org).

Therefore, to complete the path part of the link, it seems to be a good idea to enumerate the .org files from the directory where the original blog file is stored, and remove the extension from the file name and use it as a completion candidate. The following code does that.

(defun my-org-blog-link-capf-path ()
  "Complete the path part of the link on point.

I expect it to be called when you press C-M-i somewhere like this:
     [[blog:<permalink>(here)
     [[subblog:<permalink>(here)"
  (org-elisp-link-capf-parse-let :path (type path-beg path-end)
    (let ((blog (my-blog-from-link-type type)))
      (when blog
        (list
         path-beg path-end
         (cl-loop for file in (directory-files (plist-get blog :local-dir))
                  when (string-match "\\`\\(.+\\)\\.org\\'" file)
                  collect (match-string 1 file))
         :company-kind (lambda (_) 'file))))))

Registering this function in org-link-parameters enables completion using C-M-i for the path part of blog: links.

(dolist (blog my-blog-list)
  (org-link-set-parameters (plist-get blog :link-type)
                           :capf-path #'my-org-blog-link-capf-path))

Next, I’ll implement completion for the description part. What kind of candidates should be provided for the description part? I thought that I would like the titles of the posts to be completed. In addition to two types of candidates, those with blog titles and those without, I also plan to include the original permalinks as candidates.

(defun my-org-blog-link-capf-desc ()
  "Complete the description part of the link on the point.

I expect it to be called when you press C-M-i somewhere like this:
     [[blog:<permalink>][<description>(here)
     [[subblog:<permalink>][<description>(here)"
  (org-elisp-link-capf-parse-let :desc (type path desc-beg desc-end)
    (let* ((blog (my-blog-from-link-type type)))
      (when blog
        (let* ((title (let* ((dir (plist-get blog :local-dir))
                             (file (expand-file-name (concat path ".org") dir)))
                        (my-org-blog-org-file-title file))))
          (list
           desc-beg desc-end
           (append
            (when title
              (list title
                    (concat title " | " (plist-get blog :title))))
            (list path))))))))

(defun my-org-blog-org-file-title (file)
  "Get title from FILE written in org-mode."
  (when (file-regular-p file)
    (with-temp-buffer
      (insert-file-contents file nil nil 16384) ;; It's probably near the top.
      (goto-char (point-min))
      (let ((case-fold-search t))
        (when (re-search-forward
               "^#\\+TITLE: *\\(.*\\)$" nil t)
          (match-string-no-properties 1))))))

I extracted the title of the post from the beginning of the .org file, where it says #+TITLE:. Although this code does not do this, if it is opened in Emacs, it may be a good idea to also extract it from the buffer.

Register this in org-link-parameters as before.

(dolist (blog my-blog-list)
  (org-link-set-parameters (plist-get blog :link-type)
                           :capf-desc #'my-org-blog-link-capf-desc))

Other operations (:follow, :store, :export, :complete, :insert-description) are omitted as they are outside the purpose of this library. Please feel free to write as you like.

License

This software is licensed under GPLv3. You are free to use, modify and distribute this software.

If you wish to register this software in any package archive, please fork this repository, make the necessary modifications to fit the package archive’s requirements, and submit the registration on your own. Also continue with the necessary maintenance. You don’t need my permission.

I also welcome you to publish your improved version. If that works better than mine, I might start using it too. I may suddenly be unable to develop, and I cannot guarantee any continued development. This software is the result of what I want, so please add what you want yourself.

I am not proficient in English, so please do not expect continuous communication in English.

About

Complete the link type, path and description part of links at point in org-mode buffer.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published