/emacs.d

My emacs setup

Primary LanguageEmacs Lisp

EMACS

This is possibly my 5th attempt to configuring Emacs. My previous attempts were around:

  • Spacemacs
  • Own configuration based on use-package in literal mode.
  • Own configuration based on straight.el in pure elisp mode.
  • Doom Emacs

    I decided to move back to literate style configuration for the following reasons:

    • Distros like Spacemacs and Doom Emacs are easy to start, but using but really hard to tune and troubleshoot.
    • I am a `one file` kind of a person (vs multiple files scatered around).
    • The literate style allows you to create and define more than `elsip`.
      • Shell scripts
      • Tangle other configuration files
      • Examples
    • Reads better
    • Easiest to move things around.

    The pillars of this build are the following:

Bootstrap

Lexical binding

Lexical Binding

;;; -*- lexical-binding: t; -*-

Core

Settings

One of the things like using Doom Emacs is that all non-versioned files (caches, worskapces, runtime data) went under `.local`. Let’s do the same and define `my/local-dir` that we’ll use all across the board.

(defvar my/local-dir (concat user-emacs-directory ".local/") "Local state directory")

Warnings

(setq warning-minimum-level :emergency)

Package Manager

Elpaca

Elpaca is an elisp package manager. It allows users to find, install, update, and remove third-party packages for Emacs. It is a replacement for the built-in Emacs package manager, package.el Github: https://github.com/progfolio/elpaca

Installer
(defvar elpaca-installer-version 0.5)
(defvar elpaca-directory (expand-file-name "elpaca/" my/local-dir))
(defvar elpaca-builds-directory (expand-file-name "builds/" elpaca-directory))
(defvar elpaca-repos-directory (expand-file-name "repos/" elpaca-directory))
(defvar elpaca-order '(elpaca :repo "https://github.com/progfolio/elpaca.git"
                              :ref nil
                              :files (:defaults (:exclude "extensions"))
                              :build (:not elpaca--activate-package)))
(let* ((repo  (expand-file-name "elpaca/" elpaca-repos-directory))
       (build (expand-file-name "elpaca/" elpaca-builds-directory))
       (order (cdr elpaca-order))
       (default-directory repo))
  (add-to-list 'load-path (if (file-exists-p build) build repo))
  (unless (file-exists-p repo)
    (make-directory repo t)
    (when (< emacs-major-version 28) (require 'subr-x))
    (condition-case-unless-debug err
        (if-let ((buffer (pop-to-buffer-same-window "*elpaca-bootstrap*"))
                 ((zerop (call-process "git" nil buffer t "clone"
                                       (plist-get order :repo) repo)))
                 ((zerop (call-process "git" nil buffer t "checkout"
                                       (or (plist-get order :ref) "--"))))
                 (emacs (concat invocation-directory invocation-name))
                 ((zerop (call-process emacs nil buffer nil "-Q" "-L" "." "--batch"
                                       "--eval" "(byte-recompile-directory \".\" 0 'force)")))
                 ((require 'elpaca))
                 ((elpaca-generate-autoloads "elpaca" repo)))
            (kill-buffer buffer)
          (error "%s" (with-current-buffer buffer (buffer-string))))
      ((error) (warn "%s" err) (delete-directory repo 'recursive))))
  (unless (require 'elpaca-autoloads nil t)
    (require 'elpaca)
    (elpaca-generate-autoloads "elpaca" repo)
    (load "./elpaca-autoloads")))
(add-hook 'after-init-hook #'elpaca-process-queues)
(elpaca `(,@elpaca-order))
use-package

Configure elpace `use-package` integration so that the rest of the configuration just uses `use-package`.

(elpaca elpaca-use-package
  ;; Enable :elpaca use-package keyword.
  (elpaca-use-package-mode)
  ;; Assume :elpaca t unless otherwise specified.
  (setq elpaca-use-package-by-default t))

;; Block until current queue processed.
(elpaca-wait)
use-feature

There are cases where we want to use `use-package` with internal packages. In these cases `:elpaca nil` needs to be set. Let’s create a macro `use-feature` that combines `use-package` with `:elpaca nil` nicely.

Source: https://github.com/progfolio/.emacs.d/blob/master/init.org

(defmacro use-feature (name &rest args)
  "Like `use-package' but accounting for asynchronous installation.
  NAME and ARGS are in `use-package'."
  (declare (indent defun))
  `(use-package ,name
     :elpaca nil
     ,@args))

File Manager

Dired

(use-feature dired
:config
(setq dired-dwim-target t))

Dired Narrow

(use-package dired-narrow
:after dired
:commands (dired-narrow dired-narrow-fuzzy dired-narrow-regexp)
:bind (:map dired-mode-map 
            ("C-c C-n" . dired-narrow)
            ("C-c C-f" . dired-narrow-fuzzy)
            ("C-c C-N" . dired-narrow-regexp)))

Dired Subtree

(use-package dired-subtree
  :after dired
  :init
  (defun my/dired-expand-all ()
    (interactive)
    "Expand all subtrees in the dired buffer."
    (let ((has-more t))
      (while has-more
        (condition-case ex
            (progn
              (dired-next-dirline 1)
              (dired-subtree-toggle))
          ('error (setq has-more nil))))))
  :commands (dired-subtree-toggle dired-subtree-cycle)
  :general (:keymaps dired-mode-map 
              "<tab>" 'dired-subtree-toggle
              "S-<tab>" 'my/dired-expand-all
              "<backtab>" 'dired-subtree-cycle))

General

(use-package general
  :demand t
  :config
  (general-override-mode)
  (general-auto-unbind-keys))
(elpaca-wait)
(general-create-definer leader-key!
  :states '(insert normal hybrid motion visual operator emacs)
  :keymaps 'override
  :prefix "SPC" ;; set leader
  :global-prefix "M-SPC")

Evil Mode

Package

(use-package evil
  :custom
  (evil-symbol-word-search t "search by symbol with * and #.")
  (evil-shift-width 2 "Same behavior for vim's '<' and '>' commands")
  (evil-want-C-i-jump t)
  (evil-complete-all-buffers nil)
  (evil-want-keybinding nil)
  (evil-want-integration t)
  (evil-search-module 'evil-search "use vim-like search instead of 'isearch")
  (evil-undo-system 'undo-redo)
  :config
  (setq evil-want-fine-undo nil) ;; Fix issue with undo granularity (See: https://github.com/syl20bnr/spacemacs/issues/2675)
  (evil-mode))

Evil Collection

Evil related stuff for misc modes (e.g. Dired).

(use-package evil-collection
  :elpaca (:remotes ("fork" :repo "progfolio/evil-collection"))
  :after (evil)
  :custom
  (evil-collection-elpaca-want-g-filters nil)
  (evil-collection-setup-minibuffer t "Add evil bindings to minibuffer")
  (evil-collection-company-use-tng t)
  (evil-collection-ement-want-auto-retro t)
  :init
  (evil-collection-init))

Utilities

Mark

Credits: https://emacs.stackexchange.com/questions/15033/how-to-mark-current-line-and-move-cursor-to-next-line

(defun my/mark-line (&optional arg)
  "Select the current line and move the cursor by ARG lines IF no region is selected.
If a region is already selected when calling this command, only move
the cursor by ARG lines."
  (interactive "p")
  (let ((lines (or arg 1)))
    (when (not (use-region-p))
      (forward-line 0)
      (set-mark-command nil))
    (forward-line lines)))

Libraries

f.el

(use-package f :demand t)
;; As this is asynchronous let's call `elpaca-await` to ensure that f.el
;; is available for use in my emacs configuration
(elpaca-wait) 

emacsql

(use-package emacsql :elpaca (emacsql :host github :repo "magit/emacsql" :branch "main"))

Editor

Autorevert

Ensure that we always see the actual file content.

(global-auto-revert-mode 1)

Autosave

(use-package real-auto-save
  :ensure t ;; Won't work if we defer.
  :config
  (setq real-auto-save-interval 10
        auto-save-file-name-transforms `((".*" ,(concat my/local-dir "autosaves/") t))
        backup-directory-alist `(("." . ,(concat my/local-dir "backups/")))
        backup-by-copying t    ; Don't delink hardlinks
        version-control t      ; Use version numbers on backups
        delete-old-versions t  ; Automatically delete excess backups
        kept-new-versions 20   ; how many of the newest versions to keep
        kept-old-versions 5    ; and how many of the old
        create-lockfiles nil)
  (global-auto-revert-mode 1)
  :hook ((text-mode . real-auto-save-mode)
         (prog-mode . real-auto-save-mode)
         (snippet-mode . (lambda () (real-auto-save-mode -1)))))

Expand Region

(use-package expand-region
  :general ("C-q" 'er/expand-region))

Exit confirmation

(setq confirm-kill-emacs 'y-or-n-p)
(defalias 'yes-or-no-p 'y-or-n-p)

Save history

Persists history over Emacs restarts

(use-feature savehist
  :init
  (savehist-mode))

Identation

(setq-default indent-tabs-mode nil)
(setq electric-indent-inhibit t)

Undo

(use-package undo-tree
  :config
  (setq undo-tree-auto-save-history t
        undo-tree-history-directory-alist `(("." . ,(concat my/local-dir "undo/"))))
  :hook ((text-mode . undo-tree-mode)
         (prog-mode . undo-tree-mode))
  :general
  (:states 'normal
           "u" 'undo-tree-undo
           "U" 'undo-tree-redo))

Popup buffers

(use-package popper
  :defer t 
  :commands (my/shell-pop-up-frame-enable my/shell-pop-up-frame-disable my/kill-if-popup)
  :init
  (setq popper-reference-buffers
        '(
          "\\*Messages\\*"
          "\\*Warnings\\*"
          "\\*Backtrace\\*"
          "\\*Flycheck errors\\*"
          "\\*Flymake diagnostics for .*\\*"
          "\\*Async Shell Command\\*"
          "\\*.*compilation.*\\*"
          "\\*Org QL View: Github issues for .*\\*"
          "\\*eshell.*\\*"
          "\\*shell.*\\*"
          "\\*vterm.*\\*"
          "\\*scratch.*\\*"
          "\\*undo-tree*\\*")
        popper-mode-line (propertize " π " 'face 'mode-line-emphasis))
  :config
  (defun my/shell-pop-up-frame-enable()
    "Make shell windows pop-up frame."
    (interactive)
    (setq display-buffer-alist (add-to-list 'display-buffer-alist `("\\*\\(eshell.*\\|shell.*\\|vterm.*\\)\\*"
                                                                    (display-buffer-reuse-window display-buffer-pop-up-frame)
                                                                    (reusable-frames . visible)
                                                                    (window-height . 0.40)
                                                                    (side . bottom)
                                                                    (slot . 0)))))

  (defun my/shell-pop-up-frame-dissable()
    "Make shell windows pop-up use window."
    (interactive)
    (setq display-buffer-alist (add-to-list 'display-buffer-alist `("\\*\\(eshell.*\\|shell.*\\|vterm.*\\)\\*"
                                                                    (display-buffer-in-side-window)
                                                                    (window-height . 0.40)
                                                                    (side . bottom)
                                                                    (slot . 0)))))


  (defadvice switch-to-buffer (around my/switch-to-buffer-pop-to-buffer (buffer-or-name &optional norecord force-same-window))
    (pop-to-buffer buffer-or-name :norecord norecord))

  (defmacro my/use-pop-to-buffer (&rest body)
    "Intercept switch-to-buffer and delegate to pop-to-buffer while evaluating BODY."
    (declare (indent 1) (debug t))
    `(let ()
      (ad-enable-advice 'switch-to-buffer 'around 'my/switch-to-buffer-pop-to-buffer)
      (ad-activate 'switch-to-buffer)
      ,@body
      (ad-disable-advice 'switch-to-buffer 'around 'my/switch-to-buffer-pop-to-buffer)
      (ad-activate 'switch-to-buffer)))
  ;;
  ;; The command below is used to kill popup buffers.
  ;; The idea is that the function will bind to `q` and 
  ;; kill the buffer is buffer is a popup or otherwise record marco.
  ;;
  (defun my/kill-if-popup (register)
    "If the buffer is a pop-up buffer kill it, or record a macro using REGISTER otherwise."
    (interactive
     (list (unless (or (popper-popup-p (current-buffer)) (and evil-this-macro defining-kbd-macro))
             (or evil-this-register (evil-read-key)))))
    "Kill the currently selected window if its a popup."
    (if (popper-popup-p (current-buffer))
        (popper-kill-latest-popup)
      (evil-record-macro register)))
  :general (:states 'normal
                    "q" 'my/kill-if-popup)
  :hook ((eshell-mode . popper-mode)
         (vterm-mode . popper-mode)
         (undo-tree-mode . popper-mode)
         (helm-ag-mode . popper-mode)
         (flycheck-error-list-mode . popper-mode)
         (flymake-mode . popper-mode)))

Snippets

(use-package yasnippet
  :after org
  :ensure t
  :init
  (defvar my/yas-snippets-loaded nil "Variable to track wether snippets have been loaded")
  (setq yas-snippet-dirs `(
                           ,(concat my/local-dir "snippets") ;; personal snippets
                           "~/.config/emacs/snippets"
                           "~/.config/emacs/templates")
        yas-indent-line 'fixed  ;; Use yas-indent-line fixed in yaml-mode. This fixes issues with parameter mirroring breaking indentation
        yas-prompt-functions '(yas-completing-prompt))

  (defun my/yas-set-org-buffer-local ()
    "Prevent org-mode snippets shadowing mode snippets in src blocks."
    (interactive)
    (setq-local yas-buffer-local-condition
                '(not (org-in-src-block-p t)))) 

  ;;
  ;; Configure org-src mode as extra mode for yassnippet
  ;;
  (defun my/yas-maybe-activate-org-src-mode (orig-func &rest args)
    "Enrich yas-extra-mode with mode from org-src block"
    (let* ((mode (if (and (eq major-mode 'org-mode) (fboundp 'org-in-src-block-p) (org-in-src-block-p)) (my/get-org-src-mode) nil))
           (yas-extra-modes (if mode (list (intern (concat mode "-mode"))) nil)))
      (apply orig-func args)))

  (advice-add 'yas--modes-to-activate :around #'my/yas-maybe-activate-org-src-mode)

  ;;
  ;; Ensure snippets loaded
  ;;
  (defun my/yas-ensure-snippets-loaded ()
    "Ensure that snippets have been loaded."
    (interactive)
    (when (not my/yas-snippets-loaded)
      (setq my/yas-snippets-loaded  t)
      (message "Loading yassnippets")
      (yas-reload-all))
    (yas-minor-mode-on))

  :commands (yas-reload-all yas-recompile-all yas-expand yas-insert-snippet)
  :hook ((prog-mode
          plantuml-mode
          org-mode) . my/yas-ensure-snippets-loaded))

UI

Appearance

All icons

(use-package all-the-icons :defer t)

Themes

Doom themes
(use-package doom-themes
  :config
  (when (display-graphic-p)
    (load-theme 'doom-one t)(setq mode-line-format nil)))

Bell

Disable the annoying bell

(setq visible-bell nil)
(setq ring-bell-function 'ignore)

Display Settings

(setq inhibit-message nil) ;; Changing that makes evil-search '/' invisible!
(setq inhibit-startup-message t)
(set-face-attribute 'default nil :height 150)

Mode line

Doom modeline
(use-package doom-modeline
  :init
  (setq doom-modeline-buffer-file-name-style 'truncate-upto-project
        doom-modeline-icon t
        doom-modeline-major-mode-icon t
        doom-modeline-major-mode-color-icon t
        doom-modeline-lsp t
        doom-modeline-column-zero-based t)
  :config
  (when
      (display-graphic-p)
    (doom-modeline-mode)
    (column-number-mode)))

Fonts

The configured font needs to support the unicode characters that are used by the modeline. The default font is good enough so let’s not define additonal configuration here.

  (push '(font . "Source Code Pro") default-frame-alist)
(set-face-font 'default "Source Code Pro")
(set-face-font 'variable-pitch "DejaVu Sans")
(copy-face 'default 'fixed-pitch)

Window Management

Ace Window

(use-package ace-window
  :custom
  (aw-keys '(?a ?s ?d ?f ?g ?h ?j ?k ?l))
  (aw-scope 'global)
  :general ("M-o" 'other-window))

Winner

(winner-mode)

Hydra

Package

(use-package hydra)

IDE

Eglot

Package

(use-feature eglot
  :custom
  (eglot-report-progress nil)
  :init
  (setq eglot-sync-connect 3
        eglot-connect-timeout 30
        eglot-autoshutdown t
        eglot-send-changes-idle-time 0.5
        eglot-events-buffer-size 0
        eglot-report-progress nil
        eglot-ignored-server-capabilities '(:documentHighlightProvider
                                            :foldingRangeProvider)
        ;; NOTE We disable eglot-auto-display-help-buffer because :select t in
        ;;      its popup rule causes eglot to steal focus too often.
        eglot-auto-display-help-buffer nil))

consult-eglot

(use-package consult-eglot)

Tree Sitter

Package

(use-package tree-sitter)

Languages

(use-package tree-sitter-langs)

Tree Sitter Automation

(use-package treesit-auto
  :config
  (global-treesit-auto-mode -1))
Utilities
(defun my/tree-sitter-parser (lang)
  "Create a parser for language."
  (let* ((language (tree-sitter-require lang))
         (parser (tsc-make-parser)))
    (tsc-set-language parser language)
    parser))

(defun my/tree-sitter-parse-str (lang str &optional parser)
  "Parse STR for the specified LAGN and return the root node."
  (let ((parser (or parser (my/tree-sitter-parser lang))))
    (with-temp-buffer
      (insert str)
      (tsc-parse-string parser str))))

(defun my/tree-sitter-query (query node)
  "Execute QUERY on NODE and return a sequence of captures."
  (tsc-query-captures query node nil))



(defun my/with-tree-sitter-query (lang query target caputres-function)
  "Perform a tree sitter QUERY on TARGET and pass matches to teh CAPTURES-FUNCTION.
TARGET is expected to be a file or a string containing code in the specified LANG."
  (let* ((language (tree-sitter-require 'java))
         (parser (or parser (my/tree-sitter-parser lang))))
    (with-temp-buffer 
      (cond
       ((file-exists-p target) (insert-file target))
       ((string-p target) (insert target))
       (:t (insert target)))
      (let* ((str (buffer-substring-no-properties (point-min) (point-max)))
             (tree (tsc-parse-string str))
             (root (tsc-root-node tree))
             (captures (tsc-query-captures query node nil)))
        (funcall matches-function captures)))))



(defun my/tree-sitter-get-enclosing (node type)
  "Get recursively parent of NODE until type is found."
  (cond
   ((not node) nil)
   ((eq (tsc-node-type node) type) node)
   (:t (my/tree-sitter-get-enclosing (tsc-get-parent node) type))))


(defun my/tree-sitter-get-mehtod-name()
  ""
  (interactive)
  (let* ((language (tree-sitter-require 'java))
         (parser (my/tree-sitter-parser 'java))
         (str (buffer-substring-no-properties (point-min) (point-max)))
         (tree-sitter-tree (tsc-parse-string parser str))
         (node (tree-sitter-node-at-pos))
         (method-declaration (my/tree-sitter-get-enclosing node 'method_declaration)))

    (message "Point: %s" (tsc-node-type node))
    (message "Parent: %s" (tsc-node-type method-declaration))
    (message "Named: %s" (tsc-node-text (tsc-get-child-by-field method-declaration :name)))))

Example

(defun my/tree-sitter-java-example ()
  "Example code using java and treesitter"
  (interactive)

  (let* ((language (tree-sitter-require 'java))
         (query (tsc-make-query (tree-sitter-require 'java)
                                [(class_declaration name: (identifier) @class_name)])))
         (with-temp-buffer 
            (insert "package org.acme; public class SomeClassName {}")
            (setq tree (my/tree-sitter-parse-str 'java "package org.acme; public class SomeClassName {}"))
            (setq node (tsc-root-node tree))
            (setq matches (my/tree-sitter-query query node))
            (message "Found class name: %s" (tsc-node-text (cdr (elt matches 0)))))))

Language

Org Mode

Package

(use-feature org
  :defer t
  :config
  (setq org-pretty-entities t
        org-hide-emphasis-markers t
        ;; Use yasnippets inside src blocks
        org-src-tab-acts-natively t
        org-src-fontify-natively t)

  :general (:states 'normal
                    :keymaps 'org-mode-map
                    "<tab>"  'org-cycle
                    "<backtab>"  'org-shiftab))

Getting things done

To implement my `Getting things done workflow` I am going to use two main files:

Extra inboxes may be used when there are technical reasons.

The inbox file may have one or more subheadings and so does the archive. Ideally, inbox subheadings should match the inbox.

We have two challenges to solve:

  • Automatically archieve `DONE` items.
  • Move archived items to the correct archive subheading.
Configuration
(setq my/inbox-file "~/Documents/org/roam/Inbox.org")
(setq my/archive-file "~/Documents/org/roam/Archives.org")
Refile
Functions
Refile item
(defun my/org-refile (file headline &optional new-state)
  "Refile item to the target FILE under the HEADLINE and set the NEW-STATE."
  (let ((pos (save-excursion
               (find-file file)
               (org-find-exact-headline-in-buffer headline))))
    (save-excursion
      (org-refile nil nil (list headline file nil pos))
      (org-refile-goto-last-stored)
      (when new-state (org-todo new-state)))))
Archiving
Functions
Find archive target
(defun my/org-find-archive-target (tag)
  "Find the archive target for the specified TAG.
The idea is that the archive file has multiple headings one for each category.
When a tagged item is archived it should go to an archive with at least one matching tag
or to the 'Unsorted' when none is matched. Archives are expected to be tagged with the archive tag."
  (or (car
       (car
        (org-ql-query
         :select '(list (substring-no-properties (org-get-heading t t)))
         :from my/archive-file
         :where `(tags "archive" ,tag))))
      "Unsorted"))
Archive item
(defun my/org-archive ()
  "Mark item as complete and refile to archieve."
  (interactive)
  (save-window-excursion
    (when (equal "*Org Agenda*" (buffer-name)) (org-agenda-goto))
    (let* ((tags (org-get-tags))
           (headline (if tags (car (mapcar (lambda (tag) (my/org-find-archive-target tag)) tags)) nil)))
      (my/org-refile my/archive-file headline "DONE")))
  ;; Redo the agenda
  (when (equal "*Org Agenda*" (buffer-name)) (org-agenda-redo)))
Auto archive
(defun my/org-auto-archive ()
  "Archieve all completed items in my inbox."
  (interactive)
  (save-window-excursion
    (find-file my/inbox-file)
    (goto-char 0)
    (let ((pos))
      (while (not (eq (point) pos))
        (setq pos (point))
        (outline-next-heading)
        (let* ((line (buffer-substring-no-properties (bol) (eol)))
               (line-without-stars (replace-regexp-in-string "^[\\*]+ " "" line)))
          (when (string-prefix-p "DONE" line-without-stars)
            (my/org-archive)
            (goto-char 0) ;; We need to go back from the beggining to avoid loosing entries
            (save-buffer)))))))

Agenda

Configuration
(setq org-agenda-files (append
                        '("~/Documents/org/quickmarks.org"
                          "~/Documents/org/github.org"
                          "~/Documents/org/habits.org"
                          "~/Documents/org/nutrition.org"
                          "~/Documents/org/roam/Inbox.org")
                        (directory-files-recursively "~/Documents/org/jira" "\.org$")))
Functions
Archive agenda item at point

Requires:

(defun my/org-agenda-archive-at-point ()
  "Archive the url of the specified item."
  (interactive)
  (let ((agenda-window-configuration (current-window-configuration)))
    (org-agenda-switch-to)
    (my/org-archive)
    (set-window-configuration agenda-window-configuration)))
Browse at point
(defun my/org-agenda-browse-at-point ()
  "Browse the url of the specified item."
  (interactive)
  (let ((agenda-window-configuration (current-window-configuration)))
    (org-agenda-switch-to)
    (let ((url (car
                (mapcar (lambda (p) (replace-regexp-in-string (regexp-quote "\"") "" (org-entry-get (point) p)))
                        (seq-filter (lambda (n) (string-suffix-p "url" n t))
                                    (mapcar (lambda (e) (car e)) (org-entry-properties)))))))
      (when url (browse-url  url)))
    (set-window-configuration agenda-window-configuration)))
Export Agenda
Super Agenda

Requires:

(use-package org-super-agenda
  :commands (my/org-agenda-browse-at-point my/org-agenda-archive-at-point my/org-agenda-export my/org-archive my/org-refile)
  :config
  (setq org-super-agenda-groups '((:name "Events" :time-grid t :todo "TODAY")
                                  (:name "Habbits" :tag "habit" :todo "TODAY")
                                  (:name "Due" :deadline past)
                                  (:name "Jira" :tag "jira")
                                  (:name "Email" :tag "email")
                                  (:name "Github pulls" :tag "pull")
                                  (:name "Github issues" :tag "issue"))
        ;; agenda
        org-agenda-scheduled-leaders '("" "")
        org-agenda-tag-filter-preset '("-drill")
        org-agenda-start-day "+0"
        org-agenda-start-on-weekday nil
        org-agenda-span 2
        org-agenda-files (append
                          (directory-files-recursively "~/Documents/org/jira" "\.org$")
                          '("~/Documents/org/roam/Inbox.org" "~/Documents/org/habits.org" "~/Documents/org/github.org" "~/Documents/org/nutrition.org"))
        ;; Refile
        org-refile-targets '(
                             ;; P.A.R.A
                             ("~/Documents/org/roam/Projects.org" :maxlevel . 10)
                             ("~/Documents/org/roam/Areas.org" :maxlevel . 10)
                             ("~/Documents/org/roam/Resources.org" :maxlevel . 10)
                             ("~/Documents/org/roam/Archives.org" :maxlevel . 10)))
  :hook (org-agenda-mode . org-super-agenda-mode)
  :general (:keymaps 'org-agenda-mode-map
                     "C-a" 'my/org-agenda-archive-at-point
                     "C-b" 'my/org-agenda-browse-at-point))

Bullets

(use-package org-bullets
  :after (org)
  :hook (org-mode . org-bullets-mode)
  :custom (org-bullets-bullet-list '("" "" "" "" "" "" "" "")))

Indent

To ensure that heading is aligned with the content, let’s use the `org-indent` feaature.

(use-feature org-indent
  :after org
  :hook (org-mode . org-indent-mode)
  :config
  (define-advice org-indent-refresh-maybe (:around (fn &rest args) "when-buffer-visible")
    "Only refresh indentation when buffer's window is visible.
Speeds up `org-agenda' remote operations."
    (when (get-buffer-window (current-buffer) t) (apply fn args))))

Org Roam

Package
(use-package org-roam
  :elpaca (org-roam :host github :repo "org-roam/org-roam" :ref "74422df546a515bc984c2f3d3a681c09d6f43916")
  :custom (org-roam-completion-everywhere t)
  (org-roam-directory "~/Documents/org/roam"))
Troubleshooting
Completion not working ?

If completion does not work just call `org-roam-db-sync`.

Capture templates
(setq org-roam-capture-templates '(("d" "default" plain "%?" :target (file+head "${title}.org" "#+title: ${title}\n") :unnarrowed t)))
(setq org-roam-dailies-capture-templates `(("d" "default" entry "* %?" :target (file+head "%<%Y-%m-%d>.org"
                                                                                          ,(concat "#+title: %<%Y-%m-%d>\n"
                                                                                                   "* Daily Checklist\n"
                                                                                                   "** TODO Log weight\n"
                                                                                                   "** TODO Check emails\n"
                                                                                                   "** TODO Check github issues / pull requests"
                                                                                                   )))))
Multi directory setup

To have multiple different org roam directories, just add the following `.dir-local.el` file in the root of each roam root.

((nil . ((eval . (setq-local org-roam-directory (locate-dominating-file default-directory ".dir-locals.el"))))))
Functions
Roam extract subtree and insert

One of the pieces of functionality I am missing is the ability to move a subtree to a node. Inspiration drawn from logseq plugin: https://github.com/vipzhicheng/logseq-plugin-move-block

(defun my/org-roam-extract-subtree-and-insert ()
  "Convert current subtree at point to a node, extract it into a new file and insert a ref to it."
  (interactive)
  (save-excursion
    (org-back-to-heading-or-point-min t)
    ;; Get the stars of the heading
    (let ((stars (car (split-string (buffer-substring (bol) (eol))))))
      (when (bobp) (user-error "Already a top-level node"))
      (org-id-get-create)
      (save-buffer)
      (org-roam-db-update-file)
      (let* ((template-info nil)
             (node (org-roam-node-at-point))
             (template (org-roam-format-template
                        (string-trim (org-capture-fill-template org-roam-extract-new-file-path))
                        (lambda (key default-val)
                          (let ((fn (intern key))
                                (node-fn (intern (concat "org-roam-node-" key)))
                                (ksym (intern (concat ":" key))))
                            (cond
                             ((fboundp fn)
                              (funcall fn node))
                             ((fboundp node-fn)
                              (funcall node-fn node))
                             (t (let ((r (read-from-minibuffer (format "%s: " key) default-val)))
                                  (plist-put template-info ksym r)
                                  r)))))))
             (file-path
              (expand-file-name
               (read-file-name "Extract node to: " (file-name-as-directory org-roam-directory) template nil template)
               org-roam-directory)))
        (when (file-exists-p file-path)
          (user-error "%s exists. Aborting" file-path))
        (org-cut-subtree)
        (save-buffer)
        (with-current-buffer (find-file-noselect file-path)
          (org-paste-subtree)
          (while (> (org-current-level) 1) (org-promote-subtree))
          (save-buffer)
          (org-roam-promote-entire-buffer)
          (save-buffer))
        ;; Insert a link to the extracted node
        (insert (format "%s [[id:%s][%s]]\n" stars (org-roam-node-id node) (org-roam-node-title node)))))))
Logseq Integration

Integration based on:

Requires:

(defvar my/logseq-folder "~/Documents/logseq/" "The logseq folder")

;; You probably don't need to change these values
(defvar my/logseq-pages (f-expand (f-join my/logseq-folder "pages")))
(defvar my/logseq-journals (f-expand (f-join my/logseq-folder "journals")))
(defvar my/rich-text-types '(bold italic subscript link strike-through superscript underline inline-src-block))
Functions
Utilities
(defun my/textify (headline)
  "Create a string represntation of the current HEADLINE."
  (save-excursion
    (apply 'concat (flatten-list
                    (my/textify-all (org-element-property :title headline))))))

(defun my/textify-all (nodes)
  "Create a string representation of all NODES"
  (mapcar 'my/subtextify nodes))

(defun my/subtextify (node)
  "Create a string represntation of the current NODE."
  (cond ((not node) "") ;; if node is nil -> emtpy string
        ((stringp node) (substring-no-properties node)) ;; if string -> remove properties 
        ((member (org-element-type node) my/rich-text-types) 
         (list (my/textify-all (cddr node))
               (if (> (org-element-property :post-blank node))
                   (make-string (org-element-property :post-blank node) ?\s)
                 "")))
        (t "")))

(defun my/with-length (str) (cons (length str) str))
Logseq to Roam
(defun my/logseq-to-roam-buffer (buffer)
  "Convert BUFFER links from using logseq format to org-roam.
  Logseq is using file references, which org-roam is using ids.
  This function covnerts fuzzy anf file: links to id links."
  (save-excursion
    (let* (changed
           link)
      (set-buffer buffer)
      (goto-char 1)
      (while (search-forward "[[" nil t)
        (setq link (org-element-context))
        (setq newlink (my/logseq-to-roam-link link))
        (when newlink
          (setq changed t)
          (goto-char (org-element-property :begin link))
          (delete-region (org-element-property :begin link) (org-element-property :end link))
          ;; note, this format string is reall =[[%s][%s]]= but =%= is a markup char so one's hidden
          (insert newlink)))
      ;; ensure org-roam knows about the changed links
      (when changed (save-buffer)))))

(defun my/logseq-to-roam ()
  "Convert the current buffer from logseq to roam."
  (interactive)
  (my/logseq-to-roam-buffer (current-buffer)))

(defun my/logseq-to-roam-link (link)
  "Convert the LINK from logseq format to roam.
  Logseq is using file references, which org-roam is using ids.
  This function covnerts fuzzy anf file: links to id links."
  (let (filename
        id
        linktext
        newlink)
    (when (eq 'link (org-element-type link))
      (when (equal "fuzzy" (org-element-property :type link))
        (setq filename (f-expand (f-join my/logseq-pages
                                         (concat (org-element-property :path link) ".org"))))
        (setq linktext (org-element-property :raw-link link)))
      (when (equal "file" (org-element-property :type link))
        (setq filename (f-expand (org-element-property :path link)))
        (if (org-element-property :contents-begin link)
            (setq linktext (buffer-substring-no-properties
                            (org-element-property :contents-begin link)
                            (org-element-property :contents-end link)))
          (setq linktext (buffer-substring-no-properties
                          (+ (org-element-property :begin link) 2)
                          (- (org-element-property :end link) 2)))))
      (when (and filename (f-exists-p filename))
        (setq id (caar (org-roam-db-query [:select id :from nodes :where (like file $s1)]
                                          filename)))
        (when id
          (setq newlink (format "[[id:%s][%s]]%s"
                                id
                                linktext
                                (if (> (org-element-property :post-blank link))
                                    (make-string (org-element-property :post-blank link) ?\)
                                                 ""))))
          (when (not (equal newlink
                            (buffer-substring-no-properties
                             (org-element-property :begin link)
                             (org-element-property :end link))))
            newlink))))))
Roam to Logseq
(defun my/roam-to-logseq-buffer (buffer)
  "Convert BUFFER links from using logseq format to org-roam.
  Logseq is using file references, which org-roam is using ids.
  This function covnerts fuzzy anf file: links to id links."
  (save-excursion
    (let* (changed)
      (with-current-buffer buffer
        (goto-char 1)
        (while (search-forward "[[id:" nil t)
          (let* ((id (car (split-string (buffer-substring-no-properties (point) (eol)) "]")))
                 (node (org-roam-node-from-id id))
                 (title (org-roam-node-title node)))
            (when title
              (setq file (car (org-id-find id)))
              (setq link (org-element-context))
              (setq newlink (format "[[%s]]" title))
              (when newlink
                (setq changed t)
                (goto-char (org-element-property :begin link))
                (delete-region (org-element-property :begin link) (org-element-property :end link))
                ;; note, this format string is reall =[[%s][%s]]= but =%= is a markup char so one's hidden
                (insert newlink)))
            ;; ensure org-roam knows about the changed links
            (when changed (save-buffer))))))))

(defun my/roam-to-logseq ()
  "Convert the current buffer from roam to logseq."
  (interactive)
  (my/roam-to-logseq-buffer (current-buffer)))

(defun my/roam-file-modified-p (file-path)
  (let ((content-hash (org-roam-db--file-hash file-path))
        (db-hash (caar (org-roam-db-query [:select hash :from files
                                                   :where (= file $s1)] file-path))))
    (not (string= content-hash db-hash))))

(defun my/modified-logseq-files ()
  (emacsql-with-transaction (org-roam-db)
    (seq-filter 'my/roam-file-modified-p
                (org-roam--list-files my/logseq-folder))))
Check Logseq
(defun my/logseq-journal-p (file) (string-match-p (concat "^" my/logseq-journals) file))
(defun my/ensure-file-id (file)
  "Visit an existing file, ensure it has an id, return whether the a new buffer was created"
  (setq file (f-expand file))
  (if (my/logseq-journal-p file)
      `(nil . nil)
    (let* ((buf (get-file-buffer file))
           (was-modified (buffer-modified-p buf))
           (new-buf nil)
           has-data
           org
           changed
           sec-end)
      (when (not buf)
        (setq buf (find-file-noselect file))
        (setq new-buf t))
      (set-buffer buf)
      (setq org (org-element-parse-buffer))
      (setq has-data (cddr org))
      (goto-char 1)
      (when (not (and (eq 'section (org-element-type (nth 2 org))) (org-roam-id-at-point)))
        ;; this file has no file id
        (setq changed t)
        (when (eq 'headline (org-element-type (nth 2 org)))
          ;; if there's no section before the first headline, add one
          (insert "\n")
          (goto-char 1))
        (org-id-get-create)
        (setq org (org-element-parse-buffer)))
      (when (nth 3 org)
        (when (not (org-collect-keywords ["title"]))
          ;; no title -- ensure there's a blank line at the section end
          (setq changed t)
          (setq sec-end (org-element-property :end (nth 2 org)))
          (goto-char (1- sec-end))
          (when (and (not (equal "\n\n" (buffer-substring-no-properties (- sec-end 2) sec-end))))
            (insert "\n")
            (goto-char (1- (point)))
            (setq org (org-element-parse-buffer)))
          ;; copy the first headline to the title
          (insert (format "#+title: %s" (string-trim (my/textify (nth 3 org)))))))
      ;; ensure org-roam knows about the new id and/or title
      (when changed (save-buffer))
      (cons new-buf buf))))

(defun my/check-logseq ()
  (interactive)
  (let (created
        files
        bufs
        unmodified
        cur
        bad
        buf)
    (setq files (org-roam--list-files my/logseq-folder))
    ;; make sure all the files have file ids
    (dolist (file-path files)
      (setq file-path (f-expand file-path))
      (setq cur (my/ensure-file-id file-path))
      (setq buf (cdr cur))
      (push buf bufs)
      (when (and (not (my/logseq-journal-p file-path)) (not buf))
        (push file-path bad))
      (when (not (buffer-modified-p buf))
        (push buf unmodified))
      (when (car cur)
        (push buf created)))
    ;; patch fuzzy links
    (mapc 'my/logseq-to-roam-buffer (seq-filter 'identity bufs))
    (dolist (buf unmodified)
      (when (buffer-modified-p buf)
        (save-buffer unmodified)))
    (mapc 'kill-buffer created)
    (when bad
      (message "Bad items: %s" bad))
    nil))
Troubleshooting
org-roam-extract-subtree is creating empty file

It turns out that this was caused by doomemacs file template functionality that was over writing the extracted node file.

Org Capture

Capture Template
(setq org-capture-templates
      '(
        ("c" "Calendar")
        ("cw" "Work Event" entry (file  "~/Documents/org/calendars/work.org") "* %?\n\n%^T\n\n:PROPERTIES:\n\n:END:\n\n")
        ("cp" "Personal Event" entry (file  "~/Documents/org/calendars/personal.org") "* %?\n\n%^T\n\n:PROPERTIES:\n\n:END:\n\n")

        ("i" "Inbox")
        ("iw" "Work Inbox" entry (file+olp "~/Documents/org/roam/Inbox.org" "Work") "* TODO %?\nSCHEDULED: %(org-insert-time-stamp (org-read-date nil t \"+0d\"))\n%a\n" :prepend t)
        ("ip" "Personal Inbox" entry (file+olp "~/Documents/org/roam/Inbox.org" "Personal") "* TODO %?\nSCHEDULED: %(org-insert-time-stamp (org-read-date nil t \"+0d\"))\n%a\n" :prepend t)

        ("e" "Email Workflow")
        ("ef" "Follow Up" entry (file+olp "~/Documents/org/raom/Inbox.org" "Email" "Follow Up") "* TODO Follow up with %:fromname on %a :email:\nSCHEDULED:%t\nDEADLINE: %(org-insert-time-stamp (org-read-date nil t \"+2d\"))\n\n%i" :immediate-finish t)
        ("er" "Read Later" entry (file+olp "~/Documents/org/roam/Inbox.org" "Email" "Read Later") "* TODO Read %:subject :email: \nSCHEDULED:%t\nDEADLINE: %(org-insert-time-stamp (org-read-date nil t \"+2d\"))\n\n%a\n\n%i" :immediate-finish t)

        ("p" "Project" entry (file+headline "~/Documents/org/roam/Projects.org" "Projects")(file "~/Documents/org/templates/project.orgtmpl"))
        ("d" "System design" entry (file+headline "~/Documents/org/system-design/system-design.org" "System Design") (file "~/Documents/org/templates/system-design.orgtmpl"))

        ("b" "BJJ")
        ("bm" "Moves" entry (file+olp "~/Documents/org/bjj/BJJ.org" "Moves")(file "~/Documents/org/templates/bjj-move.orgtmpl"))
        ("bs" "Submission" entry (file+olp "~/Documents/org/bjj/BJJ.org" "Techniques" "Submissions")(file "~/Documents/org/templates/bjj-submission.orgtmpl"))
        ("bc" "Choke" entry (file+olp "~/Documents/org/bjj/BJJ.org" "Techniques" "Chokes")(file "~/Documents/org/templates/bjj-choke.orgtmpl"))
        ("bw" "Sweeps" entry (file+olp "~/Documents/org/bjj/BJJ.org" "Techniques" "Sweeps")(file "~/Documents/org/templates/bjj-sweep.orgtmpl"))
        ("be" "Escapes" entry (file+olp "~/Documents/org/bjj/BJJ.org" "Techniques" "Escapes")(file "~/Documents/org/templates/bjj-escape.orgtmpl"))
        ("bt" "Takedowns" entry (file+olp "~/Documents/org/bjj/BJJ.org" "Techniques" "Takedowns")(file "~/Documents/org/templates/bjj-takedown.orgtmpl"))
        ("bp" "Passes" entry (file+olp "~/Documents/org/bjj/BJJ.org" "Techniques" "Passes")(file "~/Documents/org/templates/bjj-pass.orgtmpl"))
        ("bf" "FAQ" entry (file+olp "~/Documents/org/bjj/BJJ.org" "FAQ")(file "~/Documents/org/templates/bjj-faq.orgtmpl"))

        ("h" "Habit" entry (file+olp "~/Documents/org/habits.org" "Habits") (file "~/Documents/org/templates/habit.orgtmpl"))

        ("f" "Flashcards")
        ("fq" "Quotes" entry (file+headline "~/Documents/org/flashcards/quotes.org" "Quotes") "* %?\n%u" :prepend t)
        ("fS" "Stories"  entry (file+headline "~/Documents/org/flashcards/stories.org" "Stories") "* Story :drill:\n %t\n %^{The story}\n")
        ("fe" "Emacs")
        ("fef" "Emacs facts"  entry (file+headline "~/Documents/org/flashcards/emacs.org" "Emacs") "* Fact :drill:\n %t\n %^{The fact}\n")
        ("feq" "Emacs questions"  entry (file+headline "~/Documents/org/flashcards/emacs.org" "Emacs") "* Question :drill:\n %t\n %^{The question} \n** Answer: \n%^{The answer}")
        ("fh" "History")
        ("fhf" "History facts"  entry (file+headline "~/Documents/org/flashcards/history.org" "History") "* Fact :drill:\n %t\n %^{The fact}\n")
        ("fhq" "History questions"  entry (file+headline "~/Documents/org/flashcards/history.org" "History") "* Question :drill:\n %t\n %^{The question} \n** Answer: \n%^{The answer}")
        ("fm" "Maths")
        ("fmf" "Math facts"  entry (file+headline "~/Documents/org/flashcards/maths.org" "Maths") "* Fact :drill:\n %t\n %^{The fact}\n")
        ("fmq" "Math questions"  entry (file+headline "~/Documents/org/flashcards/maths.org" "Maths") "* Question :drill:\n %t\n %^{The question} \n** Answer: \n%^{The answer}")
        ("fc" "Computer Science")
        ("fcf" "Computer Science facts"  entry (file+headline "~/Documents/org/flashcards/computer-science.org" "Computer Science") "* Fact :drill:\n %t\n %^{The fact}\n")
        ("fcq" "Computer Science questions"  entry (file+headline "~/Documents/org/flashcards/computer-science.org" "Computer Science") "* Question :drill:\n %t\n %^{The question} \n** Answer: \n%^{The answer}")
        ("fs" "Sports")
        ("fsf" "Sports facts"  entry (file+headline "~/Documents/org/flashcards/sports.org" "Sports") "* Fact :drill:\n %t\n %^{The fact}\n")
        ("fsq" "Sports questions"  entry (file+headline "~/Documents/org/flashcards/sports.org" "Sports") "* Question :drill:\n %t\n %^{The question} \n** Answer: \n%^{The answer}")
        ("fn" "Nutrition")
        ("ft" "Trading")
        ("ftf" "Trading facts"  entry (file+headline "~/Documents/org/flashcards/trading.org" "Trading") "* Fact :drill:\n %t\n %^{The fact}\n")
        ("ftq" "Trading questions"  entry (file+headline "~/Documents/org/flashcards/trading.org" "Trading") "* Question :drill:\n %t\n %^{The question} \n** Answer: \n%^{The answer}")
        ("fl" "Languages")
        ("fls" "Spanish"  entry (file+headline "~/Documents/org/flashcards/languages/spanish.org" "Spanish") "* Question :drill:\n %t\n %^{The question} \n** Answer: \n%^{The answer}")))
Window Manager Integration

To use org-capture directly from window manager it’s handy to close side frames and automatically close main frame when done. Credits: https://www.reddit.com/r/emacs/comments/74gkeq/system_wide_org_capture

(defadvice org-switch-to-buffer-other-window
    (after supress-window-splitting activate)
  "Delete the extra window if we're in a capture frame"
  (if (equal "org-capture" (frame-parameter nil 'name))
      (delete-other-windows)))

(defadvice org-capture-finalize
    (after delete-capture-frame activate)
  "Advise capture-finalize to close the frame"
  (when (and (equal "org-capture" (frame-parameter nil 'name))
             (not (eq this-command 'org-capture-refile)))
    (delete-frame)))

(defadvice org-capture-refile
    (after delete-capture-frame activate)
  "Advise org-refile to close the frame"
  (delete-frame))

Org Drill

;;;###autoload
(defun my/org-drill ()
  "Require, configure and call org-drill."
  (interactive)
  (require 'org-drill)
  (let ((org-drill-scope 'directory))
    (find-file "~/Documents/org/roam/index.org")
    (org-drill)
    (org-save-all-org-buffers)))

;;;###autoload
(defun my/org-drill-buffer ()
  "Require, configure and call org-drill."
  (interactive)
  (require 'org-drill)
  (let  ((org-drill-scope 'file))
    (org-drill)
    (org-save-all-org-buffers)))
:init (setq org-drill-scope 'directory)

;;;###autoload
(defun my/org-drill-match ()
  "Require, configure and call org-drill."
  (interactive)
  (require 'org-drill)
  (let ((org-drill-scope 'directory)
        (org-drill-match (read-string "Please specify a filter (e.g. tag, property etc) for the drill: ")))
    (find-file "~/Documents/org/roam/index.org")
    (org-drill)
    (org-save-all-org-buffers)))

(use-package org-drill :after org)

Org Habit

Package
(use-feature org-habit
  :after org
  :config
  (setq org-habit-following-days 7
        org-habit-preceding-days 35
        org-habit-show-habits t)

  (defvar my/org-habit-capture-alist '() "An association list that maps capture keys to habit headings")

  (defun my/org-habit-check-captured ()
    "Check if there is a habit matching that latest captured item and mark it as done."
    (let* ((key  (plist-get org-capture-plist :key))
           (habit (cdr (assoc key my/org-habit-capture-alist))))
      (if habit
          (progn
            (message "Found linked habit:%s" habit)
            (when (not org-note-abort) (my/org-habit-mark habit))))))

  (defun my/org-habit-mark (heading)
    (save-excursion
      (let* ((habits-file "/home/iocanel/Documents/org/habits.org")
             (original (current-buffer))
             (buf (find-file habits-file)))
        (with-current-buffer buf
          (goto-char (point-min))
          (re-search-forward (concat "TODO " heading ".*:habit:"))
          (org-habit-parse-todo)
          (org-todo 'done)
          (save-buffer t))
        (switch-to-buffer original t t))))

  (advice-add 'org-drill :after (lambda() (my/org-habit-mark "Org Drill")))
  (add-hook 'org-capture-after-finalize-hook 'my/org-habit-check-captured))

Org Links

(defun my/dired-file-as-plantuml-link-to-clipboard ()
  "Create an Org link to the currently selected file in Dired and copy it to the clipboard."
  (interactive)
  (let* ((file (dired-get-filename))
         (name (file-name-base file))
         (cleaned-name (replace-regexp-in-string "^[0-9]+\\(\\.\\)[[:blank:]]+" "" name))
         (extension (file-name-extension file))
         (link (format "[[\"file:%s\" %s]]"  file cleaned-name)))
    (kill-new link)
    (message "Plantuml link to file copied to clipboard: %s" file)))

(defun my/dired-file-as-org-link-to-clipboard ()
  "Create an Org link to the currently selected file in Dired and copy it to the clipboard."
  (interactive)
  (let* ((file (dired-get-filename))
         (name (file-name-base file))
         (cleaned-name (replace-regexp-in-string "^[0-9]+\\(\\.\\)[[:blank:]]+" "" name))
         (extension (file-name-extension file))
         (protocol (if (string-match-p "\\(\\.\\(mp4\\|mkv\\|avi\\)\\)$" file) "mpv" "file"))
         (link (format "[[%s:%s][%s]]" protocol file cleaned-name)))
    (kill-new link)
    (message "Org link to file copied to clipboard: %s" file)))

(define-key dired-mode-map (kbd "C-c o l") 'my/dired-file-as-org-link-to-clipboard)
(define-key dired-mode-map (kbd "C-c u l") 'my/dired-file-as-plantuml-link-to-clipboard)

Org Github Issues

Package
(use-package org-github-issues
  :elpaca (org-github-issues :host github :repo "iensu/org-github-issues")
  :init
  (defvar my/github-repositories nil "The list of watch repositories by org-github-issues")
  :commands (org-github-issues-sync-all my/org-github-issues-eww-at-point my/org-github-issues-show-open-project-issues my/org-github-issues-show-open-workspace-issues)
  :config
  (setq
   gh-user "iocanel"
   org-github-issues-user "iocanel"
   org-github-issues-org-file "~/Documents/org/github.org"
   org-github-issues-tags '("github")
   org-github-issues-issue-tags '("issue")
   org-github-issues-pull-tags '("pull")
   org-github-issues-tag-transformations '((".*" "")) ;; force all labels to empty string so that they can be ommitted.
   org-github-issues-auto-schedule "+0d"
   org-github-issues-filter-by-assignee t
   org-github-issues-headline-prefix t)

  (defun my/org-github-issues-url-at-point ()
    "Utility that fetches the url of the issue at point."
    (save-excursion
      (let ((origin (current-buffer)))
        (when (eq major-mode 'org-agenda-mode) (org-agenda-switch-to))
        (let* ((p (point))
               (url (string-trim (org-entry-get nil "GH_URL"))))
          (when (not (equal origin (current-buffer))) (switch-to-buffer origin))
          url))))

  (defun my/org-github-issues-eww-at-point ()
    "Browse the issue that corresponds to the org entry at point."
    (interactive)
    (let ((url (my/org-github-issues-url-at-point)))
      (when url
        (other-window 1)
        (split-window-horizontally)
        (eww url))))

  (defun my/org-github-issues-show-open-project-issues (root)
    "Show all the project issues currently assigned to me."
    (let* ((project (projectile-ensure-project root))
           (project-name (projectile-project-name project)))
      (org-ql-search org-github-issues-org-file
                     `(and (property "GH_URL")
                           (string-match (regexp-quote ,project-name) (org-entry-get (point) "GH_URL")))
                     :title (format "Github issues for %s" project-name))
      (goto-char (point-min))
      (org-agenda-next-line)))

  (defun my/org-github-issues-show-open-workspace-issues (workspace)
    "Show all the workspace issues currently assigned to me."
    (let* ((name (treemacs-project->name workspace))
           (projects (treemacs-workspace->projects workspace))
           (project-names (mapcar (lambda (p) (treemacs-project->name p)) projects))
           (main-project (car project-names)))
      (when main-project
        (org-ql-search org-github-issues-org-file
                       `(and (property "GH_URL")
                             (or (string-match (regexp-quote ,main-project) (org-entry-get (point) "GH_URL"))
                                 (seq-filter (lambda (p) (string-match (regexp-quote p) (org-entry-get (point) "GH_URL"))) project-names)))
                       :title (format "Github issues for %s" name))
        (goto-char (point-min))
        (org-agenda-next-line))))
  )

Org Jira

Package
(use-package org-jira
  :commands (my/org-jira-get-issues my/org-jira-hydra my/org-jira-get-issues)
  :custom (org-jira-property-overrides '("CUSTOM_ID" "self"))
  :init
  ;;
  ;;  Variables
  ;;
  (defvar my/org-jira-selected-board nil)
  (defvar my/org-jira-selected-sprint nil)
  (defvar my/org-jira-selected-epic nil)

  (defvar my/org-jira-boards-cache ())
  (defvar my/org-jira-sprint-by-board-cache ())
  (defvar my/org-jira-epic-by-board-cache ())

  :config
  (setq jiralib-url "https://issues.redhat.com/"
        jiralib-user-login-name "ikanello1@redhat.com"
        jira-password nil
        jira-token (replace-regexp-in-string "\n\\'" ""  (shell-command-to-string "pass show websites/redhat.com/ikanello1@redhat.com/token"))
        org-jira-working-dir "~/Documents/org/jira/"
        org-jira-projects-list '("ENTSBT" "SB" "QUARKUS"))
  (setq jiralib-token `("Authorization" . ,(concat "Bearer " jira-token)))

  (defun my/org-jira-get-issues ()
    "Sync using org-jira and postprocess."
    (interactive)
    (org-jira-get-issues (org-jira-get-issue-list org-jira-get-issue-list-callback))
    (my/org-jira-postprocess))

  (defun my/org-jira-issue-id-at-point ()
    "Returns the ID of the current issue."
    (save-excursion
      (org-previous-visible-heading 1)
      (org-element-property :ID (org-element-at-point))))

  (defun my/org-jira-update-issue-description()
    "Move the selected issue to an active sprint."
    (interactive)
    (let* ((issue-id (org-jira-parse-issue-id))
           (filename (buffer-file-name))
           (org-issue-description (org-trim (org-jira-get-issue-val-from-org 'description)))
           (update-fields (list (cons 'description org-issue-description))))
      (jiralib-update-issue issue-id update-fields
                            (org-jira-with-callback
                             (message (format "Issue '%s' updated!" issue-id))
                             (jiralib-get-issue
                              issue-id
                              (org-jira-with-callback
                               (org-jira-log "Update get issue for refresh callback hit.")
                               (-> cb-data list org-jira-get-issues)))))))


  (defun my/org-jira-postprocess ()
    "Postprocess the org-jira project files. It shcedules all jira issues so that they appear on agenda"
    (interactive)
    (mapcar (lambda (p)
              (let ((scheduled (format "%s  SCHEDULED: <%s>\n" (make-string 2 32) (org-read-date nil nil "+0d") ))
                    (github-project-file (concat (file-name-as-directory org-jira-working-dir) (format "%s.org" p))))
                (with-temp-buffer
                  (insert-file jira-project-file)
                  (goto-char (point-min))
                  (while (re-search-forward "^\*\* TODO" nil t)
                    (let* ((tags (org-get-tags)))
                      (add-to-list 'tags "jira")
                      (org-set-tags tags)
                      (org-set-property "SCHEDULED" scheduled)
                      (write-file jira-project-file)))))) '("QUARKUS" "SB" "ENTSBT"))))
Boards, Sprints and Epic

When I originally started playing with org-jira the was absolutely no support for any of these (I think). So, I rolled my own.

;;
;; Boards
;;
(defun my/org-jira-get-boards-list()
  "List all boards."
  (unless my/org-jira-boards-cache
    (setq my/org-jira-boards-cache (jiralib--agile-call-sync "/rest/agile/1.0/board" 'values)))
  my/org-jira-boards-cache)

(defun my/org-jira-get-board-id()
  "Select a board if one not already selected."
  (unless my/org-jira-selected-board
    (setq my/org-jira-selected-board (my/org-jira-board-completing-read)))
  (cdr (assoc 'id my/org-jira-selected-board)))

(defun my/org-jira-get-board()
  "Select a board if one not already selected."
  (unless my/org-jira-selected-board
    (setq my/org-jira-selected-board (my/org-jira-board-completing-read)))
  my/org-jira-selected-board)

(defun my/org-jira-board-completing-read()
  "Select a board by name."
  (when (not (file-exists-p (my/org-jira--get-boards-file)))
    (my/org-jira-get-boards-list))

  (let* ((boards (with-current-buffer (org-jira--get-boards-buffer)
                   (org-map-entries (lambda()
                                      `((id . ,(org-entry-get nil "id"))
                                        (self . ,(org-entry-get nil "url"))
                                        (name . ,(org-entry-get nil "name")))) t  'file)))
         (board-names (mapcar #'(lambda (a) (cdr (assoc 'name a))) boards))
         (board-name (completing-read "Choose board:" board-names)))
    (car (seq-filter #'(lambda (a) (equal (cdr (assoc 'name a)) board-name)) boards))))

(defun my/org-jira-select-board()
  "Select a board."
  (interactive)
  (setq my/org-jira-selected-board (cdr (assoc 'name (my/org-jira-board-completing-read)))))

;;
;; Sprint
;;
(defun my/org-jira-get-project-boards(project-id)
  "Find the board of the project.")

(defun my/org-jira-get-sprints-by-board(board-id &optional filter)
  "List all sprints by BOARD-ID."
  (let ((board-sprints-cache (cdr (assoc board-id my/org-jira-sprint-by-board-cache))))
    (unless board-sprints-cache
      (setq board-sprints-cache (jiralib--agile-call-sync (format "/rest/agile/1.0/board/%s/sprint" board-id)'values)))

    (add-to-list 'my/org-jira-sprint-by-board-cache `(,board-id . ,board-sprints-cache))
    (if filter
        (seq-filter filter board-sprints-cache)
      board-sprints-cache)))

(defun my/org-jira--active-sprint-p(sprint)
  "Predicate that checks if SPRINT is active."
  (not (assoc 'completeDate sprint)))

(defun my/org-jira-sprint-completing-read(board-id)
  "Select an active sprint by name."
  (let* ((sprints (my/org-jira-get-sprints-by-board board-id 'my/org-jira--active-sprint-p))
         (sprint-names (mapcar #'(lambda (a) (cdr (assoc 'name a))) sprints))
         (sprint-name (completing-read "Choose sprint:" sprint-names)))
    (car (seq-filter #'(lambda (a) (equal (cdr (assoc 'name a)) sprint-name)) sprints))))

(defun my/org-jira-move-issue-to-sprint(issue-id sprint-id)
  "Move issue with ISSUE-ID to sprint with SPRINT-ID."
  (jiralib--rest-call-it (format "/rest/agile/1.0/sprint/%s/issue" sprint-id) :type "POST" :data (format "{\"issues\": [\"%s\"]}" issue-id)))

(defun my/org-jira-assign-current-issue-to-sprint()
  "Move the selected issue to an active sprint."
  (interactive)
  (let* ((issue-id (my/org-jira-parse-issue-id))
         (board-id (cdr (assoc 'id (my/org-jira-get-board))))
         (sprint-id (cdr (assoc 'id (my/org-jira-sprint-completing-read board-id)))))

    (my/org-jira-move-issue-to-sprint issue-id sprint-id)))

(defun my/org-jira-get-sprint-id()
  "Select a sprint id if one not already selected."
  (unless my/org-jira-selected-sprint
    (setq my/org-jira-selected-sprint (my/org-jira-sprint-completing-read)))
  (cdr (assoc 'id my/org-jira-selected-sprint)))

(defun my/org-jira-get-sprint()
  "Select a sprint if one not already selected."
  (unless my/org-jira-selected-sprint
    (setq my/org-jira-selected-sprint (my/org-jira-select-sprint)))
  my/org-jira-selected-sprint)

(defun my/org-jira-select-sprint()
  "Select a sprint."
  (interactive)
  (setq my/org-jira-selected-sprint (my/org-jira-sprint-completing-read (my/org-jira-get-board-id))))

;;
;; Epics
;;
(defun my/org-jira-get-epics-by-board(board-id &optional filter)
  "List all epics by BOARD-ID."
  (interactive)
  (let ((board-epics-cache (cdr (assoc board-id my/org-jira-epic-by-board-cache))))
    (unless board-epics-cache
      (setq board-epics-cache (jiralib--agile-call-sync (format "/rest/agile/1.0/board/%s/epic" board-id)'values)))

    (add-to-list 'my/org-jira-epic-by-board-cache `(,board-id . ,board-epics-cache))
    (if filter
        (seq-filter filter board-epics-cache)
      board-epics-cache)))

(defun my/org-jira--active-epic-p(epic)
  "Predicate that checks if EPIC is active."
  (not (equal (assoc 'done epic) 'false)))


(defun my/org-jira-epic-completing-read(board-id)
  "Select an active epic by name."
  (let* ((epics (my/org-jira-get-epics-by-board board-id 'my/org-jira--active-epic-p))
         (epic-names (mapcar #'(lambda (a) (cdr (assoc 'name a))) epics))
         (epic-name (completing-read "Choose epic:" epic-names)))
    (car (seq-filter #'(lambda (a) (equal (cdr (assoc 'name a)) epic-name)) epics))))

(defun my/org-jira-move-issue-to-epic(issue-id epic-id)
  "Move issue with ISSUE-ID to epic with SPRINT-ID."
  (jiralib--rest-call-it (format "/rest/agile/1.0/epmy/%s/issue" epic-id) :type "POST" :data (format "{\"issues\": [\"%s\"]}" issue-id)))

(defun my/org-jira-assign-current-issue-to-epic()
  "Move the selected issue to an active epic."
  (interactive)
  (let* ((issue-id (my/org-jira-parse-issue-id))
         (board-id (cdr (assoc 'id (my/org-jira-get-board))))
         (epic-id (cdr (assoc 'id (my/org-jira-epic-completing-read board-id)))))

    (my/org-jira-move-issue-to-epic issue-id epic-id)))

(defun my/org-jira-get-epic-id()
  "Select a epic id if one not already selected."
  (unless my/org-jira-selected-epic
    (setq my/org-jira-selected-epic (my/org-jira-epic-completing-read)))
  (cdr (assoc 'id my/org-jira-selected-epic)))

(defun my/org-jira-get-epic()
  "Select a epic if one not already selected."
  (unless my/org-jira-selected-epic
    (setq my/org-jira-selected-epic (my/org-jira-select-epic)))
  my/org-jira-selected-epic)

(defun my/org-jira-select-epic()
  "Select a epic."
  (interactive)
  (setq my/org-jira-selected-epic (my/org-jira-epic-completing-read (my/org-jira-get-board-id))))

(defun my/org-jira-create-issue-with-defaults()
  "Create an issue and assign to default sprint and epic."
  (org-jira-create-issue)
  (my/org-jira-move-issue-to-epic)
  (my/org-jira-move-issue-to-sprint))
Hydra
(defun my/org-jira-hydra ()
  "Define (if not already defined org-jira hydra and invoke it."
  (interactive)
  (unless (boundp 'org-jira-hydra/body)
    (defhydra org-jira-hydra (:hint none :exit t)
      ;; The '_' character is not displayed. This affects columns alignment.
      ;; Remove s many spaces as needed to make up for the '_' deficit.
      "
         ^Actions^           ^Issue^              ^Buffer^                         ^Defaults^
                           ?I?
         ^^^^^^-----------------------------------------------------------------------------------------------
          _L_ist issues      _u_pdate issue       _R_efresh issues in buffer       Select _B_oard ?B?
          _C_reate issue     update _c_omment                                    Select _E_pic ?E?
                           assign _s_print                                     Select _S_print ?S?
                           assign _e_print                                     Create issue with _D_efaults
                           _b_rowse issue
                           _r_efresh issue
                           _p_rogress issue
  [_q_]: quit
"
      ("I" nil (or (my/org-jira-issue-id-at-point) ""))
      ("L" my/org-jira-get-issues)
      ("C" org-jira-create-issue)

      ("u" org-jira-update-issue)
      ("c" org-jira-update-comment)
      ("b" org-jira-browse-issue)
      ("s" my/org-jira-assign-current-issue-to-sprint)
      ("e" my/org-jira-assign-current-issue-to-epic)
      ("r" org-jira-refresh-issue)
      ("p" org-jira-progress-issue)

      ("R" org-jira-refresh-issues-in-buffer)

      ("B" my/org-jira-select-board (format "[%s]" (or my/org-jira-selected-board "")) :exit nil)
      ("E" my/org-jira-select-epic (format "[%s]" (or my/org-jira-selected-epic "")) :exit nil)
      ("S" my/org-jira-select-sprint (format "[%s]" (or my/org-jira-selected-sprint "")) :exit nil)
      ("D" my/org-jira-create-with-defaults)

      ("q" nil "quit")))
  (org-jira-hydra/body))

Org Tree Slide

;;;###autoload
(defun +org-present-hide-blocks-h ()
  "Hide org #+ constructs."
  (save-excursion
    (goto-char (point-min))
    (while (re-search-forward "^[[:space:]]*\\(#\\+\\)\\(\\(?:BEGIN\\|END\\|ATTR\\)[^[:space:]]+\\).*" nil t)
      (org-flag-region (match-beginning 1)
                       (match-end 0)
                       org-tree-slide-mode
                       'block))))

;;;###autoload
(defun +org-present-hide-leading-stars-h ()
  "Hide leading stars in headings."
  (save-excursion
    (goto-char (point-min))
    (while (re-search-forward "^\\(\\*+\\)" nil t)
      (org-flag-region (match-beginning 1)
                       (match-end 1)
                       org-tree-slide-mode
                       'headline))))

Org Asciidoc

(use-package adoc-mode)

Org Hugo

Configuration
(setq org-hugo-base-dir "~/workspace/src/github.com/iocanel/iocanel.github.io")
Customization
(defun my/org-hugo-set-export-file-name ()
  "Set the export file name to index.md."
  (interactive)
  (let ((name (file-name-nondirectory (directory-file-name (file-name-directory buffer-file-name)))))
    (save-excursion
      (goto-char 0)
      (if (re-search-forward "^#\\+EXPORT_FILE_NAME" nil t)
          (progn
            (move-beginning-of-line 1)
            (kill-line))
        (progn
          (while (string-prefix-p "#+" (buffer-substring (bol) (eol)))
            (next-line 1))
          (previous-line 1)
          (move-end-of-line 1)
          (insert "\n")))
      (insert "#+EXPORT_FILE_NAME: index.md"))))

(defun my/org-hugo-set-bundle ()
  "Set the hugo bundle property to match the directory."
  (interactive)
  (let ((name (file-name-nondirectory (directory-file-name (file-name-directory buffer-file-name)))))
    (save-excursion
      (goto-char 0)
      (if (re-search-forward "^#\\+HUGO_BUNDLE" nil t)
          (progn
            (move-beginning-of-line 1)
            (kill-line))
        (progn
          (while (string-prefix-p "#+" (buffer-substring (bol) (eol)))
            (next-line 1))
          (previous-line 1)
          (move-end-of-line 1)
          (insert "\n")))
      (insert (format! "#+HUGO_BUNDLE: %s" name)))))

(defun my/org-hugo-prepare()
  "Prepare document for export via ox-hugo."
  (interactive)
  (my/org-hugo-set-bundle)
  (my/org-hugo-set-export-file-name))

Java

Eclipse JDT Language Server

Formatting

Eclispe format configuration as configured in Quarkus

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<profiles version="15">
<profile kind="CodeFormatterProfile" name="Quarkus" version="15">
<setting id="org.eclipse.jdt.core.formatter.align_assignment_statements_on_columns" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines" value="2147483647"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_assignment" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_binary_expression" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_if" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_loops" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression" value="80"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" value="49"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_for_loop_header" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_method_declaration" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_module_statements" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiple_fields" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_resources_in_try" value="80"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_type_arguments" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_type_parameters" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.align_type_members_on_columns" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.align_variable_declarations_on_columns" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.align_with_spaces" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_imports" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_package" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_field" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_imports" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_member_type" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_method" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_package" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_import_groups" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_array_initializer" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_lambda_body" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_switch" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.comment.align_tags_descriptions_grouped" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.comment.align_tags_names_descriptions" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.comment.count_line_length_from_starting_position" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_block_comments" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_header" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_html" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_javadoc_comments" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_line_comments" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_source_code" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.comment.indent_parameter_description" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.comment.indent_root_tags" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.line_length" value="128"/>
<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.compact_else_if" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer" value="2"/>
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation" value="2"/>
<setting id="org.eclipse.jdt.core.formatter.disabling_tag" value="@formatter:off"/>
<setting id="org.eclipse.jdt.core.formatter.enabling_tag" value="@formatter:on"/>
<setting id="org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.indentation.size" value="4"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.indent_empty_lines" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_block" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_body" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_enum_constant" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_label" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_binary_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_ellipsis" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_unary_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_binary_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_ellipsis" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_unary_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.join_lines_in_comments" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.join_wrapped_lines" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.keep_annotation_declaration_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.keep_anonymous_type_declaration_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.keep_code_block_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.keep_enum_constant_declaration_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.keep_enum_declaration_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.keep_if_then_body_block_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.keep_lambda_body_block_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.keep_loop_body_block_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.keep_method_body_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.keep_simple_do_while_body_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.keep_simple_for_body_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.keep_simple_getter_setter_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.keep_simple_while_body_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.keep_type_declaration_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.lineSplit" value="128"/>
<setting id="org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_catch_clause" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_enum_constant_declaration" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_for_statment" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_if_while_statement" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_lambda_declaration" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_method_delcaration" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_method_invocation" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_switch_statement" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_try_clause" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.tabulation.char" value="space"/>
<setting id="org.eclipse.jdt.core.formatter.tabulation.size" value="4"/>
<setting id="org.eclipse.jdt.core.formatter.use_on_off_tags" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_assignment_operator" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_binary_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_conditional_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested" value="true"/>
</profile>
</profiles>
Configuration

The configuration is based on: joaotavora/eglot#1185

  {
  "settings": {
    "java": {
      "maxConcurrentBuilds": 1,
      "autobuild": {
        "enabled": false
      },
      "import": {
        "maven": {
          "enabled": true
        },
        "exclusions": [
          "**/node_modules/**",
          "**/.metadata/**",
          "**/archetype-resources/**",
          "**/META-INF/maven/**"
        ]
      },
      "configuration": {
        "updateBuildConfiguration": "automatic",
        "checkProjectSettingsExclusions": true,
        "runtimes": [
          {
            "name": "JavaSE-1.8",
            "path": "/home/iocanel/.sdkman/candidates/java/8.0.282-open"
          },
          {
            "name": "JavaSE-11",
            "path": "/home/iocanel/.sdkman/candidates/java/11.0.12-tem"
          },
          {
            "name": "JavaSE-17",
            "path": "/home/iocanel/.sdkman/candidates/java/17.0.7-tem",
            "default": true
          },
          {
            "name": "JavaSE-19",
            "path": "/home/iocanel/.sdkman/candidates/java/19.0.2-tem"
          }
        ]
      },
      "project": {
        "importHint": true,
        "importOnFirstTimeStartup": "automatic",
        "referencedLibraries": [
          "lib/**"
        ],
        "resourceFilters": [
          "node_modules",
          "\\.git",
          ".metadata",
          "archetype-resources",
          "META-INF/maven"
        ]
      },
      "server": {
        "launchMode": "Hybrid"
      },
      "format": {
        "settings": {
          "url": "/home/iocanel/.emacs.d/.local/lsp/eclipse.jdt.ls/eclipse-format.xml",
          "profile": "GoogleStyle"
        }
      },
      "contentProvider": {
        "preferred": "fernflower"
      },
      "completion": {
        "guessMethodArguments": true,
        "overwrite": true,
        "enabled": true,
        "favoriteStaticMembers": [
          "org.junit.Assert.*",
          "org.junit.Assume.*",
          "org.junit.jupiter.api.Assertions.*",
          "org.junit.jupiter.api.Assumptions.*",
          "org.junit.jupiter.api.DynamicContainer.*",
          "org.junit.jupiter.api.DynamicTest.*",
          "org.mockito.Mockito.*",
          "org.mockito.ArgumentMatchers.*",
          "org.mockito.Answers.*"
        ]
      }
    }
  },
  "extendedClientCapabilities": {
    "classFileContentsSupport": true,
    "overrideMethodsPromptSupport": true
  },
  "bundles": [
    "/home/iocanel/.emacs.d/.local/lsp/eclispe.jdt.ls/bundles/dg.jdt.ls.decompiler.cfr-0.0.3.jar",
    "/home/iocanel/.emacs.d/.local/lsp/eclipse.jdt.ls/bundles/dg.jdt.ls.decompiler.common-0.0.3.jar",
    "/home/iocanel/.emacs.d/.local/lsp/eclipse.jdt.ls/bundles/dg.jdt.ls.decompiler.fernflower-0.0.3.jar",
    "/home/iocanel/.emacs.d/.local/lsp/eclipse.jdt.ls/bundles/dg.jdt.ls.decompiler.procyon-0.0.3.jar"
  ]
}
Installer

The following code is a shell script that takes care of installing eclipse.jdt.ls

mkdir -p ~/.emacs.d/.local/lsp/eclipse.jdt.ls
pushd ~/.emacs.d/.local/lsp/eclipse.jdt.ls
curl -s -L https://www.eclipse.org/downloads/download.php?file=/jdtls/milestones/1.26.0/jdt-language-server-1.26.0-202307271613.tar.gz | tar zxv
mkdir bundles
pushd bundles
curl -O https://github.com/dgileadi/vscode-java-decompiler/raw/master/server/dg.jdt.ls.decompiler.cfr-0.0.3.jar
curl -O https://github.com/dgileadi/vscode-java-decompiler/raw/master/server/dg.jdt.ls.decompiler.common-0.0.3.jar
curl -O https://github.com/dgileadi/vscode-java-decompiler/raw/master/server/dg.jdt.ls.decompiler.fernflower-0.0.3.jar
curl -O https://github.com/dgileadi/vscode-java-decompiler/raw/master/server/dg.jdt.ls.decompiler.procyon-0.0.3.jar
popod
popd

Java Mode

(use-feature java-mode
  :init
  (defvar lsp-java-workspace-dir (expand-file-name "lsp/workspace/data" my/local-dir) "LSP data directory for Java")

  (defvar java-home "/home/iocanel/.sdkman/candidates/java/current" "The home dir of the jdk")
  (defvar java-bin (format "%s/bin/java" java-home) "The path to the java binary")
  (defvar jdtls-home "/opt/eclipse.jdt.ls" "The path to eclipse.jdt.ls installation")

  (defvar jdtls-config (format "%s/config_linux" jdtls-home) "The path to eclipse.jdt.ls installation")
  (defvar jdtls-jar (replace-regexp-in-string "\n\\'" "" (shell-command-to-string (format "find %s/plugins -iname '*launcher_*.jar'" jdtls-home)) "The jar file that starts jdtls"))

  (defun my/jdtls-start-command (arg)
    "Creates the command to start jdtls"
    `(,java-bin "-jar" ,jdtls-jar "-data" ,(format "/home/iocanel/.cache/lsp/project/%s" (project-name (project-current))) "-configuration" ,jdtls-config
     "--add-modules=ALL-SYSTEM" 
     "--add-opens java.base/java.util=ALL-UNNAMED" 
     "--add-opens java.base/java.lang=ALL-UNNAMED" 
     "-XX:+UseAdaptiveSizePolicy" "-XX:GCTimeRatio=4" "-XX:AdaptiveSizePolicyWeight=90" "-Xmx8G" "-Xms2G" "-Xverify:none"))

  (defun my/java-setup-project-workspace ()
    "Setup a local java workspace for the current project."
    (interactive)
    (let* ((project-root (project-root (project-current)))
           (file-path (concat project-root ".dir-locals.el"))
           (data-dir (concat (project-root (project-current)) ".lsp/workspace/data"))
           (cache-dir (concat (project-root (project-current)) ".lsp/workspace/cache"))
           (content '((java-mode
                       (eval . (progn
                                 (setq lsp-session-file (concat (project-root (project-current)) ".lsp/session")
                                       lsp-java-workspace-dir (concat (project-root (project-current)) ".lsp/workspace/data")
                                       lsp-java-workspace-cache-dir (concat (project-root (project-current)) ".lsp/workspace/cache"))))))))
      (make-directory data-dir t)
      (make-directory cache-dir t)
      (with-temp-buffer
        (setq-local enable-local-variables :all)
        (insert (format "%s\n" (pp-to-string content)))
        (write-file file-path))))

  (defun my/java-clear-project-workspace ()
    "Setup a local java workspace for the current project."
    (interactive)
    (let ((directory lsp-java-workspace-dir))
      (when (file-exists-p directory)
        (delete-directory directory 'recursive))
      (make-directory directory t))))

eglot-java

(use-package eglot-java
  :elpaca (eglot-java :host github :repo "iocanel/eglot-java" :files (:defaults "*.el"))
  :custom
  (eglot-java-eclipse-jdt-ls-download-url "https://www.eclipse.org/downloads/download.php?file=/jdtls/milestones/1.26.0/jdt-language-server-1.26.0-202307271613.tar.gz")
  (eglot-java-server-install-dir (file-name-concat my/local-dir "lsp" "eclipse.jdt.ls"))
  (eglot-java-eclipse-jdt-args '("-XX:+UseAdaptiveSizePolicy" "-XX:GCTimeRatio=4" "-XX:AdaptiveSizePolicyWeight=90" "-Xmx8G" "-Xms2G"))
  (eglot-java-eclipse-jdt-data-root-dir (file-name-concat my/local-dir "lsp" "eclipse.jdt.ls" "data"))
  :init

  (defun jdtls-initialization-options ()
    (let ((setting-json-file (file-name-concat my/local-dir "lsp" "eclipse.jdt.ls" "config.json")))
      (with-temp-buffer
        (insert-file-contents setting-json-file)
        (json-parse-buffer :object-type 'plist :false-object :json-false))))

  :config
  ;; Override existing options
  (cl-defmethod eglot-initialization-options ((server eglot-java-eclipse-jdt))
    (jdtls-initialization-options))
  ;; eglot-java registers in 'project-find-functions a function that lookus up for .project
  (advice-add 'eglot-java--init :after (lambda() (remove-hook 'project-find-functions  #'eglot-java--project-try)))
  :hook (java-mode . eglot-java-mode))

Example of project workspace configuration

The file below can be added to `.dir-locals.el` at the project roto in order to tune the Eclipse JDT Language Server workspace.

((java-mode
  . ((eglot-workspace-configuration
      .
       (:java
        (:autobuild (:enabled :json-false)
         :import  (:maven (:enabled t :downloadSources t)
                   :exlusions ["**/node_modules/**"
                                "**/.metadata/**"
                                "**/archetype-resources/**"
                                "**/META-INF/maven/**"]
                   ;; end of import
                   )
         :configuration (:updateBuildConfiguration "automatic"
                         :checkProjectSettingsExclusions t
                         :project (:importHint t
                                   :importOnFirstTimeStartup "automatic")
                         :server (:launchMode "LightWeight")
                         )
                         ;; end of configuration

         )
         ;; end of java
        )))))

UML

PlantUML

Package
(use-package plantuml-mode
  :after org
  :commands (plantuml-mode plantuml-download-jar)
  :init
  (add-to-list 'org-src-lang-modes '("plantuml" . plantuml)
               (setq plantuml-jar-path (concat user-emacs-directory "plantuml/" "plantuml.jar")
                     org-plantuml-jar-path plantuml-jar-path))
  :hook (plantuml-mode . yas/minor-mode))
Flycheck
(use-package flycheck-plantuml
  :after plantuml-mode
  :config (flycheck-plantuml-setup))
Snippets

This section contains snippets to ease the development of planuml diagrams.

Start
# -*- mode: snippet -*-
# name: start
# key: start 
# --
@startuml
start
$0
end
@enduml
Conditionals
If then else
# -*- mode: snippet -*-
# name: if then else
# key: ite
# --
if ($1) then (yes)
$0
else
endif
Notes
Left
# -*- mode: snippet -*-
# name: left note
# key: ln 
# --
note left
$0
end note
Right
# -*- mode: snippet -*-
# name: right note
# key: rn 
# --
note right
$0
end note
Splits
Single split
# -*- mode: snippet -*-
# name: split
# key: split 
# --
split
-[#${1:$$(yas-choose-value '("blue" "green" "red" "yellow"))}]->$2;
$0
split again
-[#${3:$$(yas-choose-value '("blue" "green" "red" "yellow"))}]->$4;
end split
Split again
# -*- mode: snippet -*-
# name: split again
# key: sa
# --
split again
-[#${1:$$(yas-choose-value '("blue" "green" "red" "yellow"))}]->$2;
$0

eyuml and flowchart.js

(use-package eyuml
  :after org
  :init
  (add-to-list 'org-src-lang-modes '("yuml" . yuml))
  (add-to-list 'org-src-lang-modes '("flowchart-js" . flowchart-js))
  :commands (org-babel-execute:yuml)
  :config
  ;;
  ;; Flowchart.js
  ;;
  (defun org-babel-execute:flowchart-js (body params)
    "Execute a block of flowchartjs code with org-babel."
    (let* ((in-file (org-babel-temp-file "" ".flowchart-js"))
           (out-file (or (cdr (assq :file params))
                         (error "flowchart-js requires a \":file\" header argument")))
           (cmd (format "diagrams flowchart %s %s" in-file out-file))
           (verbosity (or (cdr (assq :verbosity params)) 0)))
      (with-temp-buffer
        (insert body)
        (goto-char (point-min))
        (write-region nil nil in-file))
      (shell-command cmd)
      nil))

  (defun org-babel-execute:yuml (body params)
    "Execute a block of yuml code with org-babel."
    (let ((in-file (org-babel-temp-file "" ".yuml"))
          (type (or (cdr (assq :type params))
                    (error "yuml requires a \":type\" header argument")))
          (out-file (or (cdr (assq :file params))
                        (error "yuml requires a \":file\" header argument")))
          (verbosity (or (cdr (assq :verbosity params)) 0)))
      (with-temp-buffer
        (insert body)
        (goto-char (point-min))
        (while (search-forward "\n" nil t) (replace-match "," nil t))
        (write-region nil nil in-file)
        (message (buffer-substring (point-min) (point-max)))
        (eyuml-create-document type out-file))
      (format "[[file:%s]]" out-file)))

  (defun eyuml-create-document (type &optional out-file)
    "Fetch remote document, TYPE could be class,usecase or activity."
    (let ((out-file (or out-file (eyuml-create-file-name))))
      (request (eyuml-create-url type)
        :parser 'buffer-string
        :success (cl-function
                  (lambda (&key data &allow-other-keys)
                    (when data
                      (with-temp-buffer 
                        (set-buffer-file-coding-system 'raw-text)
                        (insert data)
                        (write-region nil nil out-file)))))))))

Latex

auctex

(use-package auctex
    :defer t
    :commands (latex-mode plain-tex-mode)
    :mode "\\.tex\\'"
    :custom (org-latex-classes '(("beamer" "\\documentclass[presentation]{beamer}"
                                  ("\\section{%s}" . "\\section*{%s}")
                                  ("\\subsection{%s}" . "\\subsection*{%s}")
                                  ("\\subsubsection{%s}" . "\\subsubsection*{%s}"))
                                 ("article" "\\documentclass[11pt]{article}"
                                  ("\\section{%s}" . "\\section*{%s}")
                                  ("\\subsection{%s}" . "\\subsection*{%s}")
                                  ("\\subsubsection{%s}" . "\\subsubsection*{%s}")
                                  ("\\paragraph{%s}" . "\\paragraph*{%s}")
                                  ("\\subparagraph{%s}" . "\\subparagraph*{%s}"))
                                 ("report" "\\documentclass[11pt]{report}"
                                  ("\\part{%s}" . "\\part*{%s}")
                                  ("\\chapter{%s}" . "\\chapter*{%s}")
                                  ("\\section{%s}" . "\\section*{%s}")
                                  ("\\subsection{%s}" . "\\subsection*{%s}")
                                  ("\\subsubsection{%s}" . "\\subsubsection*{%s}"))
                                 ("book" "\\documentclass[11pt]{book}"
                                  ("\\part{%s}" . "\\part*{%s}")
                                  ("\\chapter{%s}" . "\\chapter*{%s}")
                                  ("\\section{%s}" . "\\section*{%s}")
                                  ("\\subsection{%s}" . "\\subsection*{%s}")
                                  ("\\subsubsection{%s}" . "\\subsubsection*{%s}"))))
    :config
    (setq TeX-auto-save t
          TeX-parse-self t
          TeX-save-query nil
          TeX-PDF-mode t)
    (setq-default TeX-master nil))

Preview pane

(use-package latex-preview-pane
  :defer t
  :commands  (latex-preview-pane-mode)
  :hook ((latex-mode . latex-preview-pane-mode)))

Completion

Vertico

Pacakge

(use-package vertico
  :elpaca (vertico :host github :repo "minad/vertico" :files (:defaults "extensions/*"))
  :init
  (vertico-mode)
  :config
  (setq vertico-cycle t))

Consult

(use-package consult
  ;; Replace bindings. Lazily loaded due by `use-package'.
  :general 
  (;; global
   "C-x b" 'consult-buffer                ;; orig. switch-to-buffer
   "C-x C-b" 'consult-buffer              ;; orig. switch-to-buffer-other-window
   "C-x r b" 'consult-bookmark            ;; orig. bookmark-jump
   "C-x p b" 'consult-project-buffer      ;; orig. project-switch-to-buffer
   "M-y" 'consult-yank-pop                ;; orig. yank-pop
   ;; M-s bindings in `search-map'
   "C-s" 'consult-line)
  (:keymaps 'minibuffer-local-map
            "M-s" 'consult-history                 ;; orig. next-matching-history-element
            "M-r" 'consult-history)                ;; orig. previous-matching-history-element
  ;; Enable automatic preview at point in the *Completions* buffer. This is
  ;; relevant when you use the default completion UI.
  :hook (completion-list-mode . consult-preview-at-point-mode)

  ;; The :init configuration is always executed (Not lazy)
  :init

  ;; Optionally configure the register formatting. This improves the register
  ;; preview for `consult-register', `consult-register-load',
  ;; `consult-register-store' and the Emacs built-ins.
  (setq register-preview-delay 0.5
        register-preview-function #'consult-register-format)

  ;; Optionally tweak the register preview window.
  ;; This adds thin lines, sorting and hides the mode line of the window.
  (advice-add #'register-preview :override #'consult-register-window)

  ;; Use Consult to select xref locations with preview
  (setq xref-show-xrefs-function #'consult-xref
        xref-show-definitions-function #'consult-xref)

  ;; Configure other variables and modes in the :config section,
  ;; after lazily loading the package.
  :config

  ;; Optionally configure preview. The default value
  ;; is 'any, such that any key triggers the preview.
  ;; (setq consult-preview-key 'any)
  ;; (setq consult-preview-key "M-.")
  ;; (setq consult-preview-key '("S-<down>" "S-<up>"))
  ;; For some commands and buffer sources it is useful to configure the
  ;; :preview-key on a per-command basis using the `consult-customize' macro.
  (consult-customize
   consult-theme :preview-key '(:debounce 0.2 any)
   consult-ripgrep consult-git-grep consult-grep
   consult-bookmark consult-recent-file consult-xref
   consult--source-bookmark consult--source-file-register
   consult--source-recent-file consult--source-project-recent-file
   ;; :preview-key "M-."
   :preview-key '(:debounce 0.4 any))

  ;; Optionally configure the narrowing key.
  ;; Both < and C-+ work reasonably well.
  (setq consult-narrow-key "<") ;; "C-+"

  ;; Optionally make narrowing help available in the minibuffer.
  ;; You may want to use `embark-prefix-help-command' or which-key instead.
  ;; (define-key consult-narrow-map (vconcat consult-narrow-key "?") #'consult-narrow-help)

  ;; By default `consult-project-function' uses `project-root' from project.el.
  ;; Optionally configure a different project root function.
  ;;;; 1. project.el (the default)
  ;; (setq consult-project-function #'consult--default-project--function)
  ;;;; 2. vc.el (vc-root-dir)
  ;; (setq consult-project-function (lambda (_) (vc-root-dir)))
  ;;;; 3. locate-dominating-file
  ;; (setq consult-project-function (lambda (_) (locate-dominating-file "." ".git")))
  ;;;; 4. projectile.el (projectile-project-root)
  ;; (autoload 'projectile-project-root "projectile")
  ;; (setq consult-project-function (lambda (_) (projectile-project-root)))
  ;;;; 5. No project support
  ;; (setq consult-project-function nil)
  )

Orderless

Make consult and friends work with partical matches, multi part words etc.

(use-package orderless
  :ensure t
  :custom
  (completion-styles '(orderless basic))
  (completion-category-overrides '((file (styles basic partial-completion)))))

Marginalia

(use-package marginalia
  ;; Bind `marginalia-cycle' locally in the minibuffer.  To make the binding
  ;; available in the *Completions* buffer, add it to the
  ;; `completion-list-mode-map'.
  :general (:keymaps 'minibuffer-local-map
                     "M-A" 'marginalia-cycle)

  ;; The :init section is always executed.
  :init

  ;; Marginalia must be actived in the :init section of use-package such that
  ;; the mode gets enabled right away. Note that this forces loading the
  ;; package.
  (marginalia-mode))

Icons

All icons completion

These icons require GUI so let’s only enable them when its available.

(use-package all-the-icons-completion
  :config
  (when (display-graphic-p) (all-the-icons-completion-mode)))

Nerd icons completion

These icons can also run on terminal so let’s optionally enable them.

(use-package nerd-icons-completion
  :config
  (unless (display-graphic-p) (nerd-icons-completion-mode)))

At point completion

Company

(use-package company
  :defer t
  :config
  (setq company-tooltip-limit 20                      ; bigger popup window
        company-idle-delay 0.2                        ; decrease delay before autocompletion popup shows
        company-echo-delay 0                          ; remove annoying blinking
        company-begin-commands '(self-insert-command) ; start autocompletion only after typing
        company-tooltip-align-annotations t           ; aligns annotation to the right hand side
        company-dabbrev-downcase nil                  ; don't downcase
        company-require-match nil
        company-minimum-prefix-length 0
        company-frontends '(company-pseudo-tooltip-frontend company-preview-frontend))
  :general (:keymaps 'company-mode-map
            "C-c ."  'company-complete)
  :hook (prog-mode . company-mode))

Codeium

(defun my/codeium-enable()
  "Enable codeium"
  (interactive)
  (add-to-list 'completion-at-point-functions #'codeium-completion-at-point))

(use-package codeium
  :elpaca (codeium :host github :repo "Exafunction/codeium.el")
  :hook (prog-mode . my/codeium-enable)
  :config
  (setq codeium/metadata/api_key
         (replace-regexp-in-string "\n\\'" ""
                                   (shell-command-to-string "pass show services/codeium/iocanel/api-key-emacs")))

  (setq use-dialog-box nil) ;; do not use popup boxes
  ;; get codeium status in the modeline
  (setq codeium-mode-line-enable
        (lambda (api) (not (memq api '(CancelRequest Heartbeat AcceptCompletion)))))
  (add-to-list 'mode-line-format '(:eval (car-safe codeium-mode-line)) t)
  ;; alternatively for a more extensive mode-line
  ;; (add-to-list 'mode-line-format '(-50 "" codeium-mode-line) t)

  ;; use M-x codeium-diagnose to see apis/fields that would be sent to the local language server
  (setq codeium-api-enabled
        (lambda (api)
          (memq api '(GetCompletions Heartbeat CancelRequest GetAuthToken RegisterUser auth-redirect AcceptCompletion))))
  ;; you can also set a config for a single buffer like this:
  ;; (add-hook 'python-mode-hook
  ;;     (lambda ()
  ;;         (setq-local codeium/editor_options/tab_size 4)))

  ;; You can overwrite all the codeium configs!
  ;; for example, we recommend limiting the string sent to codeium for better performance
  (defun my-codeium/document/text ()
    (buffer-substring-no-properties (max (- (point) 3000) (point-min)) (min (+ (point) 1000) (point-max))))
  ;; if you change the text, you should also change the cursor_offset
  ;; warning: this is measured by UTF-8 encoded bytes
  (defun my-codeium/document/cursor_offset ()
    (codeium-utf8-byte-length
     (buffer-substring-no-properties (max (- (point) 3000) (point-min)) (point))))
  (setq codeium/document/text 'my-codeium/document/text)
  (setq codeium/document/cursor_offset 'my-codeium/document/cursor_offset))

Smart Tab

(use-package smart-tab
  :config
  (progn
    (setq hippie-expand-try-functions-list '(yas-hippie-try-expand
                                             try-complete-file-name-partially))
                                        ;try-expand-dabbrev
                                        ;try-expand-dabbrev-visible
                                        ;try-expand-dabbrev-all-buffers
                                        ;try-complete-lisp-symbol-partially
                                        ;try-complete-lisp-symbol

    smart-tab-user-provided-completion-function 'company-complete
    smart-tab-using-hippie-expand t
    smart-tab-disabled-major-modes '(org-mode term-mode eshell-mode inferior-python-mode)
    (global-smart-tab-mode 1)))

Tools

Git

Magit

(use-package magit
  :defer t
  :after (general)
  :general
  (+general-global-git/version-control
   "b"  'magit-branch
   "B"  'magit-blame
   "c"  'magit-clone
   "f"  '(:ignore t :which-key "file")
   "ff" 'magit-find-file
   "fh" 'magit-log-buffer-file
   "i"  'magit-init
   "L"  'magit-list-repositories
   "m"  'magit-dispatch
   "S"  'magit-stage-file
   "s"  'magit-status
   "U"  'magit-unstage-file)
  :config
  (transient-bind-q-to-quit))

Git timemachine

(use-package git-timemachine)

Code review

(use-package code-review
  :commands (code-review-at-point))

Which Key

(use-package which-key
  :demand t
  :init
  (setq which-key-enable-extended-define-key t)
  :config
  (which-key-mode)
  :custom
  (which-key-side-window-location 'top)
  (which-key-sort-order 'which-key-key-order-alpha)
  (which-key-side-window-max-width 0.33) 
  (which-key-idle-delay 2)
  :diminish which-key-mode)

IDEE

(use-package idee :elpaca (idee :host github :repo "iocanel/idee" :branch "next" :files (:defaults "*.el"))
  :custom (idee/resources-dir (file-name-concat my/local-dir "idee"))
  :init
  (idee/init)
  (idee/java-init)
  :config
  (require 'idee-maven)
  (require 'idee-vterm)
  :commands (idee/init idee/maven-hydra/body))

Term

vterm

(use-package vterm
  :elpaca (vterm :post-build
                 (progn
                   (setq vterm-always-compile-module t)
                   (require 'vterm)
                   ;;print compilation info for elpaca
                   (with-current-buffer (get-buffer-create vterm-install-buffer-name)
                     (goto-char (point-min))
                     (while (not (eobp))
                       (message "%S"
                                (buffer-substring (line-beginning-position)
                                                  (line-end-position)))
                       (forward-line)))
                   (when-let ((so (expand-file-name "./vterm-module.so"))
                              ((file-exists-p so)))
                     (make-symbolic-link
                      so (expand-file-name (file-name-nondirectory so)
                                           "../../builds/vterm")
                      'ok-if-already-exists))))
  :commands (vterm vterm-other-window)
  :general
  (+general-global-application
   "t" '(:ignore t :which-key "terminal")
   "tt" 'vterm-other-window
   "t." 'vterm)
  :config
  (evil-set-initial-state 'vterm-mode 'emacs))

Applications

Youtube DLP

This section includes custom code for working with yt-dlp to download videos. There are existing plugins outhere that wrap around `yt-dlp` however they didn’t fit my needs for all kinds of reasons. As my primary use case is integration with other applications like:

I created something tailored to my needs.

The setup requires:

(defconst my/youtube-watch-url-prefix "https://www.youtube.com/watch?v=" "The prefix to the youtube urls")
(defvar my/youtube-download-path "/home/iocanel/Downloads/Youtube/" "The path to the youtube download folder")
(defvar my/youtube-download-by-id-path "/home/iocanel/Downloads/Youtube/by-id/" "The path to the youtube by-id folder")
(defvar my/youtube-download-by-title-path "/home/iocanel/Downloads/Youtube/by-title/" "The path to the youtube by-title folder")
(defvar my/yt-dlp-buffer-format "*Async yt-dlp: %s*" "The format of the buffer name that will be used to async download the video")
(defvar my/youtube-rencode-format "mkv" "The format that the downloaded video will be encoded into")
(defvar my/youtube-title-alist '())
Functions
(defun my/youtube-setup-p ()
  "Checks if youtube has been setup."
  (and (file-directory-p my/youtube-download-by-id-path) (file-directory-p my/youtube-download-by-title-path)
       (not (null (executable-find "yt-dlp")))))

(defun my/youtube-url-p (url)
  "Predicate that checks if URL points to youtube."
  (if (stringp url) (string-prefix-p my/youtube-watch-url-prefix url) nil))

(defun my/youtube-by-id-path (video-id-or-url)
  "Return the output path (by-id) for the specified youtube url."
  (let* ((video-id (if (my/youtube-url-p video-id-or-url) (substring video-id-or-url (length my/youtube-watch-url-prefix) (length video-id-or-url))) video-id-or-url))
    (concat my/youtube-download-by-id-path video-id "." my/youtube-rencode-format)))

(defun my/youtube-by-title-path (video-id-or-url)
  "Return the output path (by-title) for the specified youtube url."
  (let* ((video-id (if (my/youtube-url-p video-id-or-url) (substring video-id-or-url (length my/youtube-watch-url-prefix) (length video-id-or-url))) video-id-or-url)
         (title (replace-regexp-in-string "[^[:alnum:]]-" "_" (my/youtube-get-title video-id))))
    (concat my/youtube-download-by-title-path title "." my/youtube-rencode-format)))

(defun my/youtube-local-path (url)
  "Return the output path for the specified youtube url."
  (concat my/youtube-download-path
          (substring url (length my/youtube-watch-url-prefix) (length url))))

(defun my/youtube-get-by-id-prefix (video-id-or-url)
  "Return the prefix (by-id) of the youtube video that corresponds to the specified VIDEO-ID-OR-URL."
  (let* ((video-id (if (my/youtube-url-p video-id-or-url) (substring video-id-or-url (length my/youtube-watch-url-prefix) (length video-id-or-url)) video-id-or-url))
         (url (if (my/youtube-url-p video-id-or-url) video-id-or-url (concat my/youtube-watch-url-prefix video-id)))
         (output-template (concat my/youtube-download-by-id-path video-id)))
    (replace-regexp-in-string "\n\\'" "" (shell-command-to-string (format "yt-dlp \"%s\" --get-filename -o %s" url output-template)))))

(defun my/youtube-get-by-title-prefix (video-id-or-url)
  "Return the prefix (by-title) of the youtube video that corresponds to the specified VIDEO-ID-OR-URL."
  (let* ((video-id (if (my/youtube-url-p video-id-or-url) (substring video-id-or-url (length my/youtube-watch-url-prefix) (length video-id-or-url)) video-id-or-url))
         (url (if (my/youtube-url-p video-id-or-url) video-id-or-url (concat my/youtube-watch-url-prefix video-id)))
         (output-template (concat my/youtube-download-by-title-path video-id)))
    (replace-regexp-in-string "\n\\'" "" (shell-command-to-string (format "yt-dlp \"%s\" --get-filename -o %s" url output-template)))))

(defun my/youtube-get-by-id-filename (video-id-or-url)
  "Return the filename (by-id) of the youtube video that corresponds to the specified VIDEO-ID-OR-URL."
  (let* ((video-id (if (my/youtube-url-p video-id-or-url) (substring video-id-or-url (length my/youtube-watch-url-prefix) (length video-id-or-url)) video-id-or-url)))
    (concat my/youtube-download-by-id-path video-id "." my/youtube-rencode-format)))

(defun my/youtube-get-by-title-filename (title)
  "Return the filename (by-title) of the youtube video that corresponds to the specified TITLE."
  (let* ((title-clean (replace-regexp-in-string "[^[:alnum:]]" "_" title)))
    (concat my/youtube-download-by-title-path title-clean "." my/youtube-rencode-format )))

(defun my/youtube-get-title (video-id-or-url)
  "Return the filename of the youtube video that corresponds to the specified VIDEO-ID-OR-URL."
  (let* ((video-id (if (my/youtube-url-p video-id-or-url) (substring video-id-or-url (length my/youtube-watch-url-prefix) (length video-id-or-url)) video-id-or-url))
         (url (if (my/youtube-url-p video-id-or-url) video-id-or-url (concat my/youtube-watch-url-prefix video-id)))
         (entry (assoc video-id my/youtube-title-alist))
         (output-template (concat my/youtube-download-path video-id)))
    (if entry
        (cdr entry)
      (progn
        (let ((title (replace-regexp-in-string "\n\\'" "" (shell-command-to-string (format "yt-dlp \"%s\" --get-title -o %s" url output-template)))))
          (setq my/youtube-title-alist (cons `(,video-id . ,title) my/youtube-title-alist))
          title)))))

(defun my/youtube-download (video-id-or-url &optional callback)
  "Download the youtube video from VIDEO-ID-OR-URL to a temporary file and return the path to it."
  (interactive "P")
  (when (not video-id-or-url)
    (setq video-id-or-url (read-string "Enter a youtube video id or URL: ")))
  (if (string-blank-p video-id-or-url)
      (message "No video id or URL provided. Aborting")
    (let* ((video-id (if (my/youtube-url-p video-id-or-url) (substring video-id-or-url (length my/youtube-watch-url-prefix) (length video-id-or-url)) video-id-or-url))
           (template (my/youtube-get-by-id-prefix video-id-or-url))
           (output-buffer (generate-new-buffer (format my/yt-dlp-buffer-format video-id)))
           (proc (progn
                   (message "Downloading video into: %s" template)
                   (async-shell-command (format "yt-dlp \"%s\" --no-part --hls-prefer-ffmpeg --recode-video %s -f 'bv*+ba/b' -o %s" video-id-or-url my/youtube-rencode-format template) output-buffer)
                   (get-buffer-process output-buffer))))
      (when callback (set-process-sentinel  proc callback))
      template)))

(defun my/youtube-callback (video-id-or-url title &optional func)
  "Create a callback for the specified VIDEO-ID-OR-URL"
  (lambda (p s) (when (memq (process-status p) `(exit signal))
                  (let* ((video-id (if (my/youtube-url-p video-id-or-url) (substring video-id-or-url (length my/youtube-watch-url-prefix) (length video-id-or-url)) video-id-or-url))
                         (by-id-filename (my/youtube-get-by-id-filename video-id))
                         (by-title-filename (my/youtube-get-by-title-filename title)))
                    (message "Linking %s to %s" by-id-filename by-title-filename)
                    (shell-command-to-string (format "ln -s %s %s" by-id-filename by-title-filename))
                    (cond
                     ((stringp func) (funcall (intern func) by-title-filename))
                     ((symbolp func) (funcall func by-title-filename))
                     (t "video downloaded and linked")))
                  (shell-command-sentinel p s))))

Xwidget

(use-feature xwidget-webkit
  :after popper
  :commands (xwidget-webkit-browse-url)
  :config
  (setq popper-reference-buffers (add-to-list 'popper-reference-buffers  "\\*xwidget-webkit.*\\*")))

EWW

Package

(use-feature eww
  :defer t
  :commands  (eww)
  :init
  (defvar my/eww-ignore-tag-nav-enabled t "Ignore navigation tag")
  :config
  (defun my/eww-ignore-tag-nav (dom)
    "Ignores navigation tag."
    (when (not my/eww-ignore-tag-nav-enabled) (shr-generic dom)))
  (add-to-list 'shr-external-rendering-functions '(nav . my/eww-ignore-tag-nav)))

Ace Link

(use-package ace-link
  :after eww
  :defer t
  :commands (eww-back-url eww-forward-url ace-link-eww)
  :general  (:keyamps eww-mode-map
                      "<" 'eww-back-url
                      ">" 'eww-forward-url
                      "C-c f" 'ace-link-eww))

Bongo

Package

(use-package bongo
  :ensure t
  :init
  (defvar my/bongo-yt-dlp-enabled t "Download videos using yt-dlp and play them as local files, instead of streaming them")
  (setq bongo-mpv-initialization-period 1
        popper-reference-buffers (add-to-list 'popper-reference-buffers  "\\*Bongo.*\\*")
        display-buffer-alist (add-to-list 'display-buffer-alist `("\\*\\(Bongo.*\\)\\*"
                                                                  (display-buffer-in-side-window)
                                                                  (window-height . 0.40)
                                                                  (side . bottom)
                                                                  (slot . 1))))
  :config
  (setq bongo-enabled-backends '(mpv)
        bongo-backend-matchers '((mpv (local-file "file:" "http:" "https:" "ftp:") "ogg" "flac" "mp3" "mka" "wav" "wma" "mpg" "mpeg" "vob" "avi" "ogm" "mp4" "mkv" "mov" "asf" "wmv" "rm" "rmvb" "ts")))
  (evil-set-initial-state 'bongo-mode 'emacs) ;; Let's disable evil mode for bongo
  :custom
  (bongo-default-directory "~/Documents/music")
  (bongo-mplayer-extra-arguments '("-af" "scaletempo" "-vf" "scale"))

  :commands (bongo bongo-playlist)
  :general (:keymaps 'bongo-mode-map
                     "i" 'bongo-insert-file
                     "I" 'bongo-insert-special
                     "u" 'bongo-insert-uri
                     "e" 'bongo-append-enqueue
                     "b" 'bongo-switch-buffers
                     "x" 'bongo-delete-line
                     "j" 'bongo-next-object-line
                     "J" 'bongo-next-header-line
                     "k" 'bongo-previous-object-line
                     "K" 'bongo-previous-header-line
                     "p" 'bongo-play-line
                     "s" 'bongo-stop))

Filename Mappings

Bongo filename mampings Some podcasts do use redirects, that are not supported by all backends. In these cases we want to apply the redirects upfront. hacky solution

(defvar my/bongo-filename-mapping-alist '(("http://dts.podtrac.com/redirect.mp3/feeds.soundcloud.com" . "https://feeds.soundcloud.com")))

Bluetooth

Utilities to query bluetooth devices so that we pass the right arguemtns to player. Requires: https://www.nirsoft.net/utils/bluetoothcl.html On archlinux this is installed by:

sudo pacman -Sy bluez bluez-utils
(defun my/bluetooth-setup-p()
  "Check if required blueetooth tools are available"
  (not (null (executable-find "bluetoothcl"))))

(defun my/bluetooth-device-connected-p (device-id)
  "Predicate that checks if bluetooth device with DEVICE-ID is currently connected"
  (if (my/bluetooth-setup-p)
      (not (= (length (replace-regexp-in-string "\n\\'" "" (shell-command-to-string (format "bluetoothctl info %s | grep 'Connected: yes'" device-id)))) 0))
    nil))

(defun my/get-bluetooth-audio-devices (&optional connected)
  "Returns the the bluetooth audio devices that are available. Optional flag CONNECTED can filter out devices that are/aren't currently connected"
  (if (my/bluetooth-setup-p)
      (let ((device-ids (split-string (replace-regexp-in-string "\n\\'" "" (shell-command-to-string "bluetoothctl paired-devices | cut -d ' ' -f2")) "\n")))
        (if connected
            (seq-filter 'my/bluetooth-device-connected-p device-ids)
          device-ids)) nil))

(defadvice bongo-play-line (around bongo-play-line-around activate)
  "Check if bluetooth is connected and use pulse audio driver."
  (interactive)
  (if (my/get-bluetooth-audio-devices t)
      (let ((bongo-mplayer-audio-driver "pulse")) ad-do-it)
    (let ((bongo-mplayer-audio-driver nil)) ad-do-it)))

Youtube callback

A callback for yt-dlp (see Youtube DLP)

(defun my/bongo-start-callback (process signal)
  "Callback to be called when a youtube video gets downloaded."
  (when (memq (process-status process) '(exit signal))
    (message "Video finished!")
    (with-bongo-playlist-buffer
     (when (not (bongo-playing-p)) (bongo-start/stop)))
    (shell-command-sentinel process signal)))

Extras

(defun my/apply-string-mappings (source mappings)
  "Apply MAPPINGS to the SOURCE string"
  (let ((result source))
    (dolist (mapping mappings)
      (let ((key (car mapping))
            (value (cdr mapping)))
        (setq result (replace-regexp-in-string (regexp-quote key) value result))))
    result))

(defun my/bongo-enqueue-file (filename &rest ignored)
  "Enqueue media from FILENAME to playlist."
  (let ((f (my/apply-string-mappings filename my/bongo-filename-mapping-alist)))
    (bongo-playlist)
    (goto-char (point-max))
    (bongo-insert-file filename)
    (goto-char (point-max))
    (search-backward filename)))

(defun my/bongo-enqueue-file-and-play (filename &rest ignored)
  "Enqueue media from FILENAME to playlist."
  (my/bongo-enqueue-file filename)
  (when (not (bongo-playing-p)) (bongo-start/stop)))

(defun my/bongo-play-file (filename &rest ignored)
  "Play media from FILENAME."
  (require 'bongo)
  (with-temp-bongo-playlist-buffer
   (bongo-insert-file filename)
   (backward-char)
   (bongo-play-line))) 

(defun my/bongo-play (file-or-url &optional title)
  "Play the FILE-OR-URL in the bongo player."
  (interactive "P")
  (when (not file-or-url)
    (setq file-or-url (read-string "Enter media File or URL: ")))
  (if (not (string-blank-p file-or-url))
      (if (and my/bongo-yt-dlp-enabled (my/youtube-url-p file-or-url))
          (let* ((video-id (substring file-or-url (length my/youtube-watch-url-prefix) (length file-or-url)))
                 (title (or title (my/youtube-get-title video-id)))
                 (template (concat my/youtube-download-path video-id))
                 (existing-file-name (my/youtube-get-by-title-filename file-or-url))
                 (output-path (my/youtube-local-path file-or-url)))
            (if (and existing-file-name (file-exists-p existing-file-name))
                (progn 
                  (message "Youtube video:%s already exists, playing ..." existing-file-name)
                  (my/bongo-play-file existing-file-name))
              (progn
                (message "Youtube video file:%s does not exist, donwloading ..." output-path)
                (my/youtube-download file-or-url (my/youtube-callback video-id title 'my/bongo-play-file)))))
        (my/bongo-play-file file-or-url))
    (message "No File or URL! Aborting playback.")))

(defun my/bongo-play-url-at-point ()
  "Play the url at point in the bonog player."
  (interactive)
  (let* ((url (or (thing-at-point-url-at-point) (my/org-link-url-at-point))))
    (when url (my/bongo-play url))))

(defun my/org-link-url-at-point ()
  "Fetch the url of the org link at point."
  (let* ((org-link (org-element-context))
         (raw-link (org-element-property :raw-link org-link)))
    raw-link))

Capturing

(defun my/bongo-currently-playing-elapsed-time()
  "Log the elapsed time"
  (interactive)
  (format "%s" (bongo-elapsed-time)))

(defun my/bongo-currently-playing-url ()
  "Return the file name of the file currently playing."
  (interactive)
  (with-bongo-playlist-buffer
   (cdr (assoc 'file-name (cdr bongo-player)))))

(defun my/bongo-play-org-entry-at-point ()
  "Play the media file that corresponds to the currently selected org entry."
  (interactive)
  "Play the play the media file at point."
  (let* ((p (point))
         (url  (org-entry-get nil "URL"))
         (time (org-entry-get nil "ELAPSED")))
    (with-bongo-playlist-buffer
     (bongo-insert-uri url)
     (bongo-previous-object)
     (bongo-play)
     (when (stringp time) (bongo-seek-to (string-to-number time))))))

Advices

(defadvice shr-browse-url (around shr-browse-url-around (&optional external mouse-event new-window) activate)
  "Open mp3 URLs using bongo"
  (let ((url (get-text-property (point) 'shr-url)))
    (if (s-suffix? ".mp3" url)
        (my/bongo-enqueue-file url)
      ad-do-it))) 

Elfeed

Package

(defvar my/elfeed-presenter-alist nil "An alist that contains predicates and presenter fucntions")
(use-package elfeed
  :after (popper)
  :init
  (defvar my/elfeed-external-mode-map (make-sparse-keymap))
  (define-minor-mode my/elfeed-external-mode "A minor mode to add external modes `showing` elfeed entry content" (use-local-map my/elfeed-external-mode-map))
  :config
  (setq elfeed-show-entry-switch #'pop-to-buffer
        elfeed-search-remain-on-entry t
        popper-reference-buffers (add-to-list 'popper-reference-buffers  "\\*elfeed-entry\\*")
        display-buffer-alist (add-to-list 'display-buffer-alist `("\\*\\(elfeed-entry\\)\\*"
                                                                  (display-buffer-in-side-window)
                                                                  (window-height . 0.40)
                                                                  (side . bottom)
                                                                  (slot . 0))))
  (evil-set-initial-state 'elfeed-search-mode 'emacs) 
  (evil-set-initial-state 'elfeed-show-mode 'emacs) 

  (defun my/elfeed-next-entry (&optional visited)
    (interactive)
    "Moves to next elfeed entry."
    (select-window (get-buffer-window "*elfeed-search*") 'mark-for-redisplay)
    (when elfeed-search-remain-on-entry
      (elfeed-goto-line (+ (current-line) elfeed-search--offset 1)))
    (let ((current (elfeed-search-selected :ignore-region)))
      (elfeed-untag current 'unread)
      (elfeed-search-update-entry current)
      (elfeed-search-show-entry current))
    (select-window (previous-window)))

  (defun my/elfeed-prev-entry (&optional visited)
    (interactive)
    "Moves to next elfeed entry."
    (select-window (get-buffer-window "*elfeed-search*"))
    (if elfeed-search-remain-on-entry
        (elfeed-goto-line (- (current-line) (- elfeed-search--offset 1)))
      (elfeed-goto-line (- (current-line) (abs (- 2 elfeed-search--offset)))))
    (redisplay)
    (let ((current (elfeed-search-selected :ignore-region)))
      (elfeed-untag current 'unread)
      (elfeed-search-update-entry current)
      (elfeed-search-show-entry current))
    (select-window (previous-window)))

  (defun my/elfeed-show-dwim ()
    "Open feed in the most fitting mode."
    (interactive)
    (cl-loop for (predicate . presenter) in my/elfeed-presenter-alist
           when (funcall predicate elfeed-show-entry)
           return (funcall presenter elfeed-show-entry)))

(defun my/elfeed-open-in-dwim (entry)
  "Open feed in the most fitting mode."
  (interactive (list (elfeed-search-selected :ignore-region)))
    (cl-loop for (predicate . presenter) in my/elfeed-presenter-alist
           when (funcall predicate entry)
           return (funcall presenter entry)))

  :general (general-define-key :keymaps 'elfeed-search-mode-map
                               "j" 'next-line
                               "k" 'previous-line
                               "d" 'my/elfeed-open-in-dwim
                               "<tab>" 'my/elfeed-next-entry
                               "C-<tab>" 'my/elfeed-prev-entry
                               "E" 'my/elfeed-enqueue-media-url
                               "Q" 'my/elfeed-save-and-quit
                               "r" 'my/elfeed-mark-as-read
                               "R" 'my/elfeed-mark-all-as-read)
  (general-define-key :keymaps 'elfeed-show-mode-map
                      "<tab>" 'my/elfeed-next-entry
                      "C-<tab>" 'my/elfeed-prev-entry
                      "d" 'my/elfeed-show-dwim)
  (general-define-key :keymaps 'my/elfeed-external-mode-map
                      "<tab>" 'my/elfeed-next-entry
                      "C-<tab>" 'my/elfeed-prev-entry))

Org

(use-package elfeed-org
  :after (elfeed org)
  :custom (rmh-elfeed-org-files '("~/Documents/org/blogs.org"))
  :config (elfeed-org))

Mark

(defun my/elfeed-mark-as-read ()
  "Mark all items in the elfeed buffer as read."
  (interactive)
  (my/mark-line 0)
  (elfeed-search-untag-all-unread))

(defun my/elfeed-mark-all-as-read ()
  "Mark all items in the elfeed buffer as read."
  (interactive)
  (mark-whole-buffer)
  (elfeed-search-untag-all-unread))

(defun my/elfeed-mark-current-as-read ()
  (interactive)
  "Mark current entry as read."
  (let ((current (elfeed-search-selected :ignore-region)))
    (elfeed-untag current 'unread)
    (elfeed-search-update-entry current)
    (elfeed-db-save-safe)))

Youtube

  (defun my/elfeed-start-bongo-callback (process signal)
    "Callback to be called when a youtube video gets downloaded."
    (when (memq (process-status process) '(exit signal))
      (message "Video download finished!")
      (when (not (bongo-playing-p)) (bongo-start/stop)))
    (shell-command-sentinel process signal))

(defun my/elfeed-entry-youtube-p (entry)
  "Predicate that checks if ENTRY points to youtube."
    (let ((link (elfeed-entry-link entry))
          (enclosure (car (elt (elfeed-entry-enclosures entry) 0))))
    (or (my/youtube-url-p link) (my/youtube-url-p enclosure))))

  (defun my/elfeed-open-in-youtube (entry)
    "Display the currently selected item in youtube."
    (interactive (list (elfeed-search-selected :ignore-region)))
    (require 'elfeed-show)
    ;;(my/elfeed-mark-current-as-read)
    (when (elfeed-entry-p entry)
      (let* ((url (elfeed-entry-link entry))
             (title (elfeed-entry-title entry)))
        (my/bongo-play url title)
        (my/elfeed-external-mode 1))))

  (defun my/elfeed-show-in-youtube ()
    "Display the currently shown item in youtube."
    (interactive)
    (require 'elfeed-show)
    (when (elfeed-entry-p elfeed-show-entry)
      (let* ((link (elfeed-entry-link elfeed-show-entry))
             (download-path (my/bongo-enqueue-file link)))
        (my/bongo-enqueue-file-and-play (concat "file://" download-path)))))
Elfeed DWIM Registration
(setq my/elfeed-presenter-alist (add-to-list 'my/elfeed-presenter-alist '(my/elfeed-entry-youtube-p . my/elfeed-open-in-youtube)))

Bongo

(defun my/mp3-url-p (url)
  "Predicate that checks if URL points to mp3."
  (if (stringp url) (string-match-p "\\.mp3(?.*)*$" url) nil))

(defun my/normalize-mp3-url (url)
  "Strips query params from mp3 url"
  (let ((index (string-match-p ".mp3?" url)))
    (if index (concat (substring url 0 index) ".mp3")
      url)))

(defun my/elfeed-start-bongo-callback (process signal)
  "Callback to be called when a youtube video gets downloaded."
  (when (memq (process-status process) '(exit signal))
    (message "Video download finished!")
    (when (not (bongo-playing-p)) (bongo-start/stop)))
  (shell-command-sentinel process signal))

(defun my/elfeed-enqueue-media-url (entry)
  "Enqueue media url for the specified ENTRY."
  (interactive (list (elfeed-search-selected :ignore-region)))
  (require 'elfeed-show)
  (when (elfeed-entry-p entry)
    (let ((link (elfeed-entry-link entry))
          (enclosure (car (elt (elfeed-entry-enclosures entry) 0))))
      (cond
       ((my/mp3-url-p link) (my/bongo-enqueue-file-and-play (my/normalize-mp3-url link)))
       ((my/mp3-url-p enclosure) (my/bongo-enqueue-file-and-play (my/normalize-mp3-url enclosure)))))))

    (defun my/elfeed-entry-bongo-p (entry)
      "Predicate that checks if ENTRY points to media file (mp3 etc)."
      (let ((link (elfeed-entry-link entry))
            (enclosure (car (elt (elfeed-entry-enclosures entry) 0))))
        (or (my/mp3-url-p link) (my/mp3-url-p enclosure))))

    (defun my/elfeed-open-in-bongo (entry)
      "Display the currently selected item in bongo."
      (interactive (list (elfeed-search-selected :ignore-region)))
      (require 'elfeed-show)
      (my/elfeed-mark-current-as-read)
      (when (elfeed-entry-p entry)
        (let ((link (elfeed-entry-link entry)))
          ;;(when (derived-mode-p 'elfeed-search-mode) (my/split-and-follow-vertically))
          (my/elfeed-enqueue-media-url entry))))

    (defun my/elfeed-show-in-bongo ()
      "Display the currently shown item in bongo."
      (interactive)
      (require 'elfeed-show)
      (when (elfeed-entry-p elfeed-show-entry)
        (let ((link (elfeed-entry-link elfeed-show-entry)))
          (my/elfeed-enqueue-media-url elfeed-show-entry))))
Elfeed DWIM Registration
(setq my/elfeed-presenter-alist (add-to-list 'my/elfeed-presenter-alist '(my/elfeed-entry-bongo-p . my/elfeed-open-in-bongo)))

Browser

Common
(defun my/elfeed-entry-www-p (entry)
      "Predicate that checks if ENTRY points to http(s)."
      (let ((link (elfeed-entry-link entry))
            (enclosure (car (elt (elfeed-entry-enclosures entry) 0))))
        (and (s-starts-with-p "http" link t)
             ;; Either youtube is not enabled or its not a youtube buffer
             (or (not (fboundp 'my/elfeed-entry-youtube-p)) (not (my/elfeed-entry-youtube-p entry))))))
EWW
(defun my/elfeed-open-in-eww (entry)
  "Display the currently selected item in eww."
  (interactive (list (elfeed-search-selected :ignore-region)))
  (require 'elfeed-show)
  (my/elfeed-mark-current-as-read)
  (when (elfeed-entry-p entry)
    (let ((link (elfeed-entry-link entry)))
      (eww link)
      (rename-buffer (format "*elfeed eww %s*" link)))))

(defun my/elfeed-show-in-eww ()
  "Display the currently shown item in eww."
  (interactive)
  (require 'elfeed-show)
  (when (elfeed-entry-p elfeed-show-entry)
    (let ((link (elfeed-entry-link elfeed-show-entry)))
      (eww link)
      (rename-buffer (format "*elfeed eww %s*" link)))))
Xwidget Webkit Browser
(defun my/elfeed-open-in-xwidget-webkit-browser (entry)
  "Display the currently selected item in xwidget-webkit-browser."
  (interactive (list (elfeed-search-selected :ignore-region)))
  (require 'elfeed-show)
  (my/elfeed-mark-current-as-read)
  (when (elfeed-entry-p entry)
    (let ((link (elfeed-entry-link entry)))
      (my/use-pop-to-buffer
        (xwidget-webkit-browse-url link)))))

(defun my/elfeed-show-in-xwidget-webkit-browser ()
  "Display the currently shown item in xwidget-webkit-browser."
  (interactive)
  (require 'elfeed-show)
  (when (elfeed-entry-p elfeed-show-entry)
    (let ((link (elfeed-entry-link elfeed-show-entry)))
      (my/use-pop-to-buffer
        (xwidget-webkit-browse-url link)))))
Elfeed DWIM Registration
(setq my/elfeed-presenter-alist (add-to-list 'my/elfeed-presenter-alist '(my/elfeed-entry-www-p . my/elfeed-open-in-xwidget-webkit-browser)))

Additional Functions

Troubleshooting

Display Modes

(defun my/echo-major-mode ()
  "Display the current major mode in a message."
  (interactive)
  (message "Major mode: %s" major-mode))

Display Org Src Mode

(defun my/get-org-src-mode ()
  "Display the current major mode in a message."
  (interactive)
  (let ((element (org-element-context)))
    (when (and (eq (org-element-type element) 'src-block)
               (org-element-property :begin element)
               (org-element-property :end element))
      (org-element-property :language element))))

(defun my/echo-org-src-mode ()
  "Display the current major mode in a message."
  (interactive)
  (message "Cursor is within a `#+begin_src` block. Language: %s" (my/get-org-src-mode)))

Key Bindings

Binding functions

To decouple the binding definition from specific packages that may come and go, let’s create some abstraction commands. These commands should check if the desired function / command is available and delegate to it. Using `cond` it should be simply to easily change priorites.

Macro

(defmacro alternatives! (name type &rest alts)
  `(defun ,(intern (concat "my/" (symbol-name name))) (&rest args)
     (interactive ,(when type type))
     (cond ,@(mapcar (lambda (arg)
                       `((fboundp (quote ,(intern (symbol-name arg)))) (if args (,arg args) (,arg))))
                     alts)
           (t (if args (,name args) (,name))))))

Buffers list

(alternatives! list-buffers nil helm-buffers-list)

Find files

(alternatives! find-file "P" helm-find-files)

Recent files

(alternatives! recentf nil helm-recentf)

Binding definition

Let’s configure here all our keybindings to keep things tidy!

(leader-key!

  ;;
  ;; Apps
  ;;

  "a" '(:ignore t :wk "apps")
  ;; Bongo
  "ab" '(:ignore t :wk "bongo")
  "abb" '(bongo t :wk "bongo show")
  "abs" '(bongo-start/stop t :wk "bongo start/stop")
  "abi" '(bongo-insert-special t :wk "bongo insert")
  "abn" '(bongo-next t :wk "bongo next")
  "abp" '(bongo-next t :wk "bongo previous")
  ;; Elfeed
  "ae" '(elfeed t :wk "elfeed")


  ;; 
  ;; Open
  ;;
  "o" '(:ignore t :wk "open")
  "of" '(find-file :wk "open file")
  "ob" '(consult-buffer :wk "open buffer")
  "oc" '(vterm :wk "open console")
  "or" '(recentf :wk "open buffer")
  "op" '(project-switch-project :wk "open project")
  "SPC" '(project-find-file :wk "open project file")

  ;; 
  ;; Buffer
  ;;
  "b" '(:ignore t :wk "buffer")
  "bb" '(switch-to-buffer :wk "buffer switch")
  "bk" '(kill-this-buffer :wk "buffer kill")
  "bn" '(next-buffer :wk "next buffer")
  "bp" '(previous-buffer :wk "previous buffer")
  "br" '(revert-buffer :wk "peload buffer")
  "bs" '(consult-line :wk "buffer search")

  ;;
  ;; Magit
  ;;
  "g" '(:ignore t :wk "git")
  "gg" '(magit :wk "magit")
  "gt" '(git-timemachine :wk "git time machine")

  ;;
  ;; Search
  ;;
  "s" '(:ignore t :wk "search")
  "sg" '(consult-git-grep :wk "search git grep")
  "sr" '(consult-ripgrep :wk "search rip grep")
  "sb" '(consult-line :wk "search buffer")
  "so" '(:ignore t :wk "search org")
  "sor" '(org-raom-node-find :wk "search org-roam")

  ;;
  ;; Insert
  ;;
  "i" '(:ignore t :wk "insert")
  "io" '(:ignore t :wk "insert org")
  "ior" '(org-roam-node-insert :wk "insert org-roam")

  ;;
  ;; LSP
  ;;
  "l" '(:ignore t :wk "lsp")
  "lgd" '(xref-find-definitions :wk "lsp goto definition")
  "lgr" '(xref-find-references :wk "lsp find references")
  "ltd" '(eglot-find-typeDefinition :wk "lsp type definition")
  "lca" '(eglot-code-actions :wk "lsp go back")
  "lb" '(xref-go-back :wk "lsp go back")
  "lf" '(xref-go-forward :wk "lsp go forward")

  ;;
  ;; Window
  ;;
  "w" '(:ignore t :wk "window")
  "ws" '(:ignore t :wk "window split")
  "wsh" '(split-window-horizontally :wk "window split horizontally")
  "wsv" '(split-window-vertically :wk "window split vertically")
  "wp" '(ace-select-window :wk "window pick")
  "wn" '(other-window :wk "window next")
  "wo" '(other-window :wk "window other")
  "wk" '(ace-delete-window :wk "window kill")
  "wu" '(winner-undo :wk "window undo")
  "wr" '(winner-redo :wk "window redo")


  ;; Tools
  "t" '(:ignore t :wk "tools")
  "tm" '(idee/maven-hydra/body t :wk "maven")
  )