Skip to content
This repository has been archived by the owner on Nov 14, 2022. It is now read-only.
/ emacs-config Public archive

ARCHIVED: Personal setups for Emacs, (mostly) tailored for data analysis with R/Python and reproducible researchs with Org mode/Markdown

Notifications You must be signed in to change notification settings

hieutkt/emacs-config

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 

Repository files navigation

Emacs Configuration

NOTE: Since March 2020, I have migrated to Doom Emacs, and won’t be using this config anymore. This emacs configuration will be archived at this Github repo.

Table of contents

Prerequisite

Reproducible information

Current emacs version:

I build my emacs from source, with the following configuration options:

Installing

The whole thing is driven by the following .emacs.

(defvar bootstrap-version)
(let ((bootstrap-file
       (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
      (bootstrap-version 5))
  (unless (file-exists-p bootstrap-file)
    (with-current-buffer
        (url-retrieve-synchronously  "https://raw.githubusercontent.com/raxod502/straight.el/develop/install.el"
                                     'silent 'inhibit-cookies)
      (goto-char (point-max))
      (eval-print-last-sexp)))
  (load bootstrap-file nil 'nomessage))

;; Install use-package
(straight-use-package 'use-package)
(setq straight-use-package-by-default t)
(use-package diminish)
(use-package bind-key :straight use-package)

(straight-use-package 'org-plus-contrib)

(setq config-directory "~/Documents/Emacs/emacs-config/")
(org-babel-load-file
 (expand-file-name "init.org" config-directory))

Then I clone this git into “~/Documents/Emacs/emacs-config/”. Then everything is installed using straight.el and use-package.

Initiation

Things may breaks mid-setup (this is a personal config after all), so I want the basic things to be put in place first.

Setup and bootstrap

First and foremost: enable lexical-binding

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

The first part of the config is to ensure things run smoothly during startup time.

;; The GC can easily double startup time,
;; so we suppress it at startup by turning up `gc-cons-threshold`
;; and perhaps `gc-cons-percentage` temporarily:

(setq gc-cons-threshold most-positive-fixnum ; 2^61 bytes
      gc-cons-percentage 0.6)

(add-hook 'emacs-startup-hook
          (lambda ()
            (setq gc-cons-threshold 16777216 ; 16mb
                  gc-cons-percentage 0.1)))

;; Auto-revert mode
(global-auto-revert-mode 1)
(setq auto-revert-interval 0.5)

;; Backup stored in /tmp
(setq backup-directory-alist
      `((".*" . ,temporary-file-directory)))
(setq auto-save-file-name-transforms
      `((".*" , temporary-file-directory t)))

;; Delete old backup
(message "Deleting old backup files...")
(let ((week (* 60 60 24 7))
      (current (float-time (current-time))))
  (dolist (file (directory-files temporary-file-directory t))
    (when (and (backup-file-name-p file)
               (> (- current (float-time (nth 5 (file-attributes file))))
                  week))
      (message "%s" file)
      (delete-file file))))

;; Information settings
(setq user-full-name "Hiếu Phẩy"
      user-mail-address "[email protected]")

;; Set emacs as a client
(use-package server
  :config
  (unless (server-running-p) (server-start)))

Better Defaults

Emacs is made in 1976, so it retains some weird defaults. I modified them to my opinionated preferences here:

;; Ensure starting from home directory
(cd "~/")

;; Everything utf-8
(set-language-environment "UTF-8")
(prefer-coding-system 'utf-8)
(set-terminal-coding-system 'utf-8)
(set-keyboard-coding-system 'utf-8)
(set-buffer-file-coding-system 'utf-8)
(set-default-coding-systems 'utf-8)
(setq default-input-method 'vietnamese-telex)
(setq search-default-mode 'char-fold-to-regexp)

;; Set some annoying command disabled
;; the function `unbind-key` require `use-package`
(unbind-key "<insert>") 		;overwrite-mode
(unbind-key "C-x C-z"   )		;suspend-frame
(unbind-key "C-x m")			;compose-mail
(unbind-key "C-x C-l")                  ;downcase region
(unbind-key "C-x <right>")              ;next-buffer
(unbind-key "C-x <left>")               ;previous-buffer
(unbind-key "C-v")                      ;scroll-up-command
;; Rebind some commands to more sane hotkeys
(bind-key "M-p" 'other-window)

;; And keep quited please
(setq visible-bell 1)

;; Delete marked region when input
(delete-selection-mode 1)

;; Pressing TAB indents first then complete
(setq tab-always-indent 'complete)

;; Indent always use spaces instead of tabs
(setq indent-tabs-mode nil)

;; Global mark ring
(setq global-mark-ring-max 50000)

;; Auto save abbreviation
(setq save-abbrevs 'silently)

;; "Yes or no"? Too much writing
(defalias 'yes-or-no-p 'y-or-n-p)

;; Make comint promts read-only
(setq comint-prompt-read-only t)

;; Set kill ring size
(setq global-mark-ring-max 50000)

;; Bound undo to C-z
(global-set-key (kbd "C-z") 'undo)

;; Scrolling
(setq scroll-step 1) ; keyboard scroll one line at a time
(setq scroll-preserve-screen-position t)
(setq scroll-conservatively 101)

Informative variable

These variable is for handily tangle blocks on different OSes.

(set 'linuxp (when (eq system-type 'gnu/linux) "yes"))
(set 'windowp (when (eq system-type 'windows-nt) "yes"))

Appearence

Interface

;; Startup screen
(setq inhibit-startup-screen t)

;; Global truncate line, except in text-based modes
(set-default 'truncate-lines t)

;; Initialize Emacs full screen
;; (add-to-list 'initial-frame-alist '(fullscreen . maximized))
;; (global-set-key (kbd "<f11>") 'toggle-frame-maximized)

;; No startup messages on *scratch* buffer
(setq initial-scratch-message "")

;; Cursor type
(setq-default cursor-type 'bar
              cursor-in-non-selected-windows nil)

;; Global font-lock mode
(setq global-font-lock-mode t)


;; Enable line number and column number
(setq column-number-mode t)

;; Display line number
;; (add-hook 'text-mode-hook (lambda () (setq display-line-numbers 'relative)))
(add-hook 'prog-mode-hook (lambda () (setq display-line-numbers 'relative)))
(add-hook 'conf-mode-hook (lambda () (setq display-line-numbers 'relative)))
(setq-default display-line-numbers-width 2)
(setq-default display-line-numbers-widen t)

;; Disable tool bar, menu bar, and scroll bar
(tool-bar-mode -1)
(scroll-bar-mode -1)
(menu-bar-mode 1)
(add-hook 'after-init-hook (lambda () (window-divider-mode -1)))

Beacon-mode

Show a trail flash of lights whenever my cursor jumps over a certain distance. Great to keep tracks of the cursor position.

(use-package beacon
  :config
  (setq beacon-push-mark 35)
  (setq beacon-color "#d65d0e")
  (beacon-mode t)
  )

Smooth-scrolling

Better scrolling in Emacs.

;; Smooth scrolling
(use-package smooth-scrolling :config (smooth-scrolling-mode t))

Visual fill colunmn

visual-fill-column-mode complements the built in visual-fill-mode by allowing lines to wraps at a certain column.

(use-package visual-fill-column
  :init
  (dolist (hook '(visual-line-mode-hook
                  ;; prog-mode-hook
                  text-mode-hook))
    (add-hook hook #'visual-fill-column-mode))
  (setq visual-fill-column-width 80)
  :hook ((visual-fill-column-mode-hook . visual-line-mode))
  :config
  (setq
   ;; visual-fill-column-center-text nil
   ;; visual-fill-column-fringes-outside-margins nil
   split-window-preferred-function 'visual-fill-column-split-window-sensibly)
  (defun toggle-frame-fullscreen-and-visual-fill-adjust ()
    (interactive)
    (toggle-frame-maximized)
    (run-with-timer 0.1 nil 'visual-fill-column--adjust-window))
  :bind
  ("<f11>" . toggle-frame-fullscreen-and-visual-fill-adjust))

Aesthetics

Faces

My favorites font:

;; Default font
(when (member "Iosevka" (font-family-list))
  (set-frame-font "Iosevka 11" nil t))
(when (member "Source Han Sans" (font-family-list))
  (set-fontset-font t 'han (font-spec :name "Source Han Sans")))

(set-face-attribute 'variable-pitch nil
                    :font "Iosevka Aile")
(set-face-attribute 'fixed-pitch nil
                    :font "Iosevka")

(use-package gruvbox-theme
  :config
  (load-theme 'gruvbox-dark-medium t)
  (set-face-attribute 'secondary-selection nil
                      :weight 'bold :background "#1d2021"))

(use-package rainbow-delimiters
  :config
  (add-hook 'prog-mode-hook 'rainbow-delimiters-mode))

Mode-line

The mode-line is the ultimate pimp for Emacs. It makes Emacs look a lot more modern.

(use-package spaceline-config
  :straight (spaceline :host github :repo "TheBB/spaceline" :branch "master")
  :config
  (setq spaceline-workspace-numbers-unicode t)
  (spaceline-toggle-minor-modes-off)
  (spaceline-toggle-column-on)
  (spaceline-emacs-theme)
  (spaceline-helm-mode 1))

Hydra and self-defined commands

My commands

Self-defined handy commands that I used frequently.

;; Rename file and buffer
;; source: https://steve.yegge.googlepages.com/my-dot-emacs-file
(defun hieu/rename-file-and-buffer (new-name)
  "Renames both current buffer and file it's visiting to NEW-NAME."
  (interactive "sNew name: ")
  (let ((name (buffer-name))
        (filename (buffer-file-name)))
    (if (not filename)
        (message "Buffer '%s' is not visiting a file!" name)
      (if (get-buffer new-name)
          (message "A buffer named '%s' already exists!" new-name)
        (progn
          (rename-file filename new-name 1)
          (rename-buffer new-name)
          (set-visited-file-name new-name)
          (set-buffer-modified-p nil))))))

;; Eval and replace lisp expression
(defun hieu/fc-eval-and-replace ()
  "Replace the preceding sexp with its value."
  (interactive)
  (backward-kill-sexp)
  (prin1 (eval (read (current-kill 0)))
         (current-buffer)))

(bind-key "C-c e" 'hieu/fc-eval-and-replace)

;; Move line/region up/down
(defun hieu/move-text-internal (arg)
  (cond
   ((and mark-active transient-mark-mode)
    (if (> (point) (mark))
        (exchange-point-and-mark))
    (let ((column (current-column))
          (text (delete-and-extract-region (point) (mark))))
      (forward-line arg)
      (move-to-column column t)
      (set-mark (point))
      (insert text)
      (exchange-point-and-mark)
      (setq deactivate-mark nil)))
   (t
    (beginning-of-line)
    (when (or (> arg 0) (not (bobp)))
      (forward-line)
      (when (or (< arg 0) (not (eobp)))
        (transpose-lines arg))
      (forward-line -1)))))

(defun hieu/move-text-down (arg)
  "Move region (transient-mark-mode active) or current line
  arg lines down."
  (interactive "*p")
  (hieu/move-text-internal arg))

(defun hieu/move-text-up (arg)
  "Move region (transient-mark-mode active) or current line
  arg lines up."
  (interactive "*p")
  (hieu/move-text-internal (- arg)))

(bind-key "M-<up>" 'hieu/move-text-up)
(bind-key "M-<down>" 'hieu/move-text-down)

;; Open the gnome terminal
(defun hieu/open-gnome-terminal ()
  "Open an instance of gnome-terminal on Ubuntu machine"
  (interactive)
  (shell-command "gnome-terminal"))

(bind-key "<f10>" 'hieu/open-gnome-terminal)

;; Insert current date (and time)
(defun hieu/insert-current-date () (interactive)
       (insert (shell-command-to-string "bash -c 'echo -n $(date +%Y-%m-%d)'")))

(defun hieu/insert-current-time () (interactive)
       (insert (shell-command-to-string "bash -c 'echo -n $(date +%H:%M)'")))

(bind-key "C-x M-d" 'hieu/insert-current-date)
(bind-key "C-x M-t" 'hieu/insert-current-time)


;; Replace Org keywords to lowercase, in consistent with Org-mode 9.2
;; https://scripter.co/org-keywords-lower-case/
(defun hieu/lower-case-org-keywords ()
  "Lower case Org keywords and block identifiers.

  Example: \"#+TITLE\" -> \"#+title\"
           \"#+BEGIN_EXAMPLE\" -> \"#+begin_example\"

  Inspiration:
  https://code.orgmode.org/bzg/org-mode/commit/13424336a6f30c50952d291e7a82906c1210daf0."
  (interactive)
  (save-excursion
    (goto-char (point-min))
    (let ((case-fold-search nil)
          (count 0))
      ;; Match examples: "#+FOO bar", "#+FOO:", "=#+FOO=", "~#+FOO~",
      ;;                 "‘#+FOO’", "“#+FOO”", ",#+FOO bar",
      ;;                 "#+FOO_bar<eol>", "#+FOO<eol>".
      (while (re-search-forward "\\(?1:#\\+[A-Z_]+\\(?:_[[:alpha:]]+\\)*\\)\\(?:[ :=~’”]\\|$\\)" nil :noerror)
        (setq count (1+ count))
        (replace-match (downcase (match-string-no-properties 1)) :fixedcase nil nil 1))
      (message "Lower-cased %d matches" count))))

My hydras

The hydra package create a reasonable UI for complicated keybinds.

(use-package hydra)

(defhydra hydra-straight-helper (:hint nil)
  "
    _c_heck all       |_f_etch all     |_m_erge all      |_n_ormalize all   |p_u_sh all
    _C_heck package   |_F_etch package |_M_erge package  |_N_ormlize package|p_U_sh package
    ----------------^^+--------------^^+---------------^^+----------------^^+------------||_q_uit||
    _r_ebuild all     |_p_ull all      |_v_ersions freeze|_w_atcher start   |_g_et recipe
    _R_ebuild package |_P_ull package  |_V_ersions thaw  |_W_atcher quit    |prun_e_ build"
  ("c" straight-check-all)
  ("C" straight-check-package)
  ("r" straight-rebuild-all)
  ("R" straight-rebuild-package)
  ("f" straight-fetch-all)
  ("F" straight-fetch-package)
  ("p" straight-pull-all)
  ("P" straight-pull-package)
  ("m" straight-merge-all)
  ("M" straight-merge-package)
  ("n" straight-normalize-all)
  ("N" straight-normalize-package)
  ("u" straight-push-all)
  ("U" straight-push-package)
  ("v" straight-freeze-versions)
  ("V" straight-thaw-versions)
  ("w" straight-watcher-start)
  ("W" straight-watcher-quit)
  ("g" straight-get-recipe)
  ("e" straight-prune-build)
  ("q" nil))

Editing

Various packages that helps with editing

Incremental completion with helm

Helm can be opened in a separate frame. In Gnome, I can press S-<direction> to move this window around.

(use-package helm-config
  :straight helm
  :init
  (helm-mode 1)
  :config
  (setq
   ;; Open helm in a seperate frame
   helm-display-function                 'helm-display-buffer-in-own-frame
   helm-display-buffer-reuse-frame       t
   helm-use-undecorated-frame-option     t
   helm-display-buffer-width             80     ;; move to end or beginning of source when reaching top or bottom of source	.
   helm-move-to-line-cycle-in-source     t
   ;; Inherit input method
   helm-inherit-input-method             nil
   ;; Others
   helm-M-x-fuzzy-match                  t
   helm-ff-skip-boring-files             t
   helm-ff-file-name-history-use-recentf t)

  ;; The default "C-x c" is quite close to "C-x C-c", which quits Emacs.
  ;; Changed to "C-c h". Note: We must set "C-c h" globally, because we
  ;; cannot change `helm-command-prefix-key' once `helm-config' is loaded.
  (global-unset-key (kbd "C-x c"))

  :bind (("C-c h" . helm-command-prefix)
         ("C-x b" . helm-mini)
         ("M-x" . helm-M-x)
         ("C-x C-f" . helm-find-files)
         ("M-y" . helm-show-kill-ring)
         :map helm-map
         ("<tab>" . helm-execute-persistent-action) ; rebind tab to run persistent action
         ("C-i" . helm-execute-persistent-action)   ; make TAB work in terminal
         ("M-x" . helm-select-action)              ; list actions using C-z
         :map helm-command-map
         ("o" . helm-occur)))

Helm-company

;; Use "C-:" to switch to Helm interface during company-ing
(use-package helm-company
  :after company
  :bind (:map company-mode-map
         (("C-:" . helm-company))
         :map company-active-map
         (("C-:" . helm-company))))

Swiper-helm

(use-package swiper-helm
  :init
  (setq swiper-helm-display-function 'helm-display-buffer-in-own-frame)
  :bind ("C-s" . swiper-helm))

Company

Company is THE completion mechanism for Emacs

(use-package company
  :init
  ;; Activate globally
  (add-hook 'after-init-hook 'global-company-mode)
  ;; Press <F1> to show the documentation buffer and press C-<F1> to jump to it
  (defun my/company-show-doc-buffer ()
    "Temporarily show the documentation buffer for the selection."
    (interactive)
    (let* ((selected (nth company-selection company-candidates))
           (doc-buffer (or (company-call-backend 'doc-buffer selected)
                           (error "No documentation available"))))
      (with-current-buffer doc-buffer
        (goto-char (point-min)))
      (display-buffer doc-buffer t)))

  :config
  ;; Some useful configs
  (setq company-selection-wrap-around t
        company-tooltip-align-annotations t
        company-tooltip-limit 10
        company-idle-delay 0.5)
  ;; Add yasnippet support for all company backends
  ;; https://github.com/syl20bnr/spacemacs/pull/179
  (defvar company-mode/enable-yas t "Enable yasnippet for all backends.")
  (defun company-mode/backend-with-yas (backend)
    (if (or (not company-mode/enable-yas) (and (listp backend)    (member 'company-yasnippet backend)))
        backend
      (append (if (consp backend) backend (list backend))
              '(:with company-yasnippet))))
  (setq company-backends (mapcar #'company-mode/backend-with-yas company-backends))
  :bind
  (:map company-active-map
   ("C-<f1>" . my/company-show-doc-buffer)
   ("C-n" . company-select-next)
   ("C-p" . company-select-previous)
   ))

Yasnippets

Yasnippet is THE templating system for Emacs. My custom snippets are stored in the same repo.

;; Enable Yasnippets
(use-package yasnippet
  :init
  ;; It will test whether it can expand, if yes, change cursor color}
  (defun yasnippet-can-fire-p (&optional field)
    (interactive)
    (setq yas--condition-cache-timestamp (current-time))
    (let (templates-and-pos)
      (unless (and yas-expand-only-for-last-commands
                   (not (member last-command yas-expand-only-for-last-commands)))
        (setq templates-and-pos (if field
                                    (save-restriction
                                      (narrow-to-region (yas--field-start field)
                                                        (yas--field-end field))
                                      (yas--templates-for-key-at-point))
                                  (yas--templates-for-key-at-point))))

      (set-cursor-color (if (and templates-and-pos (first templates-and-pos))
                            "#d65d0e" (face-attribute 'default :foreground)))))
  (add-hook 'post-command-hook 'yasnippet-can-fire-p)
  (yas-global-mode 1)
  :config
  (setq yas-fallback-behavior 'call-other-command)

  (setq yas-snippet-dirs-custom (format "%s/%s" config-directory "Snippets/"))
  (add-to-list' yas-snippet-dirs 'yas-snippet-dirs-custom)
  (yas-reload-all)

  :bind*  (("<C-tab>" . yas-insert-snippet)
           :map yas-minor-mode-map
           ("`" . yas-expand-from-trigger-key)))

Smartparens

Working with, not just parenthesis and braces but, every abstract pairs of text objects.

(use-package smartparens-config
  :straight smartparens
  :hook (((text-mode prog-mode comint-mode) . smartparens-mode)
	       (smartparens-mode . show-smartparens-mode))
  :config
  ;; Define a hydra
  (defhydra hydra-smartparens (:idle 1 :hint nil)
    "
  Sexps (quit with _q_)

  ^Nav^            ^Barf/Slurp^          ^Depth^
  ^---^------------^----------^----------^-----^-----------------------
  _f_: forward     _s_:  slurp forward   _R_:      splice
  _b_: backward    _S_:  barf forward    _r_:      raise
  _a_: begin       _d_:  slurp backward  _<up>_:   raise backward
  _e_: end         _D_:  barf backward   _<down>_: raise forward
  _m_: mark

  ^Kill^           ^Misc^                       ^Wrap^
  ^----^-----------^----^-----------------------^----^------------------
  _w_: copy        _j_: join                    _(_: wrap with ( )
  _k_: kill        _s_: split                   _{_: wrap with { }
  ^^               _t_: transpose               _'_: wrap with ' '
  ^^               _c_: convolute               _\"_: wrap with \" \"
  ^^               _i_: indent defun"
    ("q" nil)
    ;; Wrapping
    ("(" (lambda (a) (interactive "P") (sp-wrap-with-pair "(")))
    ("{" (lambda (a) (interactive "P") (sp-wrap-with-pair "{")))
    ("'" (lambda (a) (interactive "P") (sp-wrap-with-pair "'")))
    ("\"" (lambda (a) (interactive "P") (sp-wrap-with-pair "\"")))
    ;; Navigation
    ("f" sp-beginning-of-next-sexp)
    ("b" sp-beginning-of-previous-sexp)
    ("a" sp-beginning-of-sexp)
    ("e" sp-end-of-sexp)
    ("m" sp-mark-sexp)
    ;; Kill/copy
    ("w" sp-copy-sexp :exit t)
    ("k" sp-kill-sexp :exit t)
    ;; Misc
    ("t" sp-transpose-sexp)
    ("j" sp-join-sexp)
    ("c" sp-convolute-sexp)
    ("i" sp-indent-defun)
    ;; Depth changing
    ("R" sp-splice-sexp)
    ("r" sp-splice-sexp-killing-around)
    ("<up>" sp-splice-sexp-killing-backward)
    ("<down>" sp-splice-sexp-killing-forward)
    ;; Barfing/slurping
    ("s" sp-forward-slurp-sexp)
    ("S" sp-forward-barf-sexp)
    ("D" sp-backward-barf-sexp)
    ("d" sp-backward-slurp-sexp))
  :bind (("M-<backspace>" . sp-unwrap-sexp)
	       ("C-c s" . hydra-smartparens/body)))

(use-package smartparens-org
  :straight smartparens
  :after org)

Multiple-cursor

Operate from multiple text position at once.

;; Multi-cursor
(use-package multiple-cursors
  :init
  ;; In case commands behavior is messy with multiple-cursors,
  ;; check your ~/.emacs.d/.mc-lists.el
  (defun mc/check-command-behavior ()
    "Open ~/.emacs.d/.mc-lists.el.
  So you can fix the list for run-once and run-for-all multiple-cursors commands."
    (interactive)
    (find-file "~/.emacs.d/.mc-lists.el"))
  :config
  (defhydra hydra-multiple-cursors (:columns 3 :idle 1.0)
    "Multiple cursors"
    ("l" mc/edit-lines "Edit lines in region" :exit t)
    ("b" mc/edit-beginnings-of-lines "Edit beginnings of lines in region" :exit t)
    ("e" mc/edit-ends-of-lines "Edit ends of lines in region" :exit t)
    ("a" mc/mark-all-like-this "Mark all like this" :exit t)
    ("S" mc/mark-all-symbols-like-this "Mark all symbols likes this" :exit t)
    ("w" mc/mark-all-words-like-this "Mark all words like this" :exit t)
    ("r" mc/mark-all-in-region "Mark all in region" :exit t)
    ("R" mc/mark-all-in-region-regexp "Mark all in region (regexp)" :exit t)
    ("i" (lambda (n)
           (interactive "nInsert initial number: ")
           (mc/insert-numbers n))
     "Insert numbers")
    ("s" mc/sort-regions "Sort regions")
    ("v" mc/reverse-regions "Reverse order")
    ("d" mc/mark-all-dwim "Mark all dwim")
    ("n" mc/mark-next-like-this "Mark next like this")
    ("N" mc/skip-to-next-like-this "Skip to next like this")
    ("M-n" mc/unmark-next-like-this "Unmark next like this")
    ("p" mc/mark-previous-like-this "Mark previous like this")
    ("P" mc/skip-to-previous-like-this "Skip to previous like this")
    ("M-p" mc/unmark-previous-like-this "Unmark previous like this")
    ("q" nil "Quit" :exit t))
  :bind
  ("C-c m" . hydra-multiple-cursors/body))

Electric-operator

Electric Operator is an emacs minor-mode to automatically add spacing around operators. It’s handy to keep code clean without much effort.

(use-package electric-operator
  :config
  (setq electric-operator-R-named-argument-style 'spaced)
  (add-hook 'ess-mode-hook #'electric-operator-mode)
  (add-hook 'python-mode-hook #'electric-operator-mode)

  (electric-operator-add-rules-for-mode 'ess-r-mode
                                        (cons ":=" " := ")))

Auto-highlight-symbol

Auto highlight the same symbol in any (complex) script.

(use-package auto-highlight-symbol
  :init (add-hook 'prog-mode-hook 'auto-highlight-symbol-mode)
  :config
  (setq ahs-idle-interval 1.0
        ahs-default-range 'ahs-range-whole-buffer
        ahs-inhibit-face-list '(font-lock-comment-delimiter-face
                                font-lock-comment-face
                                font-lock-doc-face))
  (unbind-key "M--" auto-highlight-symbol-mode-map))

Expand-region

Gradually expanding the selected region.

(use-package expand-region :bind ("M-." . er/expand-region))

Eyebrowse

Eyebrowse falicitates workspaces in Emacs.

(use-package eyebrowse
  :config
  (setq eyebrowse-new-workspace t)
  (eyebrowse-mode 1)
  ;; define hydra
  (defhydra hydra-eyebrowse (:hint nil :color red)
    "
  Window Manager
  _0_ to _9_, _s_: Switch     _<left>_: Previous      _<right>_: Next
  _c_: Create             _C_: Close              _r_: Rename"
    ("q" nil :color blue)
    ("0" eyebrowse-switch-to-window-config-0)
    ("1" eyebrowse-switch-to-window-config-1)
    ("2" eyebrowse-switch-to-window-config-2)
    ("3" eyebrowse-switch-to-window-config-3)
    ("4" eyebrowse-switch-to-window-config-4)
    ("5" eyebrowse-switch-to-window-config-5)
    ("6" eyebrowse-switch-to-window-config-6)
    ("7" eyebrowse-switch-to-window-config-7)
    ("8" eyebrowse-switch-to-window-config-8)
    ("9" eyebrowse-switch-to-window-config-9)
    ("r" eyebrowse-rename-window-config :exit t)
    ("c" eyebrowse-create-window-config :exit t)
    ("s" eyebrowse-switch-to-window-config :exit t)
    ("C" eyebrowse-close-window-config :exit t)
    ("<left>" eyebrowse-prev-window-config)
    ("<right>" eyebrowse-next-window-config)
    )
  :bind* ("C-c C-w" . hydra-eyebrowse/body))

Notetaking with org-mode

Org mode is for keeping notes, maintaining TODO lists, planning projects, and authoring documents with a fast and effective plain-text system. See here.

Setting up

;; org has quite some spurious commands
(unbind-key "C-c C-z" org-mode-map)	;org-add-note

;; org-indent-mode looks better
(add-hook 'org-mode-hook 'org-indent-mode)

;; Enable shift selection
(setq org-support-shift-select t)

;; Fontification
(set-face-attribute 'org-document-title nil :height 150)
(set-face-attribute 'org-level-1 nil :weight 'bold)
(set-face-attribute 'org-level-2 nil :weight 'bold)
(set-face-attribute 'org-block nil :background
                    (color-lighten-name
                     (face-attribute 'default :background) 2))
;; Highlight temporal notes in texts with ~...~
(add-to-list 'org-emphasis-alist
             '("~" (:foreground "#d65d0e" :background "#1d2021")
               ))

;; Highlight latex stuffs
(setq org-highlight-latex-and-related '(latex entities))

;; Variable pitch
(add-hook 'org-mode-hook
          '(lambda ()
             (variable-pitch-mode 1)))

(mapc (lambda (face)
        (set-face-attribute face nil :inherit 'fixed-pitch))
      (list 'org-code
            'org-link
            'org-block
            'org-table
            'org-block-begin-line
            'org-block-end-line
            'org-meta-line
            'org-document-info-keyword
            'org-latex-and-related))


;; ORG LATEX PREVIEW
(setq org-startup-with-latex-preview t
      ;; Make latex preview with "C-c C-x C-l" slightly bigger
      org-format-latex-options
      (plist-put org-format-latex-options :scale 1.8)
      ;; Cache the preview images elsewhere
      org-preview-latex-image-directory "~/.cache/ltximg/")

;; Auto expand preview latex images when cursor is on it
(use-package org-fragtog
  :config (add-hook 'org-mode-hook 'org-fragtog-mode))

;; org-open-file use Evince if possible
(add-to-list 'org-file-apps '("\\.pdf\\'" . "evince %s"))

org-tempo: quickly insert templates with <trigger TAB. It used to be defaults befor Org 9.2

(use-package org-tempo :straight org)

Org-exports

From org-mode to every other filetypes.

(use-package ox-latex
  :straight org
  :config
  ;; Highlight code blocks in org-latex-export-to-pdf
  ;; Minted options can be found in:
  ;; https://mirror.kku.ac.th/CTAN/macros/latex/contrib/minted/minted.pdf
  (setq org-latex-listings 'minted
        org-latex-packages-alist '(("" "minted"))
        org-latex-minted-options '(
                                   ;; ("breaklines" "true")
                                   ;; ("breakanywhere" "true")
                                   ;; ("mathescape")
                                   ;; ("linenos" "true")
                                   ;; ("firstnumber" "last")
                                   ;; ("frame" "lines")
                                   ("fontsize" "\\footnotesize")
                                   ("bgcolor" "yellow!5")
                                   ;; ("framesep" "2mm")
                                   )
        org-latex-pdf-process
        '("latexmk -pdflatex='%latex -shell-escape -bibtex -interaction=nonstopmode' -pdf -output-directory=%o -f %f")
        )

  ;; Default packages
  (setq org-latex-default-packages-alist
        '(("AUTO" "inputenc" t
           ("pdflatex"))
          ("T1" "fontenc" t
           ("pdflatex"))
          ("" "fontspec" t
           ("xelatex"))
          ("" "graphicx" t)
          ("" "grffile" t)
          ;; Array, tabularx, booktabs are for tables
          ("" "array" nil)
          ("" "tabularx" nil)
          ("" "booktabs" nil)
          ("" "multirow" nil)
          ("" "siunitx" nil)
          ("" "wrapfig" nil)
          ("" "rotating" nil)
          ("normalem" "ulem" t)
          ("" "amsmath" t)
          ("" "textcomp" t)
          ("" "amssymb" t)
          ("" "capt-of" nil)
          ("dvipsnames" "xcolor" nil)
          ("colorlinks=true, linkcolor=Blue, citecolor=BrickRed, urlcolor=PineGreen" "hyperref" nil)
          ("" "indentfirst" nil))
        )

  ;; Writing latex in org-mode
  (add-hook 'org-mode-hook 'org-cdlatex-mode)

  ;; Add KOMA-scripts classes to org export
  (add-to-list 'org-latex-classes
               '("koma-article" "\\documentclass{scrartcl}"
                 ("\\section{%s}" . "\\section*{%s}")
                 ("\\subsection{%s}" . "\\subsection*{%s}")
                 ("\\subsubsection{%s}" . "\\subsubsection*{%s}")
                 ("\\paragraph{%s}" . "\\paragraph*{%s}")
                 ("\\subparagraph{%s}" . "\\subparagraph*{%s}")))

  (add-to-list 'org-latex-classes
               '("koma-report" "\\documentclass{scrreprt}"
                 ("\\part{%s}" . "\\part*{%s}")
                 ("\\chapter{%s}" . "\\chapter*{%s}")
                 ("\\section{%s}" . "\\section*{%s}")
                 ("\\subsection{%s}" . "\\subsection*{%s}")
                 ("\\subsubsection{%s}" . "\\subsubsection*{%s}")))

  (add-to-list 'org-latex-classes
               '("koma-book" "\\documentclass[11pt]{scrbook}"
                 ("\\part{%s}" . "\\part*{%s}")
                 ("\\chapter{%s}" . "\\chapter*{%s}")
                 ("\\section{%s}" . "\\section*{%s}")
                 ("\\subsection{%s}" . "\\subsection*{%s}")
                 ("\\subsubsection{%s}" . "\\subsubsection*{%s}")))
  )

I am also implementing ox-hugo

(use-package ox-hugo
  :after ox)

;; This may breaks things
(use-package org-ref-ox-hugo
  :ensure org-ref
  :straight (:host github :repo "jethrokuan/org-ref-ox-hugo"))

(add-to-list 'org-ref-formatted-citation-formats
             '("md"
               ("article" . "${author} (${year}), *${title}*, ${journal}, *${volume}(${number})*, ${pages}. ${doi}")
               ("inproceedings" . "${author} (${year}), *${title}*, In ${editor}, ${booktitle} (pp. ${pages}). ${address}: ${publisher}.")
               ("book" . "${author} (${year}), *${title}*, ${address}: ${publisher}.")
               ("phdthesis" . "${author} (${year}), *${title}* (Doctoral dissertation). ${school}, ${address}.")
               ("inbook" . "${author} (${year}), *${title}*, In ${editor} (Eds.), ${booktitle} (pp. ${pages}). ${address}: ${publisher}.")
               ("incollection" . "${author} (${year}), *${title}*, In ${editor} (Eds.), ${booktitle} (pp. ${pages}). ${address}: ${publisher}.")
               ("proceedings" . "${editor} (Eds.), _${booktitle}_ (${year}). ${address}: ${publisher}.")
               ("unpublished" . "${author} (${year}), *${title}*. Unpublished manuscript.")
               ("misc" . "${author} (${year}). *${title}*. Retrieved from [${howpublished}](${howpublished}). ${note}.")
               (nil . "${author} (${year}), *${title}*.")))

Org-agenda

GET.THINGS.DONE.

(use-package org-agenda
  :straight org
  :config
  (setq org-agenda-files '("~/Dropbox/Notes/Agenda")
        org-default-notes-file "~/Dropbox/Notes/Agenda/inbox.org"
        org-columns-default-format-for-agenda
        "%60ITEM(Task) %10Effort(Estimate){:} %CLOCKSUM")
  :init
  (defun hieu/open-agenda()
    (interactive)
    (org-agenda nil "n")
    (delete-other-windows))
  :bind ("<f1>" . org-agenda)
  ;; :hook (after-init . hieu/open-agenda)
  )

(use-package org-super-agenda
  :after org-agenda
  :config
  (setq org-super-agenda-groups
        '((:auto-outline-path t :time-grid t)))
  (org-super-agenda-mode))

(use-package org-capture
  :straight org
  :bind
  ("C-c c" . org-capture))

Org-ref

Org-refs make citations and managing bibliographies much easier.

(use-package org-ref
  :config
  (setq
   org-ref-default-bibliography	     '("~/Dropbox/Notes/Research/papers.bib")
   org-ref-pdf-directory             "~/Dropbox/Notes/Papers/"
   bibtex-dialect                    'biblatex
   bibtex-completion-notes-extension "_notes.org"
   bibtex-completion-notes-path      "~/Dropbox/Notes/Roam/"
   bibtex-completion-bibliography    "~/Dropbox/Notes/Research/papers.bib"
   bibtex-completion-library-path    "~/Dropbox/Notes/Papers/"
   ;; Optimize for 80 character frame display
   bibtex-completion-display-formats
   '((t . "${title:46} ${author:20} ${year:4} ${=type=:3}${=has-pdf=:1}${=has-note=:1}"))
   bibtex-completion-notes-template-multiple-files
   "#+title: ${author-or-editor} (${year}): ${title}
  #+roam_key: cite:${=key=}
  #+roam_tags: bibliography"
   bibtex-completion-pdf-symbol ""
   bibtex-completion-notes-symbol ""
   ;; Open pdf in external tool instead of in Emacs
   bibtex-completion-pdf-open-function
   (lambda (fpath)
     (call-process "evince" nil 0 nil fpath)))
  :bind ("C-c ]" . helm-bibtex))

Org-journal

A journal inside Emacs.

(use-package org-journal
  :bind
  ("C-c n j" . org-journal-new-entry)
  :init
  (setq org-journal-date-format "%A, %Y-%m-%d"
        org-journal-date-prefix "* Daily Journal "
        org-journal-file-format "journal_%Y-%m-%d.org"
        org-journal-dir "~/Dropbox/Notes/Roam/"
        org-journal-file-header "#+title: %Y-%m-%d %a\n#+roam_tags: journal\n"
        org-journal-enable-agenda-integration t))

Org-roam

Org-roam falicitate a system of networked notetaking that I am experiments with.

(use-package org-roam
  :after org
  :straight (:host github :repo "jethrokuan/org-roam" :branch "master")
  :hook
  (after-init . org-roam-mode)
  :config
  (setq org-roam-directory "~/Dropbox/Notes/Roam/"
        org-roam-db-location "~/.emacs.d/org-roam.db")
  ;; Exclude roam files from helm
  (add-to-list 'helm-boring-buffer-regexp-list "^[0-9]\\{14\\}.+\\.org$")
  :bind (:map org-roam-mode-map
         (("C-c n l" . org-roam)
          ("C-c n f" . org-roam-find-file)
          ("C-c n g" . org-roam-graph)
          ("C-c n b" . org-roam-switch-to-buffer)
          ("C-c n r" . org-roam-find-ref)
          ("C-c n d" . org-roam-find-directory))
         :map org-mode-map
         (("C-c n i" . org-roam-insert))))

(use-package org-roam-protocol :straight org-roam)

(use-package org-roam-graph
  :straight org-roam
  :init
  (setq org-roam-graph-executable	    (executable-find "dot")
        org-roam-graph-extra-config        '(("overlap" . "false")
                                             ("concentrate" . "true")
                                             ("bgcolor" . "lightblue"))
        org-roam-graph-edge-cites-extra-config
        '(("color" . "gray")
          ("style" . "dashed")
          ("sep" . "20"))
        org-roam-graph-shorten-titles      'wrap
        org-roam-graph-max-title-length    50
        org-roam-graph-exclude-matcher     '("journal")))

(use-package org-roam-capture
  :straight org-roam
  :config
  (setq org-roam-capture-templates
        '(("d" "default" plain (function org-roam-capture--get-point)
           "%?"
           :file-name "%<%Y%m%d%H%M%S>-${slug}"
           :head "#+title: ${title}\n#+roam_alias:\n#+roam_tags:\n"
           :unnarrowed t))
        org-roam-capture-ref-templates
        '(("r" "ref" plain (function org-roam-capture--get-point)
           "#+roam_key: ${ref}\n%?"
           :file-name "%<%Y%m%d%H%M%S>_web_${slug}"
           :head "#+title: ${title}]\n#+roam_tags: website\n"
           :unnarrowed t))
        org-roam-dailies-capture-templates
        '(("d" "daily" plain (function org-roam-capture--get-point)
           ""
           :immediate-finish t
           :file-name "journal_%<%Y-%m-%d>"
           :head "#+title: %<%Y-%m-%d %a>\n#+roam_tags: journal\n"))
        ))

(use-package company-org-roam
  :straight (:host github :repo "jethrokuan/company-org-roam")
  :config
  (push 'company-org-roam company-backends))

Utilities

Projectile

Working with multiple projects.

(use-package projectile
  :init
  (setq projectile-keymap-prefix (kbd "C-c C-p"))
  :config
  (projectile-mode)
  (setq projectile-completion-system 'helm)
  (setq projectile-mode-line '(:eval (format " 𝐏[%s]" (projectile-project-name)))))

;; Helm-projectile
(use-package helm-projectile
  :config
  (helm-projectile-on))

Magit

Magit is an interface to the version control system Git, implemented as an Emacs package. Magit aspires to be a complete Git porcelain. See here

(use-package magit
  :bind
  ;; Set magit-status to F9
  ("<f9>" . magit-status)
  :config
  ;; Currently magit cause some error when auto revert mode is on
  (setq magit-auto-revert-mode nil))

(use-package git-gutter
  :init
  (global-git-gutter-mode))

(use-package git-gutter-fringe
  :after git-gutter)

Search

Ripgrep is the best search tool out there.

(use-package rg :config (rg-enable-default-bindings))

Which-key

which-key is a minor mode for Emacs that displays the key bindings following your currently entered incomplete command (a prefix) in a popup.

(use-package which-key
  :diminish which-key-mode
  :config
  (which-key-mode 1))

Restart-emacs

Make restarting easier, handy whenever I’m grokking Emacs.

(use-package restart-emacs)

Languages Modes

R: ess-mode

(use-package ess-site
  :straight ess
  :config
  ;; Execute screen options after initialize process
  (add-hook 'ess-post-run-hook 'ess-execute-screen-options)

  ;; Disable IDO so helm is used instead
  (setq ess-use-ido nil)

  ;; We don’t want R evaluation to hang the editor, hence
  (setq ess-eval-visibly 'nowait)

  ;; Unbind ess-insert-assign (defaut value is "_")
  (setq ess-smart-S-assign-key nil))


(use-package ess-r-mode
  :straight ess
  :config
  ;; Hot key C-S-m for pipe operator in ESS
  (defun pipe_R_operator ()
    "R - %>% operator or 'then' pipe operator"
    (interactive)
    (just-one-space 1)
    (insert "%>%")
    (just-one-space 1))

  ;; ESS syntax highlight
  (setq ess-R-font-lock-keywords
        '((ess-R-fl-keyword:keywords . t)
          (ess-R-fl-keyword:constants . t)
          (ess-R-fl-keyword:modifiers . t)
          (ess-R-fl-keyword:fun-defs . t)
          (ess-R-fl-keyword:assign-ops . t)
          (ess-fl-keyword:fun-calls . t)
          (ess-fl-keyword:numbers . t)
          (ess-fl-keyword:operators . t)
          (ess-fl-keyword:delimiters . t)
          (ess-fl-keyword:= . t)
          (ess-R-fl-keyword:F&T . t)
          (ess-R-fl-keyword:%op% . t)))

  (setq inferior-ess-r-font-lock-keywords
        '((ess-S-fl-keyword:prompt . t)
          (ess-R-fl-keyword:messages . t)
          (ess-R-fl-keyword:modifiers . nil)
          (ess-R-fl-keyword:fun-defs . t)
          (ess-R-fl-keyword:keywords . nil)
          (ess-R-fl-keyword:assign-ops . t)
          (ess-R-fl-keyword:constants . t)
          (ess-fl-keyword:matrix-labels . t)
          (ess-fl-keyword:fun-calls . nil)
          (ess-fl-keyword:numbers . nil)
          (ess-fl-keyword:operators . nil)
          (ess-fl-keyword:delimiters . nil)
          (ess-fl-keyword:= . t)
          (ess-R-fl-keyword:F&T . nil)))

  :bind
  (:map ess-r-mode-map
   ("M--" . ess-insert-assign)
   ("C-S-m" . pipe_R_operator)
   :map
   inferior-ess-r-mode-map
   ("M--" . ess-insert-assign)
   ("C-S-m" . pipe_R_operator))
  )

Python: elpy

(use-package python
  :mode ("\\.py\\'" . python-mode)
  :config
  (setq python-shell-interpreter "python3"))

(use-package elpy
  :after python
  :init
  ;; Truncate long line in inferior mode
  (add-hook 'inferior-python-mode-hook (lambda () (setq truncate-lines t)))
  ;; Enable company
  (add-hook 'python-mode-hook 'company-mode)
  (add-hook 'inferior-python-mode-hook 'company-mode)
  ;; Enable highlight indentation
  (add-hook 'highlight-indentation-mode-hook
            'highlight-indentation-current-column-mode)
  ;; Enable elpy
  (elpy-enable)
  :config
  ;; Do not enable elpy flymake for now
  (remove-hook 'elpy-modules 'elpy-module-flymake)
  (remove-hook 'elpy-modules 'elpy-module-highlight-indentation)

  ;; The old `elpy-use-ipython' is obseleted, see:
  ;; https://elpy.readthedocs.io/en/latest/ide.html#interpreter-setup
  ;; (setq python-shell-interpreter "ipython3"
  ;; python-shell-interpreter-args "-i --simple-prompt")

  (setq elpy-rpc-python-command "python3")

  ;; Completion backend
  (setq elpy-rpc-backend "rope")

  ;; Function: send block to elpy: bound to C-c C-c
  (defun forward-block (&optional n)
    (interactive "p")
    (let ((n (if (null n) 1 n)))
      (search-forward-regexp "\n[\t\n ]*\n+" nil "NOERROR" n)))

  (defun elpy-shell-send-current-block ()
    (interactive)
    (beginning-of-line)
    "Send current block to Python shell."
    (push-mark)
    (forward-block)
    (elpy-shell-send-region-or-buffer)
    (display-buffer (process-buffer (elpy-shell-get-or-create-process))
                    nil
                    'visible))

  ;; Font-lock
  (add-hook 'python-mode-hook
            '(lambda()
               (font-lock-add-keywords
                nil
                '(("\\<\\([_A-Za-z0-9]*\\)(" 1
                   font-lock-function-name-face) ; highlight function names
                  ))))

  :bind (:map python-mode-map
         ("C-c <RET>" . elpy-shell-send-region-or-buffer)
         ("C-c C-c" . elpy-send-current-block)))

(use-package pipenv
  :hook (python-mode . pipenv-mode))

Julia: julia-mode and julia-snail

(use-package julia-mode
  :magic ("%JL" . julia-mode)
  :init
  (setq inferior-julia-program-name 'julia)
  :config
  (define-key julia-mode-map (kbd "TAB") 'julia-latexsub-or-indent))

(use-package julia-snail
  :after julia
  :ensure vterm
  :hook (julia-mode . julia-snail-mode))

Jupyter Notebook: emacs-jupiter

(use-package jupyter)

(use-package jupyter-org-extensions
  :straight jupyter
  :bind (:map jupyter-org-interaction-mode-map
         ("C-c h" . nil)
         ("C-c j" . jupyter-org-hydra/body)))

(use-package ob-jupyter :straight jupyter)

(setq org-babel-default-header-args:jupyter-julia
      '((:async . "yes")
        (:session . "jl")
        (:kernel . "julia")))

LaTeX: AUCTeX and CDLaTeX

AUCTeX

AUCTeX is THE TeX extension for Emacs. Be careful with configuration because it overrides the built-in tex package.

(use-package auctex
  :mode ("\\.tex\\'" . TeX-latex-mode)
  :config
  ;; General configs
  (setq TeX-master		 nil
        TeX-auto-save		 t
        TeX-parse-self		 t
        TeX-PDF-mode		 t
        TeX-electric-escape	 t)
  ;; Turn on RefTeX in AUCTeX
  (add-hook 'LaTeX-mode-hook 'turn-on-reftex)
  ;; Reftex default bibfile
  (setq reftex-default-bibliography "~/Dropbox/Notes/Research/papers.bib")
  ;; Activate nice interface between RefTeX and AUCTeX
  (setq reftex-plug-into-AUCTeX t)
  )

;; Completion
(use-package company-auctex
  :after tex
  :init
  (company-auctex-init))

CDLaTex

CDLaTeX is a minor mode for Emacs supporting fast insertion of environment templates and math stuff in LaTeX. For more information see here

(use-package cdlatex
  :after (tex)
  :config
  (add-hook 'LaTeX-mode-hook 'turn-on-cdlatex))

Markdown: markdown-mode

(use-package markdown-mode
  :commands (markdown-mode gfm-mode)
  :mode (("README\\.md\\'" . gfm-mode)
         ("\\.md\\'" . markdown-mode)
         ("\\.markdown\\'" . markdown-mode))
  :bind (:map markdown-mode-map
         ("C-c i" . markdown-insert-code-chunk)))

Emacs Lisp

Customisation to emacs-lisp itself, this is mainly syntax highlighting

(use-package highlight-defined
  :config
  (add-hook 'emacs-lisp-mode-hook 'highlight-defined-mode))

(use-package highlight-quoted
  :config
  (add-hook 'emacs-lisp-mode-hook 'highlight-quoted-mode)
  (set-face-attribute 'highlight-quoted-symbol nil
                      :inherit 'font-lock-string-face))

(use-package helpful
  :bind
  (("C-h f" . helpful-callable)
   ("C-h v" . helpful-variable)
   ("C-h k" . helpful-key)))

CSS: css-mode

(use-package css-mode
  :mode (("\\.css?\\'" . css-mode)))

PDF: pdf-tools

(use-package pdf-tools
  :magic ("%PDF". pdf-view-mode)
  :config
  (pdf-tools-install :no-query))

Org-mode Babel

Finally, the Babel functionality of Org-mode makes writings in literate programming style a lot easier. This config itself is powered by org-babel. Since it requires the major-mode for other languages, I put this at the bottom of configuration.

(setq org-confirm-babel-evaluate nil)

(org-babel-do-load-languages
 'org-babel-load-languages
 '((emacs-lisp . t)
   (julia . t)
   (R . t)
   (python . t)
   (jupyter . t)))

About

ARCHIVED: Personal setups for Emacs, (mostly) tailored for data analysis with R/Python and reproducible researchs with Org mode/Markdown

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published