├── emacs └── .doom.d │ ├── README.org │ ├── favicon-pixel.png │ ├── pics │ ├── org-em-dash.png │ ├── org-latex-preview.png │ ├── citar-org-roam-follow.png │ └── org-preview-vs-export.png │ ├── packages.el │ ├── init.el │ ├── themes │ └── doom-everforest-theme.el │ └── config.org ├── .gitmodules ├── .gitignore ├── ulauncher └── .local │ └── share │ └── ulauncher │ └── extensions │ └── docfind │ ├── images │ └── icon.png │ ├── versions.json │ ├── manifest.json │ └── main.py ├── shell ├── .fzf.zsh ├── .fzf.bash ├── .zshrc └── .bashrc ├── README.org ├── scripts └── .local │ └── bin │ └── docfind ├── julia └── .julia │ └── config │ └── startup.jl └── latex └── texmf └── tex └── generic └── hpcheatsheet.cls /emacs/.doom.d/README.org: -------------------------------------------------------------------------------- 1 | config.org -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "shell/.fzf"] 2 | path = shell/.fzf 3 | url = https://github.com/junegunn/fzf.git 4 | -------------------------------------------------------------------------------- /emacs/.doom.d/favicon-pixel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hieutkt/dotfiles/HEAD/emacs/.doom.d/favicon-pixel.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | config/.config/* 2 | pop-os/.local/share/pop-launcher/plugins/bangs/db.json 3 | /emacs/.doom.d/config.el 4 | -------------------------------------------------------------------------------- /emacs/.doom.d/pics/org-em-dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hieutkt/dotfiles/HEAD/emacs/.doom.d/pics/org-em-dash.png -------------------------------------------------------------------------------- /emacs/.doom.d/pics/org-latex-preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hieutkt/dotfiles/HEAD/emacs/.doom.d/pics/org-latex-preview.png -------------------------------------------------------------------------------- /emacs/.doom.d/pics/citar-org-roam-follow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hieutkt/dotfiles/HEAD/emacs/.doom.d/pics/citar-org-roam-follow.png -------------------------------------------------------------------------------- /emacs/.doom.d/pics/org-preview-vs-export.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hieutkt/dotfiles/HEAD/emacs/.doom.d/pics/org-preview-vs-export.png -------------------------------------------------------------------------------- /ulauncher/.local/share/ulauncher/extensions/docfind/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hieutkt/dotfiles/HEAD/ulauncher/.local/share/ulauncher/extensions/docfind/images/icon.png -------------------------------------------------------------------------------- /ulauncher/.local/share/ulauncher/extensions/docfind/versions.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "required_api_version": "^1.0.0", 4 | "commit": "api-v1" 5 | }, 6 | { 7 | "required_api_version": "^2.0.0", 8 | "commit": "master" 9 | } 10 | ] 11 | -------------------------------------------------------------------------------- /shell/.fzf.zsh: -------------------------------------------------------------------------------- 1 | # Setup fzf 2 | # --------- 3 | if [[ ! "$PATH" == */home/hieuphay/.fzf/bin* ]]; then 4 | PATH="${PATH:+${PATH}:}/home/hieuphay/.fzf/bin" 5 | fi 6 | 7 | # Auto-completion 8 | # --------------- 9 | [[ $- == *i* ]] && source "/home/hieuphay/.fzf/shell/completion.zsh" 2> /dev/null 10 | 11 | # Key bindings 12 | # ------------ 13 | source "/home/hieuphay/.fzf/shell/key-bindings.zsh" 14 | -------------------------------------------------------------------------------- /shell/.fzf.bash: -------------------------------------------------------------------------------- 1 | # Setup fzf 2 | # --------- 3 | if [[ ! "$PATH" == */home/hieuphay/.fzf/bin* ]]; then 4 | PATH="${PATH:+${PATH}:}/home/hieuphay/.fzf/bin" 5 | fi 6 | 7 | # Auto-completion 8 | # --------------- 9 | [[ $- == *i* ]] && source "/home/hieuphay/.fzf/shell/completion.bash" 2> /dev/null 10 | 11 | # Key bindings 12 | # ------------ 13 | source "/home/hieuphay/.fzf/shell/key-bindings.bash" 14 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | #+TITLE: Personal dotfiles 2 | 3 | This is my dotfiles, managed using [[https://www.gnu.org/software/stow/][GNU stow]]. 4 | Basic prerequisites in an Ubuntu-based distro are as follow: 5 | 6 | #+begin_src bash 7 | sudo apt install fd-find ripgrep git stow 8 | #+end_src 9 | 10 | Then the dotfiles can be set up all at once with: 11 | 12 | #+begin_src bash 13 | cd 14 | git clone https://github.com/hieutkt/dotfiles 15 | cd dotfiles 16 | stow --target=$HOME --restow */ 17 | #+end_src 18 | 19 | Or set up individual modules only. For example: 20 | 21 | #+begin_src bash 22 | stow --target=$HOME --restow shell # For zsh or bash setup 23 | #+end_src 24 | -------------------------------------------------------------------------------- /scripts/.local/bin/docfind: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Document find in Zotero 3 | # Integration between ripgrep-all and fzf 4 | RG_PREFIX="rga --files-with-matches --ignore-case" 5 | file="$(cd ~/Dropbox/Documents/ZotFile/ && 6 | FZF_DEFAULT_COMMAND="$RG_PREFIX '$1'" \ 7 | fzf --sort --preview="[[ ! -z {} ]] && rga --pretty --context 5 {q} {}" \ 8 | --phony -q "$1" \ 9 | --color=pointer:magenta,spinner:#87ff00,fg+:yellow,info:green\ 10 | --bind "change:reload:$RG_PREFIX {q}" \ 11 | "--preview-window="60%)" && 12 | printf "Opening \033[0;34m$file" && 13 | nohup xdg-open "$HOME/Dropbox/Documents/ZotFile/$file" > /dev/null 2>&1 14 | -------------------------------------------------------------------------------- /ulauncher/.local/share/ulauncher/extensions/docfind/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "required_api_version": "^2.0.0", 3 | "name": "Document Find", 4 | "description": "Quick Search Search in stored Zotero Documents", 5 | "developer_name": "Nguyễn Đức Hiếu (Hiếu Phẩy)", 6 | "icon": "images/icon.png", 7 | "options": { 8 | "query_debounce": 0.3 9 | }, 10 | "preferences": [ 11 | { 12 | "id": "kw_df", 13 | "type": "keyword", 14 | "name": "df - Search in stored Zotero Documents", 15 | "default_value": "df" 16 | },{ 17 | "id": "kw_docfind", 18 | "type": "keyword", 19 | "name": "docfind - Search in stored Zotero Documents", 20 | "default_value": "docfind" 21 | }, 22 | { 23 | "id": "base_dir", 24 | "type": "input", 25 | "name": "Base dir", 26 | "description": "The base directory to start your searches. Ex: /", 27 | "default_value": "/home/hieuphay/Dropbox/Documents/ZotFile" 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /julia/.julia/config/startup.jl: -------------------------------------------------------------------------------- 1 | # Import 2 | using Revise 3 | using BenchmarkTools 4 | # using OhMyREPL; colorscheme!("GruvboxDark") 5 | 6 | # Eviroment 7 | push!(LOAD_PATH, homedir()*"/Dropbox/Codes/Julia") 8 | 9 | # To make @edit macro works within emacs `julia-repl-mode` 10 | if haskey(ENV, "INSIDE_EMACS") 11 | ENV["JULIA_EDITOR"] = "emacsclient" 12 | end 13 | 14 | # Functions 15 | function Base.show(s::Type) 16 | col = isconcretetype(s) ? :yellow : :blue 17 | col = isprimitivetype(s) ? :red : col 18 | printstyled(string(s); color=col) 19 | if ismutable(s) 20 | printstyled(" (mutable)"; color=:cyan) 21 | end 22 | end 23 | 24 | function subtypetree(roottype, prefix = "") 25 | stype = subtypes(roottype) 26 | if prefix == "" 27 | if length(stype) == 0 28 | show(roottype) 29 | print(" doesn't have any subtype, and is a subtype of ") 30 | show(supertype(roottype)) 31 | else 32 | show(roottype) 33 | end 34 | end 35 | for i in 1:length(stype) 36 | print("\n") 37 | printstyled(prefix, i == length(stype) ? "└── " : "├── "; color=:light_black) 38 | s = stype[i] 39 | show(s) 40 | subtypetree(s, "│ " * prefix) 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /shell/.zshrc: -------------------------------------------------------------------------------- 1 | # Enable Powerlevel10k instant prompt. Should stay close to the top of ~/.zshrc. 2 | # Initialization code that may require console input (password prompts, [y/n] 3 | # confirmations, etc.) must go above this block; everything else may go below. 4 | if [[ -r "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh" ]]; then 5 | source "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh" 6 | fi 7 | 8 | # Variables 9 | export ZSH="$HOME/.oh-my-zsh" 10 | export ZSH_THEME="powerlevel10k/powerlevel10k" 11 | export PATH="/usr/local/stata_now:$PATH" 12 | export PATH="$HOME/.config/emacs/bin:$PATH" 13 | export PATH="/usr/local/texlive/2025/bin/x86_64-linux:$PATH" 14 | export PATH="$HOME/.local/bin:$PATH" 15 | export POP_PLUGINS_PATH="$HOME/.local/share/pop-launcher/plugins" 16 | export FZF_DEFAULT_COMMAND="fd --type file --follow --hidden --exclude .git --color=always" 17 | export FZF_DEFAULT_OPTS="--ansi --layout=reverse --border --preview 'bat --color=always --style=header,grid --line-range :300 {}'" 18 | export LANG=en_US.UTF-8 19 | export EDITOR='emacsclient' 20 | 21 | # Aliases 22 | if which bat &> /dev/null ; then alias cat="bat" ; fi 23 | if which batcat &> /dev/null ; then alias cat="batcat"; fi 24 | if which fdfind &> /dev/null ; then alias fd="fdfind"; fi 25 | if which eza &> /dev/null ; 26 | then alias ls='eza --icons --color=always --group-directories-first'; 27 | alias la='eza -a --icons --color=always --group-directories-first'; 28 | alias ll='eza -alF --icons --color=always --group-directories-first'; 29 | fi 30 | 31 | # Critical external app setup 32 | # Oh-my-Zsh 33 | plugins=(git) 34 | source $ZSH/oh-my-zsh.sh 35 | # fzf integration 36 | [ -f ~/.fzf.zsh ] && source ~/.fzf.zsh 37 | 38 | rga-fzf() { 39 | RG_PREFIX="rga --files-with-matches" 40 | local file 41 | file="$( 42 | FZF_DEFAULT_COMMAND="$RG_PREFIX '$1'" \ 43 | fzf --sort --preview="[[ ! -z {} ]] && rga --pretty --context 5 {q} {}" \ 44 | --phony -q "$1" \ 45 | --bind "change:reload:$RG_PREFIX {q}" \ 46 | --preview-window="70%:wrap" 47 | )" && 48 | echo "opening $file" && 49 | xdg-open "$file" 50 | } 51 | 52 | # To customize prompt, run `p10k configure` or edit ~/.p10k.zsh. 53 | [[ ! -f ~/.p10k.zsh ]] || source ~/.p10k.zsh 54 | -------------------------------------------------------------------------------- /emacs/.doom.d/packages.el: -------------------------------------------------------------------------------- 1 | ;; -*- no-byte-compile: t; -*- 2 | ;;; $DOOMDIR/packages.el 3 | 4 | ;; General 5 | (package! auto-olivetti 6 | :recipe (:host sourcehut :repo "ashton314/auto-olivetti")) 7 | 8 | ;; LATEX 9 | (package! laas 10 | :recipe (:host github :repo "tecosaur/LaTeX-auto-activating-snippets")) 11 | 12 | ;; Icons 13 | (package! nerd-icons-ibuffer 14 | :recipe (:host github :repo "seagle0128/nerd-icons-ibuffer")) 15 | 16 | ;; LLM 17 | (package! inline-diff 18 | :recipe (:repo "https://code.tecosaur.net/tec/inline-diff")) 19 | 20 | ;; ORG-MODE 21 | ;; Org-mode from tecosaur's development branch 22 | (package! org :recipe 23 | (:host nil :repo "https://git.tecosaur.net/mirrors/org-mode.git" :remote "mirror" :fork 24 | (:host nil :repo "https://git.tecosaur.net/tec/org-mode.git" :branch "dev" :remote "tecosaur") 25 | :files 26 | (:defaults "etc") 27 | :build t :pre-build 28 | (with-temp-file "org-version.el" 29 | (require 'lisp-mnt) 30 | (let 31 | ((version 32 | (with-temp-buffer 33 | (insert-file-contents "lisp/org.el") 34 | (lm-header "version"))) 35 | (git-version 36 | (string-trim 37 | (with-temp-buffer 38 | (call-process "git" nil t nil "rev-parse" "--short" "HEAD") 39 | (buffer-string))))) 40 | (insert 41 | (format "(defun org-release () \"The release version of Org.\" %S)\n" version) 42 | (format "(defun org-git-version () \"The truncate git commit hash of Org mode.\" %S)\n" git-version) 43 | "(provide 'org-version)\n")))) 44 | :pin nil) 45 | 46 | (unpin! org) ; there be bugs 47 | 48 | 49 | (package! org-super-agenda 50 | :recipe (:host github :repo "alphapapa/org-super-agenda")) 51 | 52 | (package! org-contrib 53 | :recipe (:host nil :repo "https://git.sr.ht/~bzg/org-contrib" 54 | :files ("lisp/*.el")) 55 | :pin "6422b265f1150204f024e33d54f2dcfd8323005c") 56 | 57 | (package! org-modern 58 | :recipe (:host github :repo "minad/org-modern")) 59 | 60 | (package! svg-tag-mode 61 | :recipe (:host github :repo "rougier/svg-tag-mode")) 62 | 63 | (package! org-appear 64 | :recipe (:host github :repo "awth13/org-appear")) 65 | 66 | (package! org-roam-ui) 67 | 68 | (package! org-csl-activate 69 | :recipe (:host github :repo "andras-simonyi/org-cite-csl-activate")) 70 | 71 | (package! org-special-block-extras 72 | :recipe (:host github :repo "alhassy/org-special-block-extras")) 73 | 74 | (package! engrave-faces 75 | :recipe (:host github :repo "tecosaur/engrave-faces")) 76 | 77 | ;; Julia 78 | (unpin! julia-snail) 79 | 80 | (package! ob-julia 81 | :recipe (:host github :repo "karthink/ob-julia" :files ("*.el" "julia"))) 82 | 83 | ;; STATA 84 | (package! ess-stata-mode 85 | :recipe (:host github :repo "emacs-ess/ess-stata-mode")) 86 | 87 | ;; RSS 88 | (package! elfeed-score 89 | :recipe (:host github :repo "sp1ff/elfeed-score")) 90 | 91 | 92 | ;; ;; Matlab 93 | ;; (package! matlab-mode 94 | ;; :recipe (:repo "https://git.code.sf.net/p/matlab-emacs/src")) 95 | 96 | ;; Magit 97 | ;; (package! magit-file-icons 98 | ;; :recipe (:host github :repo "gekoke/magit-file-icons")) 99 | -------------------------------------------------------------------------------- /shell/.bashrc: -------------------------------------------------------------------------------- 1 | # ~/.bashrc: executed by bash(1) for non-login shells. 2 | # see /usr/share/doc/bash/examples/startup-files (in the package bash-doc) 3 | # for examples 4 | 5 | # If not running interactively, don't do anything 6 | case $- in 7 | *i*) ;; 8 | *) return;; 9 | esac 10 | 11 | # don't put duplicate lines or lines starting with space in the history. 12 | # See bash(1) for more options 13 | HISTCONTROL=ignoreboth 14 | 15 | # append to the history file, don't overwrite it 16 | shopt -s histappend 17 | 18 | # for setting history length see HISTSIZE and HISTFILESIZE in bash(1) 19 | HISTSIZE=1000 20 | HISTFILESIZE=2000 21 | 22 | # check the window size after each command and, if necessary, 23 | # update the values of LINES and COLUMNS. 24 | shopt -s checkwinsize 25 | 26 | # If set, the pattern "**" used in a pathname expansion context will 27 | # match all files and zero or more directories and subdirectories. 28 | #shopt -s globstar 29 | 30 | # make less more friendly for non-text input files, see lesspipe(1) 31 | [ -x /usr/bin/lesspipe ] && eval "$(SHELL=/bin/sh lesspipe)" 32 | 33 | # set variable identifying the chroot you work in (used in the prompt below) 34 | if [ -z "${debian_chroot:-}" ] && [ -r /etc/debian_chroot ]; then 35 | debian_chroot=$(cat /etc/debian_chroot) 36 | fi 37 | 38 | # set a fancy prompt (non-color, unless we know we "want" color) 39 | case "$TERM" in 40 | xterm-color|*-256color) color_prompt=yes;; 41 | esac 42 | 43 | # uncomment for a colored prompt, if the terminal has the capability; turned 44 | # off by default to not distract the user: the focus in a terminal window 45 | # should be on the output of commands, not on the prompt 46 | #force_color_prompt=yes 47 | 48 | if [ -n "$force_color_prompt" ]; then 49 | if [ -x /usr/bin/tput ] && tput setaf 1 >&/dev/null; then 50 | # We have color support; assume it's compliant with Ecma-48 51 | # (ISO/IEC-6429). (Lack of such support is extremely rare, and such 52 | # a case would tend to support setf rather than setaf.) 53 | color_prompt=yes 54 | else 55 | color_prompt= 56 | fi 57 | fi 58 | 59 | if [ "$color_prompt" = yes ]; then 60 | PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ ' 61 | else 62 | PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ ' 63 | fi 64 | unset color_prompt force_color_prompt 65 | 66 | # If this is an xterm set the title to user@host:dir 67 | case "$TERM" in 68 | xterm*|rxvt*) 69 | PS1="\[\e]0;${debian_chroot:+($debian_chroot)}\u@\h: \w\a\]$PS1" 70 | ;; 71 | *) 72 | ;; 73 | esac 74 | 75 | # enable color support of ls and also add handy aliases 76 | if [ -x /usr/bin/dircolors ]; then 77 | test -r ~/.dircolors && eval "$(dircolors -b ~/.dircolors)" || eval "$(dircolors -b)" 78 | alias ls='ls --color=auto' 79 | #alias dir='dir --color=auto' 80 | #alias vdir='vdir --color=auto' 81 | 82 | alias grep='grep --color=auto' 83 | alias fgrep='fgrep --color=auto' 84 | alias egrep='egrep --color=auto' 85 | fi 86 | 87 | # colored GCC warnings and errors 88 | #export GCC_COLORS='error=01;31:warning=01;35:note=01;36:caret=01;32:locus=01:quote=01' 89 | 90 | # some more ls aliases 91 | alias ll='ls -alF' 92 | alias la='ls -A' 93 | alias l='ls -CF' 94 | 95 | # Add an "alert" alias for long running commands. Use like so: 96 | # sleep 10; alert 97 | alias alert='notify-send --urgency=low -i "$([ $? = 0 ] && echo terminal || echo error)" "$(history|tail -n1|sed -e '\''s/^\s*[0-9]\+\s*//;s/[;&|]\s*alert$//'\'')"' 98 | 99 | # Alias definitions. 100 | # You may want to put all your additions into a separate file like 101 | # ~/.bash_aliases, instead of adding them here directly. 102 | # See /usr/share/doc/bash-doc/examples in the bash-doc package. 103 | 104 | if [ -f ~/.bash_aliases ]; then 105 | . ~/.bash_aliases 106 | fi 107 | 108 | # enable programmable completion features (you don't need to enable 109 | # this, if it's already enabled in /etc/bash.bashrc and /etc/profile 110 | # sources /etc/bash.bashrc). 111 | if ! shopt -oq posix; then 112 | if [ -f /usr/share/bash-completion/bash_completion ]; then 113 | . /usr/share/bash-completion/bash_completion 114 | elif [ -f /etc/bash_completion ]; then 115 | . /etc/bash_completion 116 | fi 117 | fi 118 | 119 | # path configuration 120 | export PATH="/usr/local/stata15:$PATH" 121 | export PATH="$HOME/.emacs.d/bin:$PATH" 122 | export PATH="$HOME/.local/bin:$PATH" 123 | export POP_PLUGINS_PATH="$HOME/.local/share/pop-launcher/plugins" 124 | 125 | [ -f ~/.fzf.bash ] && source ~/.fzf.bash 126 | -------------------------------------------------------------------------------- /ulauncher/.local/share/ulauncher/extensions/docfind/main.py: -------------------------------------------------------------------------------- 1 | """ Main Module """ 2 | 3 | import logging 4 | import os 5 | import subprocess 6 | import mimetypes 7 | import gi 8 | from collections import Counter 9 | gi.require_version('Gtk', '3.0') 10 | # pylint: disable=import-error 11 | from gi.repository import Gio, Gtk 12 | from ulauncher.api.client.Extension import Extension 13 | from ulauncher.api.client.EventListener import EventListener 14 | from ulauncher.api.shared.event import KeywordQueryEvent 15 | from ulauncher.api.shared.item.ExtensionResultItem import ExtensionResultItem 16 | from ulauncher.api.shared.item.ExtensionSmallResultItem import ExtensionSmallResultItem 17 | from ulauncher.api.shared.action.RenderResultListAction import RenderResultListAction 18 | from ulauncher.api.shared.action.OpenAction import OpenAction 19 | from ulauncher.api.shared.action.RunScriptAction import RunScriptAction 20 | from ulauncher.api.shared.action.DoNothingAction import DoNothingAction 21 | from ulauncher.api.shared.action.HideWindowAction import HideWindowAction 22 | 23 | LOGGING = logging.getLogger(__name__) 24 | 25 | 26 | class FileSearchExtension(Extension): 27 | """ Main Extension Class """ 28 | 29 | def __init__(self): 30 | """ Initializes the extension """ 31 | super(FileSearchExtension, self).__init__() 32 | self.subscribe(KeywordQueryEvent, KeywordQueryEventListener()) 33 | 34 | def search(self, query): 35 | """ Try with the default fd or the previously successful command """ 36 | bin_name = 'rga' 37 | 38 | """ Searches for Files using ripgrep-all """ 39 | cmd = [ 40 | 'timeout', '5s', 'ionice', '-c', '3', 41 | bin_name, query, self.preferences['base_dir'], 42 | # '--files-with-matches', 43 | '--vimgrep', 44 | '--ignore-case' 45 | ] 46 | 47 | LOGGING.info(cmd) 48 | 49 | process = subprocess.Popen(cmd, 50 | stdout=subprocess.PIPE, 51 | stderr=subprocess.PIPE) 52 | 53 | out, err = process.communicate() 54 | 55 | if err: 56 | self.logger.error(err) 57 | return [] 58 | 59 | matches = out.split('\n'.encode()) 60 | matches = list([_f.split(':'.encode())[0] for _f in matches if _f]) # remove empty lines 61 | matches = Counter(matches) 62 | # Sort by count 63 | matches = {k: v for k, v in sorted(matches.items(), 64 | key=lambda e: e[1], reverse=True)} 65 | 66 | result = [] 67 | #get folder icon outside loop, so it only happens once 68 | file = Gio.File.new_for_path("/") 69 | icon_theme = Gtk.IconTheme.get_default() 70 | 71 | # pylint: disable=C0103 72 | for filepath, count in matches.items(): 73 | filename = os.path.basename(filepath) 74 | if len(splits := filename.split(b") ", 1)) == 2: 75 | authoryear, title = splits 76 | authoryear = authoryear + b")" 77 | else: 78 | authoryear, title = filename.split(b" ", 1) 79 | 80 | authoryear = f"{count:<3} match{'es' if count > 1 else ''} — ".encode() + authoryear 81 | 82 | type_, encoding = mimetypes.guess_type(filepath.decode('utf-8')) 83 | 84 | if type_: 85 | file_icon = Gio.content_type_get_icon(type_) 86 | file_info = icon_theme.choose_icon(file_icon.get_names(), 128, 0) 87 | if file_info: 88 | icon = file_info.get_filename() 89 | else: 90 | icon = "images/icon.png" 91 | else: 92 | icon = "images/icon.png" 93 | 94 | result.append({'path': filepath, 95 | 'name': title.decode('utf-8'), 96 | 'description': authoryear, 97 | 'icon': icon}) 98 | return result 99 | 100 | def get_open_at_search_position(self, path, query): 101 | """ Returns open pdf file at query location""" 102 | cmd = f"xdg-open '{path}'" 103 | LOGGING.info(cmd) 104 | return RunScriptAction(cmd) 105 | 106 | 107 | class KeywordQueryEventListener(EventListener): 108 | """ Listener that handles the user input """ 109 | 110 | # pylint: disable=unused-argument,no-self-use 111 | def on_event(self, event, extension): 112 | """ Handles the event """ 113 | items = [] 114 | 115 | query = event.get_argument() 116 | 117 | if not query or len(query) < 3: 118 | return RenderResultListAction([ 119 | ExtensionResultItem( 120 | icon='images/icon.png', 121 | name='Docfind', 122 | description='Search for keyword in stored Zotero docmuments...', 123 | on_enter=DoNothingAction()) 124 | ]) 125 | 126 | results = extension.search(query.strip()) 127 | 128 | if not results: 129 | return RenderResultListAction([ 130 | ExtensionResultItem( 131 | icon='images/icon.png', 132 | name='Docfind', 133 | description=f'No Results found matching: {query}', 134 | on_enter=HideWindowAction()) 135 | ]) 136 | 137 | items = [] 138 | for result in results: 139 | items.append( 140 | ExtensionResultItem( 141 | icon=result['icon'], 142 | name=result['name'], 143 | description=result['description'].decode("utf-8"), 144 | on_enter=extension.get_open_at_search_position(result['path'].decode("utf-8"), query) 145 | )) 146 | 147 | return RenderResultListAction(items) 148 | 149 | 150 | if __name__ == '__main__': 151 | FileSearchExtension().run() 152 | -------------------------------------------------------------------------------- /latex/texmf/tex/generic/hpcheatsheet.cls: -------------------------------------------------------------------------------- 1 | \NeedsTeXFormat{LaTeX2e} 2 | \ProvidesClass{hpcheatsheet}[2024/12/10 A simpler cheatsheet class with title & author in header, LuaLaTeX, no extra configs but calc loaded] 3 | 4 | % Load the base class 5 | \LoadClassWithOptions{article} 6 | 7 | % Handle class options 8 | \RequirePackage{xkeyval} 9 | 10 | % Default colors and columns 11 | \newcommand*\CheatSheetBgColor{white} 12 | \newcommand*\CheatSheetFgColor{black} 13 | \newcommand*\CheatSheetColumns{4} 14 | \newcommand*\CheatSheetFontSize{ssmall} 15 | 16 | \DeclareOptionX{bgcolor}{\renewcommand*\CheatSheetBgColor{#1}} 17 | \DeclareOptionX{fgcolor}{\renewcommand*\CheatSheetFgColor{#1}} 18 | \DeclareOptionX{columns}{\renewcommand*\CheatSheetColumns{#1}} 19 | \DeclareOptionX{fontsize}{\renewcommand*\CheatSheetFontSize{#1}} 20 | 21 | \ProcessOptionsX\relax 22 | 23 | % Detect engine 24 | \RequirePackage{iftex} 25 | 26 | \ifLuaTeX 27 | % LuaLaTeX settings 28 | \RequirePackage{fontspec} 29 | \defaultfontfeatures{Ligatures=TeX} 30 | \setmainfont{TeX Gyre Pagella} 31 | \else 32 | % pdfLaTeX settings 33 | \RequirePackage[T1]{fontenc} 34 | \RequirePackage{lmodern} 35 | \fi 36 | 37 | % Essential packages 38 | \RequirePackage[framemethod=TikZ]{mdframed} 39 | \RequirePackage{amsthm} 40 | \RequirePackage[landscape]{geometry} 41 | \RequirePackage{multicol} 42 | \RequirePackage{tikz} 43 | \usetikzlibrary{calc} % Needed for coordinate calculations 44 | \RequirePackage{xcolor} 45 | \RequirePackage{amsmath} 46 | \RequirePackage{changepage} 47 | \RequirePackage{amssymb} 48 | \RequirePackage{fancyhdr} 49 | \RequirePackage[many]{tcolorbox} 50 | \RequirePackage{moresize} 51 | \RequirePackage{fullpage} 52 | \RequirePackage{cancel} 53 | 54 | % Basic settings 55 | % \setlength{\baselineskip}{1.2em} 56 | % \setlength{\parskip}{-0.75em} 57 | 58 | % Define some colors 59 | \definecolor{r1}{RGB}{255, 191, 191} 60 | \definecolor{r2}{RGB}{255, 191, 223} 61 | \definecolor{r3}{RGB}{255, 207, 207} 62 | 63 | \definecolor{b1}{RGB}{191, 223, 255} 64 | \definecolor{b2}{RGB}{191, 239, 255} 65 | \definecolor{b3}{RGB}{191, 255, 255} 66 | 67 | \definecolor{g1}{RGB}{191, 255, 191} 68 | \definecolor{g2}{RGB}{191, 255, 223} 69 | \definecolor{g3}{RGB}{207, 255, 207} 70 | 71 | \definecolor{o1}{RGB}{255, 223, 191} 72 | \definecolor{o2}{RGB}{255, 239, 191} 73 | \definecolor{o3}{RGB}{255, 231, 191} 74 | 75 | \definecolor{v1}{RGB}{223, 191, 255} 76 | \definecolor{v2}{RGB}{239, 191, 255} 77 | \definecolor{v3}{RGB}{231, 191, 255} 78 | 79 | \definecolor{y1}{RGB}{255, 255, 191} 80 | \definecolor{y2}{RGB}{255, 247, 191} 81 | \definecolor{y3}{RGB}{255, 239, 191} 82 | 83 | \definecolor{w}{HTML}{eeeeee} 84 | \definecolor{g}{HTML}{444444} 85 | \definecolor{b}{HTML}{222222} 86 | \definecolor{lightgrey}{HTML}{cccccc} 87 | 88 | % Geometry adjustments 89 | \geometry{ 90 | letterpaper, 91 | left=0.25in, 92 | right=0.25in, 93 | top=0.3in, 94 | bottom=0.25in 95 | } 96 | 97 | \parindent0pt 98 | 99 | \newtcolorbox{conceptbox}[2][]{ 100 | breakable, 101 | vfill before first=false, 102 | segmentation at break=false, 103 | size=fbox, 104 | title=\scriptsize\textbf{\MakeUppercase{#2}}, 105 | left=2pt, 106 | right=2pt, 107 | top=3pt, 108 | bottom=1pt, 109 | boxrule=1pt, 110 | coltitle=\CheatSheetFgColor, 111 | colupper=\CheatSheetFgColor, 112 | pad at break=5pt, 113 | toprule at break=4pt, 114 | bottomrule at break=0.75pt, 115 | colframe=#1, 116 | colback=\CheatSheetBgColor!90!#1, 117 | enlargepage=12in, 118 | before skip = -2em, 119 | after skip = 2.5em 120 | } 121 | 122 | % Line node command (requires calc) 123 | \newcommand{\nc}[2][b]{% 124 | \newline \tikz \draw [draw=#1,thick] 125 | ($(current page.center)-(0.495\linewidth,0)$) -- 126 | ($(current page.center)+(0.495\linewidth,0)$) 127 | node [midway, fill=\CheatSheetBgColor!90!#1, text=\CheatSheetFgColor] {\ssmall\textbf{\uppercase{#2}}}; 128 | } 129 | 130 | % Booleans 131 | \newif\ifboxopen 132 | \boxopenfalse 133 | 134 | \newif\ifcontentstarted 135 | \contentstartedfalse 136 | 137 | \newcommand{\startcolumn}{% 138 | \strut\par 139 | } 140 | 141 | % \boxtitle command 142 | \newcommand{\boxtitle}[2][v3]{% 143 | \ifboxopen 144 | \end{conceptbox} 145 | \boxopenfalse 146 | \fi 147 | \begin{conceptbox}[#1]{#2} 148 | \boxopentrue 149 | } 150 | 151 | % \sectiontitle command 152 | \newcommand{\sectiontitle}[2][v3]{% 153 | \nc[#1]{#2}\\[0.5em] 154 | } 155 | 156 | % \boxcolumnbreak command 157 | \newcommand{\boxcolumnbreak}{% 158 | \ifcontentstarted 159 | \ifboxopen 160 | \end{conceptbox} 161 | \boxopenfalse 162 | \fi 163 | \columnbreak 164 | \startcolumn 165 | \else 166 | \PackageError{cheatsheet}{\string\boxcolumnbreak\space used before multicols started}{% 167 | This command can only be used after the document starts.}% 168 | \fi 169 | } 170 | 171 | % \boxpagebreak command 172 | \newcommand{\boxpagebreak}{% 173 | \ifcontentstarted 174 | \ifboxopen 175 | \end{conceptbox} 176 | \boxopenfalse 177 | \fi 178 | \newpage 179 | \startcolumn 180 | \else 181 | \PackageError{cheatsheet}{\string\boxpagebreak\space used before multicols started}{% 182 | This command can only be used after the document starts.}% 183 | \fi 184 | } 185 | 186 | 187 | % Custom header style using TikZ (old style) 188 | \renewcommand{\headrulewidth}{0pt} 189 | \newcommand{\cheatsheetheader}{% 190 | \vspace{-1.5em} 191 | \begin{tikzpicture} 192 | \pgfmathsetlengthmacro{\FullWidth}{\textwidth} 193 | \pgfmathsetlengthmacro{\LineEnd}{\textwidth-0.25in} 194 | \draw[thick, color=\CheatSheetFgColor] (1,0) -- (\LineEnd,0); 195 | \node[anchor=west, fill=\CheatSheetBgColor!90!\CheatSheetFgColor, inner sep=3pt, text=\CheatSheetFgColor] at (0,0) {\ssmall\textbf{\MakeUppercase{\@title}}}; 196 | \node[anchor=east, fill=\CheatSheetBgColor!90!\CheatSheetFgColor, inner sep=3pt, text=\CheatSheetFgColor] at (\FullWidth,0) {\ssmall\textbf{\MakeUppercase{\@author}}}; 197 | \end{tikzpicture}% 198 | } 199 | 200 | \renewcommand\maketitle{} 201 | 202 | 203 | \AtBeginDocument{% 204 | \pagecolor{\CheatSheetBgColor}% 205 | \color{\CheatSheetFgColor}% 206 | \csname\CheatSheetFontSize\endcsname% 207 | \pagestyle{fancy} 208 | \fancyhf{} 209 | \fancyheadoffset{0pt} 210 | \setlength{\headwidth}{\textwidth} 211 | \fancyhead[C]{\cheatsheetheader} 212 | \begin{multicols*}{\CheatSheetColumns} 213 | \raggedcolumns 214 | \startcolumn 215 | \contentstartedtrue 216 | } 217 | 218 | \AtEndDocument{% 219 | \ifboxopen 220 | \end{conceptbox} 221 | \fi 222 | \ifcontentstarted 223 | \end{multicols*} 224 | \fi 225 | } 226 | \endinput 227 | -------------------------------------------------------------------------------- /emacs/.doom.d/init.el: -------------------------------------------------------------------------------- 1 | ;;; init.el -*- lexical-binding: t; -*- 2 | 3 | ;; This file controls what Doom modules are enabled and what order they load 4 | ;; in. Remember to run 'doom sync' after modifying it! 5 | 6 | (setq evil-respect-visual-line-mode t) 7 | 8 | ;; NOTE Press 'SPC h d h' (or 'C-h d h' for non-vim users) to access Doom's 9 | ;; documentation. There you'll find a "Module Index" link where you'll find 10 | ;; a comprehensive list of Doom's modules and what flags they support. 11 | 12 | ;; NOTE Move your cursor over a module's name (or its flags) and press 'K' (or 13 | ;; 'C-c c k' for non-vim users) to view its documentation. This works on 14 | ;; flags as well (those symbols that start with a plus). 15 | ;; 16 | ;; Alternatively, press 'gd' (or 'C-c c d') on a module to browse its 17 | ;; directory (for easy access to its source code). 18 | 19 | (doom! :input 20 | ;;chinese 21 | ;;japanese 22 | 23 | :completion 24 | ;; (company +childframe) ; the ultimate code completion backend 25 | (corfu +icons +orderless) 26 | ;;helm ; the *other* search engine for love and life 27 | ;;ido ; the other *other* search engine... 28 | ;; (ivy) ; a search engine for love and life 29 | (vertico +icons) 30 | 31 | :os 32 | tty 33 | 34 | :ui 35 | ;;deft ; notational velocity for Emacs 36 | doom ; what makes DOOM look the way it does 37 | doom-dashboard ; a nifty splash screen for Emacs 38 | doom-quit ; DOOM quit-message prompts when you quit Emacs 39 | ;; hl-todo ; highlight TODO/FIXME/NOTE/DEPRECATED/HACK/REVIEW 40 | hydra 41 | ;;indent-guides ; highlighted indent columns 42 | minimap ; show a map of the code on the side 43 | modeline ; snazzy, Atom-inspired modeline, plus API 44 | ;;nav-flash ; blink cursor line after big motions 45 | ;;neotree ; a project drawer, like NERDTree for vim 46 | ophints ; highlight the region an operation acts on 47 | (popup +defaults) ; tame sudden yet inevitable temporary windows 48 | (ligatures) 49 | ;;tabs ; an tab bar for Emacs 50 | treemacs ; a project drawer, like neotree but cooler 51 | unicode ; extended unicode support for various languages 52 | (vc-gutter +diff-hl +pretty) ; vcs diff in the fringe 53 | vi-tilde-fringe ; fringe tildes to mark beyond EOB 54 | ;;window-select ; visually switch windows 55 | workspaces ; tab emulation, persistence & separate workspaces 56 | zen ; distraction-free coding or writing 57 | ;; (emoji +unicode) 58 | 59 | :editor 60 | (evil +everywhere) ; come to the dark side, we have cookies 61 | file-templates ; auto-snippets for empty files 62 | fold ; (nigh) universal code folding 63 | ;;(format +onsave) ; automated prettiness 64 | ;;god ; run Emacs commands without modifier keys 65 | ;;lispy ; vim for lisp, for people who don't like vim 66 | multiple-cursors ; editing in many places at once 67 | ;;objed ; text object editing for the innocent 68 | ;;parinfer ; turn lisp into python, sort of 69 | ;;rotate-text ; cycle region at point between text candidates 70 | snippets ; my elves. They type so I don't have to 71 | word-wrap ; soft wrapping with language-aware indent 72 | 73 | :emacs 74 | (dired +icons) ; making dired pretty [functional] 75 | electric ; smarter, keyword-based electric-indent 76 | (ibuffer + icons) ; interactive buffer management 77 | undo ; persistent, smarter undo for your inevitable mistakes 78 | vc ; version-control and Emacs, sitting in a tree 79 | 80 | :term 81 | (:if IS-WINDOWS shell) ; the elisp shell that works everywhere 82 | (:if IS-LINUX vterm) ; the best terminal emulation in Emacs 83 | 84 | :checkers 85 | ;;syntax ; tasing you for every semicolon you forget 86 | ;;spell ; tasing you for misspelling mispelling 87 | ;;grammar ; tasing grammar mistake every you make 88 | 89 | :tools 90 | ;;ansible 91 | biblio 92 | ;;debugger ; FIXME stepping through code, to help you add bugs 93 | ;;direnv 94 | ;;docker 95 | ;;editorconfig ; let someone else argue about tabs vs spaces 96 | ;;ein ; tame Jupyter notebooks with emacs 97 | (eval +overlay) ; run code, run (also, repls) 98 | ;;gist ; interacting with github gists 99 | (lookup +dictionary) ; navigate your code and its documentation 100 | lsp 101 | ;;macos ; MacOS-specific commands 102 | (magit +forge) ; a git porcelain for Emacs 103 | ;;make ; run make tasks from Emacs 104 | ;;pass ; password manager for nerds 105 | pdf ; pdf enhancements 106 | ;;prodigy ; FIXME managing external services & code builders 107 | ;;rgb ; creating color strings 108 | ;;taskrunner ; taskrunner for all your projects 109 | ;;terraform ; infrastructure as code 110 | ;;tmux ; an API for interacting with tmux 111 | tree-sitter ; syntax and parsing, sitting in a tree... 112 | llm 113 | ;;upload ; map local to remote projects via ssh/ftp 114 | 115 | :lang 116 | ;;agda ; types of types of types of types... 117 | ;;cc ; C/C++/Obj-C madness 118 | ;;clojure ; java with a lisp 119 | ;;common-lisp ; if you've seen one lisp, you've seen them all 120 | ;;coq ; proofs-as-programs 121 | ;;crystal ; ruby at the speed of c 122 | ;;csharp ; unity, .NET, and mono shenanigans 123 | data ; config/data formats 124 | ;;(dart +flutter) ; paint ui and not much else 125 | ;;elixir ; erlang done right 126 | ;;elm ; care for a cup of TEA? 127 | emacs-lisp ; drown in parentheses 128 | ;;erlang ; an elegant language for a more civilized age 129 | (ess +lsp) ; emacs speaks statistics 130 | ;;faust ; dsp, but you get to keep your soul 131 | (fortran +lsp +intel) 132 | ;;fsharp ; ML stands for Microsoft's Language 133 | ;;fstar ; (dependent) types and (monadic) effects and Z3 134 | ;;gdscript ; the language you waited for 135 | ;;(go +lsp) ; the hipster dialect 136 | ;;(haskell +dante) ; a language that's lazier than I am 137 | ;;hy ; readability of scheme w/ speed of python 138 | ;;idris 139 | ;;json ; At least it ain't XML 140 | ;;(java +meghanada) ; the poster child for carpal tunnel syndrome 141 | ;;javascript ; all(hope(abandon(ye(who(enter(here)))))) 142 | (julia +snail +lsp +tree-sitter); a better, faster MATLAB 143 | ;;kotlin ; a better, slicker Java(Script) 144 | (latex +latexmk +cdlatex +lsp) ; writing papers in Emacs has never been so fun 145 | ;;lean 146 | ;;factor 147 | ;;ledger ; an accounting system in Emacs 148 | ;;lua ; one-based indices? one-based indices 149 | markdown ; writing docs for people to ignore 150 | ;;nim ; python + lisp at the speed of c 151 | ;;nix ; I hereby declare "nix geht mehr!" 152 | ;;ocaml ; an objective camel 153 | (org +roam2 +jupyter +hugo 154 | +beamer 155 | +dragndrop) ; organize your plain life in plain text 156 | ;;php ; perl's insecure younger brother 157 | plantuml ; diagrams for confusing people more 158 | ;;purescript ; javascript, but functional 159 | (python +lsp +pyright +poetry 160 | +tree-sitter) ; beautiful is better than ugly 161 | ;;qt ; the 'cutest' gui framework ever 162 | ;;racket ; a DSL for DSLs 163 | ;;raku ; the artist formerly known as perl6 164 | ;;rest ; Emacs as a REST client 165 | ;;rst ; ReST in peace 166 | ;;(ruby +rails) ; 1.step {|i| p "Ruby is #{i.even? ? 'love' : 'life'}"} 167 | rust ; Fe2O3.unwrap().unwrap().unwrap().unwrap() 168 | ;;scala ; java, but good 169 | ;;scheme ; a fully conniving family of lisps 170 | (sh +tree-sitter) ; she sells {ba,z,fi}sh shells on the C xor 171 | ;;sml 172 | ;;solidity ; do you need a blockchain? No. 173 | ;;swift ; who asked for emoji variables? 174 | ;;terra ; Earth and Moon in alignment for performance. 175 | (web +css +html +lsp) ; the tubes 176 | yaml ; JSON, but readable 177 | 178 | :email 179 | ;;(mu4e +gmail) 180 | ;;notmuch 181 | ;;(wanderlust +gmail) 182 | 183 | :app 184 | ;;calendar 185 | ;;irc ; how neckbeards socialize 186 | (rss +org) ; emacs as an RSS reader 187 | ;;twitter ; twitter client https://twitter.com/vnought 188 | 189 | :config 190 | literate 191 | (default +bindings +smartparens)) 192 | ;; 193 | -------------------------------------------------------------------------------- /emacs/.doom.d/themes/doom-everforest-theme.el: -------------------------------------------------------------------------------- 1 | ;;; doom-everforest-theme.el --- inspired by Everforest 2 | ;;; https://github.com/sainnhe/everforest 3 | (require 'doom-themes) 4 | 5 | ;; 6 | (defgroup doom-everforest-theme nil 7 | "Options for doom-everforest" 8 | :group 'doom-themes) 9 | 10 | (defcustom doom-everforest-brighter-modeline nil 11 | "If non-nil, more vivid colors will be used to style the mode-line." 12 | :group 'doom-everforest-theme 13 | :type 'boolean) 14 | 15 | (defcustom doom-everforest-brighter-comments nil 16 | "If non-nil, comments will be highlighted in more vivid colors." 17 | :group 'doom-everforest-theme 18 | :type 'boolean) 19 | 20 | (defcustom doom-everforest-comment-bg doom-everforest-brighter-comments 21 | "If non-nil, comments will have a subtle, darker background. Enhancing their 22 | legibility." 23 | :group 'doom-everforest-theme 24 | :type 'boolean) 25 | 26 | (defcustom doom-everforest-padded-modeline doom-themes-padded-modeline 27 | "If non-nil, adds a 4px padding to the mode-line. Can be an integer to 28 | determine the exact padding." 29 | :group 'doom-everforest-theme 30 | :type '(choice integer boolean)) 31 | 32 | (defcustom doom-everforest-background nil 33 | "Choice between \"soft\", \"medium\" and \"hard\" background contrast. 34 | Defaults to \"soft\"" 35 | :group 'doom-everforest-theme 36 | :type 'string) 37 | 38 | (defcustom doom-everforest-palette nil 39 | "Choose between \"material\", \"mix\" and \"original\" color palette. 40 | Defaults to \"material\"" 41 | :group 'doom-everforest-theme 42 | :type 'string) 43 | 44 | (defcustom doom-everforest-dired-height 1.15 45 | "Font height for dired buffers" 46 | :group 'doom-everforest-theme 47 | :type 'float) 48 | ;; colors from 49 | ;; https://github.com/sainnhe/everforest/blob/master/autoload/everforest.vim 50 | (cond 51 | ((equal doom-everforest-background "hard") 52 | (setq ef/bg "#272e33" ;; bg0 53 | ef/bg-alt "#1e2326" ;; bg1 54 | ef/base0 "#3a454a" ;; bg2 55 | ef/base1 "#445055" ;; bg3 56 | ef/base2 "#4c555b" ;; bg4 57 | ef/base3 "#53605c" ;; bg5 58 | ef/base4 "#503946")) ;; bg_visual 59 | ((equal doom-everforest-background "medium") 60 | (setq ef/bg "#2d353b" ;; bg0 61 | ef/bg-alt "#232a2e" ;; bg1 62 | ef/base0 "#404c51" ;; bg2 63 | ef/base1 "#4a555b" ;; bg3 64 | ef/base2 "#525c62" ;; bg4 65 | ef/base3 "#596763" ;; bg5 66 | ef/base4 "#573e4c")) ;; bg_visual 67 | (t 68 | (setq ef/bg "#333c43" ;; bg0 69 | ef/bg-alt "#293136" ;; bg1 70 | ef/base0 "#465258" ;; bg2 71 | ef/base1 "#505a60" ;; bg3 72 | ef/base2 "#576268" ;; bg4 73 | ef/base3 "#5f6d67" ;; bg5 74 | ef/base4 "#5d4251"))) ;; bg_visual 75 | 76 | (def-doom-theme doom-everforest 77 | "A dark theme inspired by Everforest" 78 | ;; name default 256 16 79 | ((bg `(,ef/bg nil nil )) 80 | (bg-alt `(,ef/bg-alt nil nil )) 81 | (base0 `(,ef/base0 "black" "black" )) 82 | (base1 `(,ef/base1 "#1e1e1e" "brightblack" )) 83 | (base2 `(,ef/base2 "#2e2e2e" "brightblack" )) 84 | (base3 `(,ef/base3 "#262626" "brightblack" )) 85 | (base4 `(,ef/base4 "#3f3f3f" "brightblack" )) 86 | (base5 '("#7a8478" "#525252" "brightblack" )) 87 | (base6 '("#859289" "#6b6b6b" "brightblack" )) 88 | (base7 '("#9da9a0" "#979797" "brightblack" )) 89 | (base8 '("#7a8478" "#dfdfdf" "white" )) 90 | (fg '("#d3c6aa" "#bfbfbf" "brightwhite" )) 91 | (fg-alt '("#b9c0ab" "#2d2d2d" "white" )) ;; bg5 light soft 92 | 93 | (grey base8) 94 | (red '("#e67e80" "#ff6655" "red" )) 95 | (orange '("#e69875" "#dd8844" "brightred" )) 96 | (green '("#a7c080" "#99bb66" "green" )) 97 | (teal '("#83c092" "#44b9b1" "brightgreen" )) ;; aqua 98 | (yellow '("#dbbc7f" "#ECBE7B" "yellow" )) 99 | (blue '("#7fbbb3" "#51afef" "brightblue" )) 100 | (dark-blue `("#60948d" "#2257A0" "blue" )) ;; own 101 | (magenta '("#d699b6" "#c678dd" "brightmagenta")) ;; purple 102 | (violet '("#d699b6" "#a9a1e1" "magenta" )) ;; purple 103 | (cyan '("#83c092" "#46D9FF" "brightcyan" )) ;; aqua 104 | (dark-cyan `("#74ab82" "#5699AF" "cyan" )) ;; own 105 | 106 | ;; face categories -- required for all themes 107 | (highlight cyan) 108 | (vertical-bar (doom-darken base1 0.1)) 109 | (selection dark-blue) 110 | (builtin magenta) 111 | (comments (if doom-everforest-brighter-comments cyan 112 | (doom-blend magenta cyan 0.65))) 113 | (doc-comments (doom-lighten (if doom-everforest-brighter-comments dark-cyan green) 0.2)) 114 | (constants violet) 115 | (functions cyan) 116 | (keywords (doom-lighten teal 0.1)) 117 | (methods cyan) 118 | (operators blue) 119 | (type orange) 120 | (strings green) 121 | (variables blue) 122 | (numbers magenta) 123 | (region `(,(doom-lighten (car bg-alt) 0.15) ,@(doom-lighten (cdr base1) 0.35))) 124 | (error red) 125 | (warning yellow) 126 | (success green) 127 | (vc-modified orange) 128 | (vc-added green) 129 | (vc-deleted red) 130 | 131 | ;; custom categories 132 | (hidden `(,(car bg) "black" "black")) 133 | (-modeline-bright doom-everforest-brighter-modeline) 134 | (-modeline-pad 135 | (when doom-everforest-padded-modeline 136 | (if (integerp doom-everforest-padded-modeline) doom-everforest-padded-modeline 4))) 137 | 138 | (modeline-fg fg) 139 | (modeline-fg-alt fg-alt) 140 | 141 | (modeline-bg 142 | (if -modeline-bright 143 | (doom-darken blue 0.475) 144 | `(,(doom-darken (car bg-alt) 0.15) ,@(cdr base0)))) 145 | (modeline-bg-l 146 | (if -modeline-bright 147 | (doom-darken blue 0.45) 148 | `(,(doom-darken (car bg-alt) 0.1) ,@(cdr base0)))) 149 | (modeline-bg-inactive `(,(doom-darken (car bg-alt) 0.1) ,@(cdr bg-alt))) 150 | (modeline-bg-inactive-l `(,(car bg-alt) ,@(cdr base1)))) 151 | 152 | 153 | ;; --- extra faces ------------------------ 154 | ((elscreen-tab-other-screen-face :background "#353a42" :foreground "#1e2022") 155 | 156 | (evil-goggles-default-face :inherit 'region :background (doom-blend region bg 0.5)) 157 | 158 | ((line-number &override) :foreground base5) 159 | ((line-number-current-line &override) :foreground fg) 160 | 161 | (font-lock-comment-face 162 | :foreground comments 163 | :background (if doom-everforest-comment-bg (doom-lighten bg 0.05))) 164 | (font-lock-doc-face 165 | :inherit 'font-lock-comment-face 166 | :foreground doc-comments) 167 | 168 | (mode-line 169 | :background modeline-bg :foreground modeline-fg 170 | :box (if -modeline-pad `(:line-width ,-modeline-pad :color ,modeline-bg))) 171 | (mode-line-inactive 172 | :background modeline-bg-inactive :foreground modeline-fg-alt 173 | :box (if -modeline-pad `(:line-width ,-modeline-pad :color ,modeline-bg-inactive))) 174 | (mode-line-emphasis 175 | :foreground (if -modeline-bright base8 highlight)) 176 | 177 | (solaire-mode-line-face 178 | :inherit 'mode-line 179 | :background modeline-bg-l 180 | :box (if -modeline-pad `(:line-width ,-modeline-pad :color ,modeline-bg-l))) 181 | (solaire-mode-line-inactive-face 182 | :inherit 'mode-line-inactive 183 | :background modeline-bg-inactive-l 184 | :box (if -modeline-pad `(:line-width ,-modeline-pad :color ,modeline-bg-inactive-l))) 185 | 186 | ;; Doom modeline 187 | (doom-modeline-bar :background (if -modeline-bright modeline-bg highlight)) 188 | (doom-modeline-buffer-file :inherit 'mode-line-buffer-id :weight 'bold) 189 | (doom-modeline-buffer-path :inherit 'mode-line-emphasis :weight 'bold) 190 | (doom-modeline-buffer-project-root :foreground green :weight 'bold) 191 | 192 | ;; ivy-mode 193 | (ivy-current-match :background dark-blue :distant-foreground base0 :weight 'normal) 194 | 195 | ;; --- major-mode faces ------------------- 196 | ;; column indicator 197 | (fill-column-indicator :foreground bg-alt :background bg-alt) 198 | 199 | ;; css-mode / scss-mode 200 | (css-proprietary-property :foreground orange) 201 | (css-property :foreground green) 202 | (css-selector :foreground blue) 203 | 204 | ;; cursor 205 | (cursor :foreground fg :background blue) 206 | 207 | ;; dired 208 | (diredfl-compressed-file-name :height doom-everforest-dired-height 209 | :foreground yellow) 210 | (diredfl-dir-heading :height doom-everforest-dired-height 211 | :foreground teal) 212 | (diredfl-dir-name :height doom-everforest-dired-height 213 | :foreground blue) 214 | (diredfl-deletion :height doom-everforest-dired-height 215 | :foreground red :background (doom-lighten red 0.55)) 216 | (diredfl-deletion-file-name :foreground red 217 | :background (doom-lighten red 0.55)) 218 | (diredfl-file-name :height doom-everforest-dired-height 219 | :foreground fg) 220 | (dired-flagged :height doom-everforest-dired-height 221 | :foreground red :background (doom-lighten red 0.55)) 222 | (diredfl-symlink :height doom-everforest-dired-height 223 | :foreground magenta) 224 | 225 | ;; ein 226 | (ein:basecell-input-area-face :background bg) 227 | 228 | ;; eshell 229 | (+eshell-prompt-git-branch :foreground cyan) 230 | 231 | ;; evil 232 | (evil-ex-lazy-highlight :foreground fg :background (doom-darken orange 0.3)) 233 | (evil-snipe-first-match-face :foreground bg :background orange) 234 | 235 | 236 | ;; ivy 237 | (ivy-current-match :foreground blue :background bg) 238 | (ivy-minibuffer-match-face-2 :foreground blue :background bg) 239 | 240 | ;; LaTeX-mode 241 | (font-latex-math-face :foreground (doom-lighten green 0.15)) 242 | (font-latex-script-char-face :foreground (doom-lighten dark-blue 0.15)) 243 | 244 | ;; lsp 245 | (lsp-face-highlight-read :foreground fg-alt 246 | :background (doom-darken dark-blue 0.3)) 247 | (lsp-face-highlight-textual :foreground fg-alt 248 | :background (doom-lighten dark-blue 0.3)) 249 | (lsp-face-highlight-write :foreground fg-alt 250 | :background (doom-darken dark-blue 0.3)) 251 | (lsp-lsp-flycheck-info-unnecessary-face :foreground (doom-lighten yellow 0.12)) 252 | 253 | ;; magit 254 | (magit-section-heading :foreground blue :weight 'bold) 255 | 256 | ;; markdown-mode 257 | (markdown-markup-face :foreground base5) 258 | (markdown-header-face :inherit 'bold :foreground red) 259 | ((markdown-code-face &override) :background (doom-lighten base3 0.05)) 260 | 261 | ;; org-mode 262 | (org-hide :foreground hidden) 263 | (solaire-org-hide-face :foreground hidden) 264 | (org-drawer :foreground (doom-darken yellow 0.15)) 265 | (org-document-info :foreground blue) 266 | (org-document-info-keyword :foreground dark-blue) 267 | (org-document-title :foreground yellow :weight 'semi-bold :height 1.4) 268 | (org-block-begin-line :foreground dark-cyan 269 | :background bg-alt) 270 | (org-block-end-line :foreground dark-cyan 271 | :background bg-alt) 272 | (org-block :foreground fg :background bg-alt) 273 | (org-meta-line :foreground dark-cyan) 274 | (org-level-1 :foreground magenta :weight 'semi-bold :height 1.3) 275 | (org-level-2 :foreground cyan :weight 'semi-bold :height 1.2) 276 | (org-level-3 :foreground green :weight 'semi-bold :height 1.1) 277 | (org-level-4 :foreground yellow :weight 'semi-bold) 278 | (org-level-5 :foreground violet :weight 'semi-bold) 279 | (org-level-6 :foreground dark-cyan :weight 'semi-bold) 280 | (org-level-7 :foreground (doom-darken green 0.15) :weight 'semi-bold) 281 | (org-level-8 :foreground (doom-darken yellow 0.15) :weight 'semi-bold) 282 | 283 | ;; org-ref 284 | (org-ref-ref-face :foreground magenta) 285 | 286 | ;; org-roam 287 | (org-roam-title :foreground orange :weight 'semi-bold) 288 | 289 | ;; rainbow and parenthesis 290 | (rainbow-delimiters-depth-1-face :foreground orange) 291 | (rainbow-delimiters-depth-2-face :foreground violet) 292 | (rainbow-delimiters-depth-3-face :foreground dark-cyan) 293 | (rainbow-delimiters-depth-4-face :foreground (doom-darken yellow 0.15)) 294 | (rainbow-delimiters-unmatched-face: :foreground fg :background 'nil) 295 | (show-paren-match :foreground bg :background (doom-darken red 0.15)) 296 | 297 | ;; vertico 298 | (vertico-current :foreground fg :background (doom-lighten bg 0.1)) 299 | 300 | ;; others 301 | (isearch :foreground fg :background violet) 302 | (selection :foreground bg-alt :background (doom-darken orange 0.15)) 303 | (company-tooltip-common-selection :foreground bg-alt :background dark-blue) 304 | ) 305 | 306 | 307 | ;; --- extra variables --------------------- 308 | () 309 | ) 310 | 311 | ;;; doom-everforest-theme.el ends here 312 | -------------------------------------------------------------------------------- /emacs/.doom.d/config.org: -------------------------------------------------------------------------------- 1 | :PROPERTIES: 2 | :ID: 21f80d7d-00f7-4959-9ea2-d7e4b680b272 3 | :END: 4 | #+title: My Doom Emacs configuration 5 | #+startup: hideblocks content 6 | #+filetags: :compilation:tool:blogs: 7 | #+date: {{{modification-time(%Y-%m-%d)}}} 8 | #+latex_class: koma-article 9 | #+latex_compiler: xelatex 10 | #+latex_header: \usepackage{parskip} 11 | #+latex_header_extra: \usepackage{AlegreyaSans} 12 | #+latex_header_extra: \usepackage{libertinus} 13 | #+latex_header_extra: \usepackage[scale=0.80]{FiraMono} 14 | #+latex_header_extra: \addtokomafont{subsubsection}{\color{RoyalBlue!50!black}\AlegreyaSansMedium} 15 | #+latex_header_extra: \urlstyle{sf} 16 | #+latex_engraved_theme: doom-nord-light 17 | #+export_file_name: Doom-Emacs-config.md 18 | #+hugo_base_dir: ~/Dropbox/Blogs/hieutkt/ 19 | #+hugo_section: ./resources/ 20 | #+hugo_tags: Emacs 21 | #+hugo_url: /Doom-Emacs-config 22 | #+hugo_slug: Doom-Emacs-config 23 | #+hugo_custom_front_matter: 24 | #+hugo_draft: false 25 | #+options: toc:5 num:t H:5 26 | 27 | * Introduction :ignore: 28 | Emacs is certainly a strange piece of software. 29 | It was created in the 1970s and is still in active development until this day, preserving weird concepts like "buffers" and "frame" and "fontification" in its documentation. 30 | It calls the button =Ctrl= on modern keyboards as ~C~ and the =Alt= button as ~M~ (Meta). 31 | In default settings, you copy text by pressing ~M-w~ , "kill" (in modern language: cut) by ~C-w~, and paste by ~C-y~. 32 | In order to customize Emacs, you need to use a dedicated programming language called Emacs Lisp. 33 | 34 | But despite all that, Emacs has become the central piece of software that I use to interact with my computer. 35 | It's still just an text editor, but the one that you can spend hours to fine-tune it just the way you want it to be. 36 | In my journey to learn Emacs, I also learnt a lot about how my computer works. 37 | Along the way, I learnt how to code and I learnt how to write. 38 | These days, I learn about stuffs beyond the computer, yet Emacs is still my friend. 39 | 40 | This document describes how I set up my Emacs, in [[https://en.wikipedia.org/wiki/Literate_programming][literate programming]] style, using a plain text format closely related to Emacs called [[https://orgmode.org/][Org-mode]]. 41 | The whole thing is contained in a [[https://raw.githubusercontent.com/hieutkt/dotfiles/main/emacs/.doom.d/config.org][single file]], from which both the Elisp code and this HTML document is generated. 42 | This Emacs configuration is built based on a configuration framework called [[https://github.com/doomemacs/][Doom Emacs]], hence the name of this document. 43 | 44 | * Prerequisites 45 | ** Reproducible information 46 | This configuration is continuingly being improved. 47 | I build my own Emacs from source in order to take advantage of some experimental features. 48 | There are also =(packages! ...)= calls to external Emacs packages that are not pinned to any specific version. 49 | As such, there might be incompabilities if one blindly copies codes from this configurations. 50 | Although I'll try to document which features are based on developing softwares and are likely to be changed in the future, it is inevitable that some bits of information are going to fall through the cracks. 51 | 52 | In this section, I reiterate the relevant info about the version of the software I'm using here, in case someone finds this infomation useful. 53 | Here's my current build of Emacs: 54 | 55 | #+begin_src emacs-lisp :exports output :tangle no :eval t 56 | (emacs-version) 57 | #+end_src 58 | 59 | #+RESULTS: 60 | : GNU Emacs 29.1 (build 1, x86_64-pc-linux-gnu, GTK+ Version 3.24.38, cairo version 1.17.8) 61 | : of 2023-07-30 62 | 63 | This Emacs is built with the following configuration options: 64 | 65 | #+begin_src emacs-lisp :exports output :tangle no :eval t 66 | system-configuration-options 67 | #+end_src 68 | 69 | #+RESULTS: 70 | : --with-modules --with-json --with-mailutils --with-rsvg --with-native-compilation --with-xinput2 --with-gif --with-pgtk --with-tree-sitter 71 | 72 | #+begin_src emacs-lisp :exports output :tangle no :eval t 73 | system-configuration-features 74 | #+end_src 75 | 76 | #+RESULTS: 77 | : ACL CAIRO DBUS FREETYPE GIF GLIB GMP GNUTLS GPM GSETTINGS HARFBUZZ JPEG JSON LCMS2 LIBSYSTEMD LIBXML2 MODULES NATIVE_COMP NOTIFY INOTIFY PDUMPER PGTK PNG RSVG SECCOMP SOUND SQLITE3 THREADS TIFF TOOLKIT_SCROLL_BARS TREE_SITTER WEBP XIM GTK3 ZLIB 78 | 79 | * Fundamental setups 80 | ** Some good defaults 81 | 82 | #+begin_src emacs-lisp 83 | ;; Some functionality uses this to identify you, e.g. GPG configuration, email 84 | ;; clients, file templates and snippets. 85 | (setq user-full-name "Hieu Phay" 86 | user-mail-address "hieunguyen31371@gmail.com" 87 | default-input-method 'vietnamese-telex 88 | +doom-dashboard-banner-dir doom-user-dir 89 | +doom-dashboard-banner-file "favicon-pixel.png" 90 | +doom-dashboard-banner-padding '(0 . 2)) 91 | 92 | ;; Turn on abbrev mode 93 | (setq-default abbrev-mode t) 94 | 95 | ;; Start Doom fullscreen 96 | (add-to-list 'default-frame-alist '(width . 92)) 97 | (add-to-list 'default-frame-alist '(height . 40)) 98 | ;; (add-to-list 'default-frame-alist '(alpha 97 100)) 99 | 100 | ;; If you use `org' and don't want your org files in the default location below, 101 | ;; change `org-directory'. It must be set before org loads! 102 | (if (and (string-match-p "Windows" (getenv "PATH")) (not IS-WINDOWS)) 103 | (setq dropbox-directory "/mnt/c/Users/X380/Dropbox/") 104 | (setq dropbox-directory "~/Dropbox/")) 105 | 106 | (setq org-directory (concat dropbox-directory "Notes/")) 107 | 108 | 109 | ;; This determines the style of line numbers in effect. If set to `nil', line 110 | ;; numbers are disabled. For relative line numbers, set this to `relative'. 111 | (setq display-line-numbers-type 'relative) 112 | (remove-hook! '(text-mode-hook) #'display-line-numbers-mode) 113 | 114 | (setq frame-title-format 115 | '("" 116 | (:eval 117 | (if (s-contains-p org-roam-directory (or buffer-file-name "")) 118 | (replace-regexp-in-string 119 | ".*/[0-9]*-?" "☰ " 120 | (subst-char-in-string ?_ ? buffer-file-name)) 121 | "%b")) 122 | (:eval 123 | (let ((project-name (projectile-project-name))) 124 | (unless (string= "-" project-name) 125 | (format (if (buffer-modified-p) " ◉ %s" "  ●  %s") project-name)))))) 126 | #+end_src 127 | 128 | ** Theme 129 | I have done a fair share of theme-hopping. In the end, I always come back to a variant of the [[https://github.com/morhetz/gruvbox][Gruvbox color scheme]]. 130 | If you are viewing this on my website, you may find that this color scheme is ubiquitous here. 131 | 132 | #+begin_src emacs-lisp 133 | ;; The custom doom-everforest theme is a green-accented variant of gruvbox-material 134 | (setq doom-theme 'doom-gruvbox) 135 | 136 | (use-package! doom-modeline 137 | :config 138 | (setq doom-modeline-persp-name t)) 139 | #+end_src 140 | 141 | ** Font configs 142 | *** Font choices 143 | [[https://typeof.net/Iosevka/][Iosevka]] is a great font with good coverage (excellent if you count its extension Sarasa Gothic). 144 | The narrow glyphs allow us to save some precious screen real estate. 145 | This is particularly useful for multitasking with multiple windows open. 146 | For example, my notetaking workflow involved having a small (not maximized) Emacs window, along with one or several windows for pdf viewers, often on a 13-inch laptop screen. 147 | You can see the benefit here. 148 | I cannot go back to non-narrow fonts anymore. 149 | 150 | It's even better that it allows me to cherry-pick glyphs that I like (or don't like). 151 | My customized Iosevka is based on the Ubuntu Mono style variant (SS12). 152 | This style brings me that nostalgic feel of my first linux distribution. 153 | The underscore =_= is more pronounced, which I like. 154 | The stylized letters (e.g. see =l=, =m=, =n=, =i=, =j=,...) bring forth a humanist, comfy yet quirky aesthetic. 155 | 156 | Below is my =private-build-plans.toml=, made with this [[https://typeof.net/Iosevka/customizer][lovely customizer]]. 157 | The font compilation takes quite a while, though. 158 | Make sure to consult with the [[https://github.com/be5invis/Iosevka/blob/main/doc/custom-build.md][instructions]]: 159 | 160 | #+begin_src toml :tangle no 161 | [buildPlans.iosevka-custom] 162 | family = "Iosevka Custom" 163 | spacing = "normal" 164 | serifs = "sans" 165 | noCvSs = true 166 | export-glyph-names = false 167 | 168 | [buildPlans.iosevka-custom.variants] 169 | inherits = "ss12" 170 | 171 | [buildPlans.iosevka-custom.variants.design] 172 | v = "straight-serifed" 173 | lower-alpha = "crossing" 174 | capital-gamma = "top-right-serifed" 175 | zero = "dotted" 176 | ampersand = "et-toothed" 177 | lig-ltgteq = "slanted" 178 | 179 | [buildPlans.iosevka-custom.ligations] 180 | inherits = "julia" 181 | #+end_src 182 | 183 | *** Setups 184 | Now to set all this up: 185 | 186 | #+begin_src emacs-lisp 187 | (when (doom-font-exists-p "Iosevka Custom") 188 | (setq doom-font (font-spec :name "Iosevka Custom" :size 14))) 189 | (when (doom-font-exists-p "Alegreya Sans") 190 | (setq doom-variable-pitch-font (font-spec :name "Alegreya Sans" :size 16))) 191 | (when (doom-font-exists-p "Noto Color Emoji") 192 | (setq doom-emoji-font (font-spec :name "Noto Color Emoji"))) 193 | (when (doom-font-exists-p "Iosevka Custom") 194 | (setq doom-symbol-font (font-spec :name "Iosevka Custom"))) 195 | #+end_src 196 | 197 | Fallback font for non-ascii glyphs: 198 | 199 | #+begin_src emacs-lisp 200 | (use-package! unicode-fonts 201 | :config 202 | ;; Common math symbols 203 | (dolist (unicode-block '("Mathematical Alphanumeric Symbols")) 204 | (push "JuliaMono" (cadr (assoc unicode-block unicode-fonts-block-font-mapping)))) 205 | (dolist (unicode-block '("Greek and Coptic")) 206 | (push "Iosevka Custom" (cadr (assoc unicode-block unicode-fonts-block-font-mapping)))) 207 | ;; CJK characters 208 | (dolist (unicode-block '("CJK Unified Ideographs" "CJK Symbols and Punctuation" "CJK Radicals Supplement" "CJK Compatibility Ideographs")) 209 | (push "Sarasa Mono SC" (cadr (assoc unicode-block unicode-fonts-block-font-mapping)))) 210 | (dolist (unicode-block '("Hangul Syllables" "Hangul Jamo Extended-A" "Hangul Jamo Extended-B")) 211 | (push "Sarasa Mono K" (cadr (assoc unicode-block unicode-fonts-block-font-mapping)))) 212 | ;; Emojis 213 | (dolist (unicode-block '("Miscellaneous Symbols")) 214 | (push "Noto Color Emoji" (cadr (assoc unicode-block unicode-fonts-block-font-mapping)))) 215 | ;; Other unicode block 216 | (dolist (unicode-block '("Braille Patterns")) 217 | (push "Iosevka Custom" (cadr (assoc unicode-block unicode-fonts-block-font-mapping)))) 218 | ) 219 | #+end_src 220 | 221 | *** Ligatures 222 | Emacs (since version 28 I think) handles ligatures pretty well. 223 | However, sometimes we still need to manually fix some ligature composition: 224 | 225 | #+begin_src emacs-lisp :tangle no 226 | ;; For Iosevka 227 | ;; (set-char-table-range composition-function-table ?+ '(["\\(?:+[\\*]\\)" 0 font-shape-gstring])) 228 | (set-char-table-range composition-function-table ?* '(["\\(?:\\*?[=+>]\\)" 0 font-shape-gstring])) 229 | ;; (set-char-table-range composition-function-table ?= '(["\\(?:=?[=\\*]\\)" 0 font-shape-gstring])) 230 | ;; (set-char-table-range composition-function-table ?= '(["\\(?:=?[\\*:]\\)" 0 font-shape-gstring])) 231 | ;; (set-char-table-range composition-function-table ?: '(["\\(?::=\\)" 0 font-shape-gstring])) 232 | ;; For Alegreya/Alegreya Sans 233 | (set-char-table-range composition-function-table ?f '(["\\(?:ff?[fijltkbh]\\)" 0 font-shape-gstring])) 234 | ;; (set-char-table-range composition-function-table ?T '(["\\(?:Th\\)" 0 font-shape-gstring])) 235 | #+end_src 236 | 237 | *** Mixed- and fixed-pitch fonts 238 | We should take care of =mixed-pitch-mode= here, too: 239 | 240 | #+begin_src emacs-lisp 241 | (use-package! mixed-pitch 242 | :hook ((org-mode . mixed-pitch-mode) 243 | (org-roam-mode . mixed-pitch-mode) 244 | (LaTeX-mode . mixed-pitch-mode)) 245 | :config 246 | (pushnew! mixed-pitch-fixed-pitch-faces 247 | 'warning 248 | 'org-drawer 'org-cite-key 'org-list-dt 'org-hide 249 | 'corfu-default 'font-latex-math-face) 250 | (setq mixed-pitch-set-height t)) 251 | #+end_src 252 | ** Icons 253 | Some nerd-icons related stuffs 254 | 255 | #+begin_src emacs-lisp 256 | (use-package! nerd-icons-ibuffer 257 | :ensure t 258 | :hook (ibuffer-mode . nerd-icons-ibuffer-mode)) 259 | 260 | ;; (use-package! magit-file-icons 261 | ;; :init 262 | ;; (magit-file-icons-mode 1)) 263 | #+end_src 264 | 265 | ** Slightly transparent Emacs 266 | Emacs version 29 added a new frame parameter for "true" transparency, which means that only the blackground is transparent while the text is not. 267 | 268 | #+begin_src emacs-lisp :tangle no 269 | (add-to-list 'default-frame-alist '(alpha-background . 96)) 270 | #+end_src 271 | 272 | I set Emacs to be slightly transparent. 273 | With this setting, I can put Emacs at full screen while still being able to read from the windows behind it. 274 | This is very useful when screen real-estate is scarce (which is always the case!) 275 | ** Modeline 276 | Some tweaks to =doom-modeline=: 277 | #+begin_src emacs-lisp 278 | (setq doom-modeline-height 35) 279 | #+end_src 280 | 281 | Show page number when viewing PDFs: 282 | 283 | #+begin_src emacs-lisp 284 | (doom-modeline-def-segment buffer-name 285 | "Display the current buffer's name, without any other information." 286 | (concat 287 | doom-modeline-spc 288 | (doom-modeline--buffer-name))) 289 | 290 | (doom-modeline-def-segment pdf-icon 291 | "PDF icon from nerd-icons." 292 | (concat 293 | doom-modeline-spc 294 | (doom-modeline-icon 'mdicon "nf-md-file_pdf_box" nil nil 295 | :face (if (doom-modeline--active) 296 | 'nerd-icons-red 297 | 'mode-line-inactive)))) 298 | 299 | (defun doom-modeline-update-pdf-pages () 300 | "Update PDF pages." 301 | (setq doom-modeline--pdf-pages 302 | (let ((current-page-str (number-to-string (eval `(pdf-view-current-page)))) 303 | (total-page-str (number-to-string (pdf-cache-number-of-pages)))) 304 | (concat 305 | (propertize 306 | (concat (make-string (- (length total-page-str) (length current-page-str)) ? ) 307 | " P" current-page-str) 308 | 'face 'mode-line) 309 | (propertize (concat "/" total-page-str) 'face 'doom-modeline-buffer-minor-mode))))) 310 | 311 | (doom-modeline-def-segment pdf-pages 312 | "Display PDF pages." 313 | (if (doom-modeline--active) doom-modeline--pdf-pages 314 | (propertize doom-modeline--pdf-pages 'face 'mode-line-inactive))) 315 | 316 | (doom-modeline-def-modeline 'pdf 317 | '(bar window-number pdf-pages pdf-icon buffer-name) 318 | '(misc-info matches major-mode process vcs)) 319 | #+end_src 320 | 321 | Recent version of [[https://github.com/seagle0128/doom-modeline/pull/622][doom-modeline]] features [[github:rainstormstudio/nerd-icons.el][nerd-icons.el]] instead of [[github:domtronn/all-the-icons.el][all-the-icons.el]]. 322 | I like this change, however different parts of Doom are still using =all-the-icons= under the hood. 323 | Some custom configurations is needed for now. 324 | 325 | #+begin_src emacs-lisp 326 | (use-package! nerd-icons 327 | :custom 328 | ;; (nerd-icons-font-family "Iosevka Nerd Font Mono") 329 | ;; (nerd-icons-scale-factor 2) 330 | ;; (nerd-icons-default-adjust -.075) 331 | (doom-modeline-major-mode-icon t)) 332 | #+end_src 333 | 334 | ** Narrowing and center buffer contents 335 | On larger screens I like buffer contents to not exceed a certain width and are centered. 336 | =olivetti-mode= solves this problem nicely. 337 | There is also an =auto-olivetti-mode= which automatically turns on =olivetti-mode= in most buffers. 338 | 339 | #+begin_src emacs-lisp 340 | (use-package! olivetti 341 | :config 342 | (setq-default olivetti-body-width 130) 343 | (add-hook 'mixed-pitch-mode-hook (lambda () (setq-local olivetti-body-width 90)))) 344 | 345 | (use-package! auto-olivetti 346 | :custom 347 | (auto-olivetti-enabled-modes '(text-mode prog-mode helpful-mode ibuffer-mode image-mode)) 348 | :config 349 | (auto-olivetti-mode)) 350 | #+end_src 351 | ** Git gutter 352 | The =diff= changes are reflected in the left fringe. 353 | However, I find them to be a little bit too intrusive, so let's change how they looks by blending the colors into the background a little bit 354 | 355 | #+begin_src emacs-lisp 356 | (use-package! diff-hl 357 | :config 358 | (custom-set-faces! 359 | `((diff-hl-change) 360 | :foreground ,(doom-blend (doom-color 'bg) (doom-color 'blue) 0.5)) 361 | `((diff-hl-insert) 362 | :foreground ,(doom-blend (doom-color 'bg) (doom-color 'green) 0.5))) 363 | ) 364 | #+end_src 365 | ** Alignment in popup fix (=which-key= and =marginalia=) 366 | The default character for ellipsis breaks alignment in =which-key= tables, so let's fix that 367 | 368 | #+begin_src emacs-lisp 369 | (use-package! which-key 370 | :init 371 | (setq which-key-ellipsis "...")) 372 | 373 | #+end_src 374 | 375 | Similarly for marginalia 376 | 377 | #+begin_src emacs-lisp 378 | (setq marginalia--ellipsis "...") 379 | #+end_src 380 | ** Precision scrolling 381 | 382 | Turn on pixel scrolling: 383 | 384 | #+begin_src emacs-lisp 385 | (pixel-scroll-precision-mode t) 386 | #+end_src 387 | 388 | The following code replace all scrolling and recenter commands with the precision-scrolling version. 389 | 390 | #+begin_src emacs-lisp 391 | (defun hp/pixel-recenter (&optional arg redisplay) 392 | "Similar to `recenter' but with pixel scrolling. 393 | ARG and REDISPLAY are identical to the original function." 394 | ;; See the links in line 6676 in window.c for 395 | (when-let* ((current-pixel (pixel-posn-y-at-point)) 396 | (target-pixel (if (numberp arg) 397 | (* (line-pixel-height) arg) 398 | (* 0.5 (window-body-height nil t)))) 399 | (distance-in-pixels 0) 400 | (pixel-scroll-precision-interpolation-total-time 401 | (/ pixel-scroll-precision-interpolation-total-time 2.0))) 402 | (setq target-pixel 403 | (if (<= 0 target-pixel) 404 | target-pixel 405 | (- (window-body-height nil t) (abs target-pixel)))) 406 | (setq distance-in-pixels (- target-pixel current-pixel)) 407 | (condition-case err 408 | (pixel-scroll-precision-interpolate distance-in-pixels nil 1) 409 | (error (message "[hp/pixel-recenter] %s" (error-message-string err)))) 410 | (when redisplay (redisplay t)))) 411 | 412 | (defun hp/pixel-scroll-up (&optional arg) 413 | "(Nearly) drop-in replacement for `scroll-up'." 414 | (cond 415 | ((eq this-command 'scroll-up-line) 416 | (funcall (ad-get-orig-definition 'scroll-up) (or arg 1))) 417 | (t 418 | (unless (eobp) ; Jittery window if trying to go down when already at bottom 419 | (pixel-scroll-precision-interpolate 420 | (- (* (line-pixel-height) 421 | (or arg (- (window-text-height) next-screen-context-lines)))) 422 | nil 1))))) 423 | 424 | (defun hp/pixel-scroll-down (&optional arg) 425 | "(Nearly) drop-in replacement for `scroll-down'." 426 | (cond 427 | ((eq this-command 'scroll-down-line) 428 | (funcall (ad-get-orig-definition 'scroll-down) (or arg 1))) 429 | (t 430 | (pixel-scroll-precision-interpolate 431 | (* (line-pixel-height) 432 | (or arg (- (window-text-height) next-screen-context-lines))) 433 | nil 1)))) 434 | 435 | (add-hook 'pixel-scroll-precision-mode-hook 436 | (lambda () 437 | (cond 438 | (pixel-scroll-precision-mode 439 | (advice-add 'scroll-up :override 'hp/pixel-scroll-up) 440 | (advice-add 'scroll-down :override 'hp/pixel-scroll-down) 441 | (advice-add 'recenter :override 'hp/pixel-recenter)) 442 | (t 443 | (advice-remove 'scroll-up 'hp/pixel-scroll-up) 444 | (advice-remove 'scroll-down 'hp/pixel-scroll-down) 445 | (advice-remove 'recenter 'hp/pixel-recenter))))) 446 | #+end_src 447 | 448 | * Editing configurations 449 | ** Handy functions 450 | The =hp/fill-to-end= function function in Emacs Lisp fills the remaining space on the current line with a specified character until the line reaches the column defined by =fill-column=. 451 | When executed interactively, the function prompts the user to input the character to be used for filling. 452 | The cursor position is temporarily saved and restored to its original location after the line is filled. 453 | 454 | #+begin_src emacs-lisp 455 | (defun hp/fill-to-end (char) 456 | (interactive "cFill Character:") 457 | (save-excursion 458 | (end-of-line) 459 | (while (< (current-column) fill-column) 460 | (insert-char char)))) 461 | #+end_src 462 | 463 | ** Evil 464 | #+begin_src emacs-lisp 465 | (use-package! evil 466 | :init 467 | (setq evil-move-beyond-eol t 468 | evil-move-cursor-back nil)) 469 | 470 | (use-package! evil-escape 471 | :config 472 | (setq evil-esc-delay 0.25)) 473 | 474 | (use-package! evil-vimish-fold 475 | :config 476 | (global-evil-vimish-fold-mode)) 477 | 478 | (use-package! evil-goggles 479 | :init 480 | (setq evil-goggles-enable-change t 481 | evil-goggles-enable-delete t 482 | evil-goggles-pulse t 483 | evil-goggles-duration 0.5) 484 | :config 485 | (custom-set-faces! 486 | `((evil-goggles-yank-face evil-goggles-surround-face) 487 | :background ,(doom-blend (doom-color 'blue) (doom-color 'bg-alt) 0.5) 488 | :extend t) 489 | `(evil-goggles-paste-face 490 | :background ,(doom-blend (doom-color 'green) (doom-color 'bg-alt) 0.5) 491 | :extend t) 492 | `(evil-goggles-delete-face 493 | :background ,(doom-blend (doom-color 'red) (doom-color 'bg-alt) 0.5) 494 | :extend t) 495 | `(evil-goggles-change-face 496 | :background ,(doom-blend (doom-color 'orange) (doom-color 'bg-alt) 0.5) 497 | :extend t) 498 | `(evil-goggles-commentary-face 499 | :background ,(doom-blend (doom-color 'grey) (doom-color 'bg-alt) 0.5) 500 | :extend t) 501 | `((evil-goggles-indent-face evil-goggles-join-face evil-goggles-shift-face) 502 | :background ,(doom-blend (doom-color 'yellow) (doom-color 'bg-alt) 0.25) 503 | :extend t) 504 | )) 505 | #+end_src 506 | *** Hack: load evil keybindings 507 | For some reasons =evil= keybindings are usually not loaded along with emacs. 508 | The simple solution is forcing emacs to load this file. 509 | 510 | #+begin_src emacs-lisp 511 | (defun hp/load-evil-keybindings () 512 | (interactive) 513 | (load-file "~/.config/emacs/modules/config/default/+evil-bindings.el")) 514 | 515 | (add-hook 'doom-after-init-hook #'hp/load-evil-keybindings) 516 | #+end_src 517 | 518 | ** Completions 519 | *** Enable corfu in the minibuffer 520 | Having completion in the minibuffer is useful for when you want to run small elisp command to temporary modify the state of Emacs. 521 | This has been getting more and more useful the longer I have been using Emacs. 522 | 523 | #+begin_src emacs-lisp 524 | (use-package! corfu 525 | :config 526 | (defun corfu-enable-in-minibuffer () 527 | "Enable Corfu in the minibuffer if `completion-at-point' is bound." 528 | (when (where-is-internal #'completion-at-point (list (current-local-map))) 529 | ;; (setq-local corfu-auto nil) ;; Enable/disable auto completion 530 | (setq-local corfu-echo-delay nil ;; Disable automatic echo and popup 531 | corfu-popupinfo-delay nil) 532 | (corfu-mode 1))) 533 | (add-hook 'minibuffer-setup-hook #'corfu-enable-in-minibuffer)) 534 | #+end_src 535 | 536 | *** Narrow down queries for non-ASCII characters 537 | Sometimes my queries return results in Vietnamese which include letters with diacritics (e.g. =ă= or =đ= or =ê=). 538 | In these cases, I want to be able to narrow the search down by typing their ASCII equivalents (e.g. =a= or =d= or =e=). 539 | The implementation is simple: set matching styles in =orderless.el= to include the function =char-fold-to-regexp=. 540 | 541 | #+begin_src emacs-lisp 542 | (use-package! orderless 543 | :config 544 | (add-to-list 'orderless-matching-styles 'char-fold-to-regexp)) 545 | #+end_src 546 | 547 | *** Smaller popup text 548 | Automatic documentation popup while autocompleting is nice, but let's reduce the font size a little bit so that it doesn't cover the screen too much and makes it easier to skim for information: 549 | 550 | #+begin_src emacs-lisp :tangle no 551 | (custom-set-faces! '((corfu-popupinfo) :height 0.9)) 552 | #+end_src 553 | 554 | *** Icons 555 | Kind-icon adds icons to =corfu= completions based on the =:company-kind= property. 556 | Let's add this properties to those that don't provide them. 557 | 558 | #+begin_src emacs-lisp 559 | (after! org-roam 560 | ;; Define advise 561 | (defun hp/org-roam-capf-add-kind-property (orig-fun &rest args) 562 | "Advice around `org-roam-complete-link-at-point' to add :company-kind property." 563 | (let ((result (apply orig-fun args))) 564 | (append result '(:company-kind (lambda (_) 'org-roam))))) 565 | ;; Wraps around the relevant functions 566 | (advice-add 'org-roam-complete-link-at-point :around #'hp/org-roam-capf-add-kind-property) 567 | (advice-add 'org-roam-complete-everywhere :around #'hp/org-roam-capf-add-kind-property)) 568 | 569 | (after! citar 570 | ;; Define advise 571 | (defun hp/citar-capf-add-kind-property (orig-fun &rest args) 572 | "Advice around `org-roam-complete-link-at-point' to add :company-kind property." 573 | (let ((result (apply orig-fun args))) 574 | (append result '(:company-kind (lambda (_) 'reference))))) 575 | ;; Wraps around the relevant functions 576 | (advice-add 'citar-capf :around #'hp/citar-capf-add-kind-property)) 577 | #+end_src 578 | 579 | Now, we can implement custom icons for Org-roam completions: 580 | 581 | #+begin_src emacs-lisp 582 | (after! (org-roam nerd-icons-corfu) 583 | (add-to-list 584 | 'nerd-icons-corfu-mapping 585 | '(org-roam :style "cod" :icon "notebook" :face font-lock-type-face))) 586 | #+end_src 587 | 588 | ** Language server protocol (LSP) 589 | #+begin_src emacs-lisp 590 | (use-package! lsp-ui 591 | :config 592 | (setq lsp-ui-doc-delay 2 593 | lsp-ui-doc-max-width 80) 594 | (setq lsp-signature-function 'lsp-signature-posframe)) 595 | #+end_src 596 | 597 | ** Yasnippet 598 | 599 | #+begin_src emacs-lisp 600 | (use-package! yasnippet 601 | :config 602 | ;; It will test whether it can expand, if yes, change cursor color 603 | (defun hp/change-cursor-color-if-yasnippet-can-fire (&optional field) 604 | (interactive) 605 | (setq yas--condition-cache-timestamp (current-time)) 606 | (let (templates-and-pos) 607 | (unless (and yas-expand-only-for-last-commands 608 | (not (member last-command yas-expand-only-for-last-commands))) 609 | (setq templates-and-pos (if field 610 | (save-restriction 611 | (narrow-to-region (yas--field-start field) 612 | (yas--field-end field)) 613 | (yas--templates-for-key-at-point)) 614 | (yas--templates-for-key-at-point)))) 615 | (set-cursor-color (if (and templates-and-pos (first templates-and-pos) 616 | (eq evil-state 'insert)) 617 | (doom-color 'red) 618 | (face-attribute 'default :foreground))))) 619 | :hook (post-command . hp/change-cursor-color-if-yasnippet-can-fire)) 620 | #+end_src 621 | 622 | ** Citations 623 | 624 | #+begin_src emacs-lisp 625 | (use-package! citar 626 | :hook 627 | (LaTeX-mode . citar-capf-setup) 628 | (org-mode . citar-capf-setup) 629 | :config 630 | (setq 631 | citar-bibliography (list (concat org-directory "/References/zotero-bibtex.bib")) 632 | citar-notes-paths (list(concat org-directory "/Org-roam/literature/")) 633 | citar-library-paths (list (concat org-directory "/Org-roam/")) 634 | citar-file-variable "file" 635 | citar-symbol-separator " " 636 | ;; The global bibliography source may be set to something, 637 | ;; but now let's set it on a per-file basis 638 | org-cite-global-bibliography citar-bibliography 639 | ) 640 | ;; Search contents of PDFs 641 | (after! (embark pdf-occur) 642 | (defun citar/search-pdf-contents (keys-entries &optional str) 643 | "Search pdfs." 644 | (interactive (list (citar-select-refs))) 645 | (let ((files (citar-file--files-for-multiple-entries 646 | (citar--ensure-entries keys-entries) 647 | citar-library-paths 648 | '("pdf"))) 649 | (search-str (or str (read-string "Search string: ")))) 650 | (pdf-occur-search files search-str t))) 651 | ;; with this, you can exploit embark's multitarget actions, so that you can run `embark-act-all` 652 | (add-to-list 'embark-multitarget-actions #'citar/search-pdf-contents))) 653 | #+end_src 654 | ** Workspaces 655 | #+begin_src emacs-lisp 656 | (defadvice! hp/config-in-its-own-workspace (&rest _) 657 | "Open Elfeeds in its own workspace." 658 | :before #'doom/find-file-in-private-config 659 | (when (modulep! :ui workspaces) 660 | (+workspace-switch "Configs" t))) 661 | #+end_src 662 | ** LLM 663 | This is for =gptel-rewrite=: 664 | 665 | #+begin_src emacs-lisp 666 | (use-package! gptel-rewrite 667 | :config 668 | ;; Use the following code to adjust the gptel rewrite overlay to have a tint of 'doom-blue color: 669 | (custom-set-faces! 670 | `(gptel-rewrite-highlight-face 671 | :background ,(doom-blend (doom-color 'teal) (doom-color 'bg) 0.1)))) 672 | 673 | ;; Updated version available at https://github.com/karthink/gptel/wiki/gptel%E2%80%90rewrite-addons 674 | (use-package! gptel-rewrite 675 | :after gptel 676 | :bind (:map gptel-rewrite-actions-map 677 | ("C-c C-i" . gptel--rewrite-inline-diff)) 678 | :config 679 | (defun gptel--rewrite-inline-diff (&optional ovs) 680 | "Start an inline-diff session on OVS." 681 | (interactive (list (gptel--rewrite-overlay-at))) 682 | (unless (require 'inline-diff nil t) 683 | (user-error "Inline diffs require the inline-diff package.")) 684 | (when-let* ((ov-buf (overlay-buffer (or (car-safe ovs) ovs))) 685 | ((buffer-live-p ov-buf))) 686 | (with-current-buffer ov-buf 687 | (cl-loop for ov in (ensure-list ovs) 688 | for ov-beg = (overlay-start ov) 689 | for ov-end = (overlay-end ov) 690 | for response = (overlay-get ov 'gptel-rewrite) 691 | do (delete-overlay ov) 692 | (inline-diff-words 693 | ov-beg ov-end response))))) 694 | (when (boundp 'gptel--rewrite-dispatch-actions) 695 | (add-to-list 696 | 'gptel--rewrite-dispatch-actions '(?i "inline-diff") 697 | 'append))) 698 | 699 | (use-package! inline-diff 700 | :after gptel-rewrite ;or use :defer 701 | :bind 702 | (:map inline-diff-overlay-map 703 | ("M-a" . inline-diff-apply) 704 | ("M-k" . inline-diff-reject))) 705 | #+end_src 706 | 707 | * Major modes and language-specific configurations 708 | ** Org-mode 709 | I came to Emacs for coding, but eventually what kept me using it is Org-mode. 710 | In fact, I spend most of my time in an Org-mode buffer. 711 | It's just that good. 712 | *** Basics 713 | 714 | #+begin_src emacs-lisp 715 | (use-package! org 716 | :config 717 | (setq org-highlight-links 718 | '(bracket angle plain tag date footnote) 719 | org-image-align 'center) 720 | ;; Setup custom links 721 | (+org-init-custom-links-h)) 722 | #+end_src 723 | 724 | Need to check if ellipsis icon works properly before committing: 725 | 726 | #+begin_src emacs-lisp :tangle no 727 | (after! (org nerd-icons) 728 | (setq org-ellipsis "")) 729 | #+end_src 730 | 731 | *** Org-tempo 732 | #+begin_src emacs-lisp 733 | (use-package! org-tempo 734 | :after org 735 | :config 736 | ;;Hugo shortcodes 737 | (tempo-define-template 738 | "Hugo info" '("#+attr_shortcode: info\n#+begin_notice\n" p "\n#+end_notice">) 739 | ") 742 | ") 745 | ") 748 | ") 751 | ") 754 | " 1015 | #+end_example 1016 | 1017 | Or run this command: 1018 | 1019 | #+begin_example bash 1020 | sed -i '/disable ghostscript format types/,+6d' /etc/ImageMagick-6/policy.xml 1021 | #+end_example 1022 | 1023 | With all that set up, let's configure =org-latex-preview=: 1024 | 1025 | #+begin_src emacs-lisp 1026 | (use-package! org-latex-preview 1027 | :after org 1028 | :hook ((org-mode . org-latex-preview-mode)) 1029 | :config 1030 | ;; Block C-n and C-p from opening up previews when using auto-mode 1031 | (add-hook 'org-latex-preview-auto-ignored-commands 'next-line) 1032 | (add-hook 'org-latex-preview-auto-ignored-commands 'previous-line) 1033 | ;; Ignored faces 1034 | (pushnew! org-latex-preview--ignored-faces 'org-list-dt 'fixed-pitch) 1035 | 1036 | ;; Enable consistent equation numbering 1037 | (setq org-latex-preview-numbered t 1038 | org-startup-with-latex-preview t 1039 | org-latex-preview-live t 1040 | org-latex-preview-preamble "\\documentclass{article} 1041 | [DEFAULT-PACKAGES] 1042 | [PACKAGES] 1043 | \\usepackage[dvipsnames,svgnames]{xcolor} 1044 | \\usepackage[sfdefault]{AlegreyaSans} 1045 | \\usepackage{newtxsf} 1046 | \\definecolor{DarkRed}{RGB}{204,36,29} 1047 | \\definecolor{ForestGreen}{RGB}{184,187,38} 1048 | \\definecolor{red}{RGB}{251,73,52} 1049 | \\definecolor{orange}{RGB}{254,128,25} 1050 | \\definecolor{blue}{RGB}{69,133,136} 1051 | \\definecolor{green}{RGB}{184,187,38} 1052 | \\definecolor{yellow}{RGB}{250, 189, 47} 1053 | \\definecolor{purple}{RGB}{211, 134, 155}")) 1054 | #+end_src 1055 | 1056 | **** Transparent background for org-block 1057 | However, by using native highlighting the org-block face is added, and that doesn’t look too great — particularly when the fragments are previewed. 1058 | Ideally =org-src-font-lock-fontify-block= wouldn’t add the =org-block= face, but we can avoid advising that entire function by just adding another face with =:inherit default= which will override the background colour. 1059 | 1060 | #+begin_src emacs-lisp 1061 | (after! org-src 1062 | (add-to-list 'org-src-block-faces '("latex" (:inherit default :extend t)))) 1063 | #+end_src 1064 | **** Ugly patch for Ox-hugo export 1065 | 1066 | #+begin_src emacs-lisp :tangle no 1067 | (defun org-html-format-latex (latex-frag processing-type info) 1068 | "Format a LaTeX fragment LATEX-FRAG into HTML. 1069 | PROCESSING-TYPE designates the tool used for conversion. It can 1070 | be `mathjax', `verbatim', `html', nil, t or symbols in 1071 | `org-preview-latex-process-alist', e.g., `dvipng', `dvisvgm' or 1072 | `imagemagick'. See `org-html-with-latex' for more information. 1073 | INFO is a plist containing export properties." 1074 | (let ((cache-relpath "") (cache-dir "")) 1075 | (unless (or (eq processing-type 'mathjax) 1076 | (eq processing-type 'html)) 1077 | (let ((bfn (or (buffer-file-name) 1078 | (make-temp-name 1079 | (expand-file-name "latex" temporary-file-directory)))) 1080 | (latex-header 1081 | (let ((header (plist-get info :latex-header))) 1082 | (and header 1083 | (concat (mapconcat 1084 | (lambda (line) (concat "#+LATEX_HEADER: " line)) 1085 | (org-split-string header "\n") 1086 | "\n") 1087 | "\n"))))) 1088 | (setq cache-relpath 1089 | (concat (file-name-as-directory org-preview-latex-image-directory) 1090 | (file-name-sans-extension 1091 | (file-name-nondirectory bfn))) 1092 | cache-dir (file-name-directory bfn)) 1093 | ;; Re-create LaTeX environment from original buffer in 1094 | ;; temporary buffer so that dvipng/imagemagick can properly 1095 | ;; turn the fragment into an image. 1096 | (setq latex-frag (concat latex-header latex-frag)))) 1097 | (with-temp-buffer 1098 | (insert latex-frag) 1099 | (org-format-latex cache-relpath nil nil cache-dir nil 1100 | "Creating LaTeX Image..." nil processing-type) 1101 | (buffer-string)))) 1102 | #+end_src 1103 | **** Ugly patch =--bbox=preview= 1104 | Seems like this is not needed anymore. 1105 | I'm keeping it here maybe until when this feature officially lands on Org-mode 9.7. 1106 | 1107 | #+begin_src emacs-lisp :tangle no 1108 | (setq org-latex-preview-process-alist 1109 | `((dvipng :programs 1110 | ("latex" "dvipng") 1111 | :description "dvi > png" :message "you need to install the programs: latex and dvipng." :image-input-type "dvi" :image-output-type "png" :latex-compiler 1112 | ("%l -interaction nonstopmode -output-directory %o %f") 1113 | :latex-precompiler 1114 | ("%l -output-directory %o -ini -jobname=%b \"&%L\" mylatexformat.ltx %f") 1115 | :image-converter 1116 | ("dvipng --follow -D %D -T tight --depth --height -o %B-%%09d.png %f") 1117 | :transparent-image-converter 1118 | ("dvipng --follow -D %D -T tight -bg Transparent --depth --height -o %B-%%09d.png %f")) 1119 | (dvisvgm :programs 1120 | ("latex" "dvisvgm") 1121 | :description "dvi > svg" :message "you need to install the programs: latex and dvisvgm." :image-input-type "dvi" :image-output-type "svg" :latex-compiler 1122 | ("%l -interaction nonstopmode -output-directory %o %f") 1123 | :latex-precompiler 1124 | ("%l -output-directory %o -ini -jobname=%b \"&%L\" mylatexformat.ltx %f") 1125 | :image-converter 1126 | ("dvisvgm --page=1- --optimize --clipjoin --relative --no-fonts --bbox=preview -o %B-%%9p.svg %f")) 1127 | (imagemagick :programs 1128 | ("pdflatex" "convert") 1129 | :description "pdf > png" :message "you need to install the programs: latex and imagemagick." :image-input-type "pdf" :image-output-type "png" :latex-compiler 1130 | ("pdflatex -interaction nonstopmode -output-directory %o %f") 1131 | :latex-precompiler 1132 | ("pdftex -output-directory %o -ini -jobname=%b \"&pdflatex\" mylatexformat.ltx %f") 1133 | :image-converter 1134 | ("convert -density %D -trim -antialias %f -quality 100 %B-%%09d.png")))) 1135 | #+end_src 1136 | **** Default previewing in =lualatex=-based buffers to use =latex= 1137 | The new previewing system is great, but only for =pdflatex=. 1138 | Sometimes I need to write LaTeX document that contains Unicode inputs, whether it's for Julia code exports with =engraved-faces= or for my own Vietnamese typing needs. 1139 | As of now, a good compromise is to use =lualatex= for latex exports but keeps using =latex= for the previewing system. 1140 | Remember that this may break if you have complicated custom latex preables in Org-mode. 1141 | 1142 | #+begin_src emacs-lisp :tangle no 1143 | (setq org-latex-preview-compiler-command-map 1144 | '(("pdflatex" . "latex") 1145 | ("xelatex" . "xelatex -no-pdf") ;Not working now, use lualatex instead 1146 | ("lualatex" . "latex"))) 1147 | #+end_src 1148 | *** Org-export 1149 | **** General 1150 | #+begin_src emacs-lisp 1151 | (use-package! ox 1152 | :config 1153 | (setq org-export-with-tags nil) 1154 | ;; Auto export acronyms as small caps 1155 | ;; Copied from tecosaur 1156 | (defun org-latex-substitute-verb-with-texttt (content) 1157 | "Replace instances of \\verb with \\texttt{}." 1158 | (replace-regexp-in-string 1159 | "\\\\verb\\(.\\).+?\\1" 1160 | (lambda (verb-string) 1161 | (replace-regexp-in-string 1162 | "\\\\" "\\\\\\\\" ; Why elisp, why? 1163 | (org-latex--text-markup (substring verb-string 6 -1) 'code '(:latex-text-markup-alist ((code . protectedtexttt)))))) 1164 | content)) 1165 | 1166 | (defun org-export-filter-text-acronym (text backend _info) 1167 | "Wrap suspected acronyms in acronyms-specific formatting. 1168 | Treat sequences of 2+ capital letters (optionally succeeded by \"s\") as an acronym. 1169 | Ignore if preceeded by \";\" (for manual prevention) or \"\\\" (for LaTeX commands). 1170 | 1171 | TODO abstract backend implementations." 1172 | (let ((base-backend 1173 | (cond 1174 | ;; ((org-export-derived-backend-p backend 'latex) 'latex) 1175 | ((org-export-derived-backend-p backend 'html) 'html))) 1176 | (case-fold-search nil)) 1177 | (when base-backend 1178 | (replace-regexp-in-string 1179 | "[;\\\\]?\\b[A-Z][A-Z]+s?\\(?:[^A-Za-z]\\|\\b\\)" 1180 | (lambda (all-caps-str) 1181 | (cond ((equal (aref all-caps-str 0) ?\\) all-caps-str) ; don't format LaTeX commands 1182 | ((equal (aref all-caps-str 0) ?\;) (substring all-caps-str 1)) ; just remove not-acronym indicator char ";" 1183 | (t (let* ((final-char (if (string-match-p "[^A-Za-z]" (substring all-caps-str -1 (length all-caps-str))) 1184 | (substring all-caps-str -1 (length all-caps-str)) 1185 | nil)) ; needed to re-insert the [^A-Za-z] at the end 1186 | (trailing-s (equal (aref all-caps-str (- (length all-caps-str) (if final-char 2 1))) ?s)) 1187 | (acr (if final-char 1188 | (substring all-caps-str 0 (if trailing-s -2 -1)) 1189 | (substring all-caps-str 0 (+ (if trailing-s -1 (length all-caps-str))))))) 1190 | (pcase base-backend 1191 | ('latex (concat "\\acr{" (s-downcase acr) "}" (when trailing-s "\\acrs{}") final-char)) 1192 | ('html (concat "" (s-downcase acr) "" (when trailing-s "s") final-char))))))) 1193 | text t t)))) 1194 | 1195 | (add-to-list 'org-export-filter-plain-text-functions 1196 | #'org-export-filter-text-acronym) 1197 | 1198 | ;; We won't use `org-export-filter-headline-functions' because it 1199 | ;; passes (and formats) the entire section contents. That's no good. 1200 | 1201 | (defun org-html-format-headline-acronymised (todo todo-type priority text tags info) 1202 | "Like `org-html-format-headline-default-function', but with acronym formatting." 1203 | (org-html-format-headline-default-function 1204 | todo todo-type priority (org-export-filter-text-acronym text 'html info) tags info)) 1205 | (setq org-html-format-headline-function #'org-html-format-headline-acronymised) 1206 | 1207 | ;; (defun org-latex-format-headline-acronymised (todo todo-type priority text tags info) 1208 | ;; "Like `org-latex-format-headline-default-function', but with acronym formatting." 1209 | ;; (org-latex-format-headline-default-function 1210 | ;; todo todo-type priority (org-latex-substitute-verb-with-texttt 1211 | ;; (org-export-filter-text-acronym text 'latex info)) tags info)) 1212 | ;; (setq org-latex-format-headline-function #'org-latex-format-headline-acronymised) 1213 | ) 1214 | #+end_src 1215 | 1216 | This allows ignoring headlines when exporting by adding the tag =:ignore:= to an Org heading. 1217 | 1218 | #+begin_src emacs-lisp 1219 | (use-package! ox-extra 1220 | :config 1221 | (ox-extras-activate '(ignore-headlines))) 1222 | #+end_src 1223 | **** Export to LaTeX 1224 | 1225 | #+begin_src emacs-lisp 1226 | (use-package! ox-latex 1227 | :config 1228 | ;; (setq org-latex-pdf-process 1229 | ;; '("latexmk -pdflatex='%latex -shell-escape -bibtex -interaction=nonstopmode' -pdf -output-directory=%o -f %f")) 1230 | 1231 | ;; Default packages 1232 | (setq org-export-headline-levels 5 1233 | org-latex-default-packages-alist 1234 | '(("AUTO" "inputenc" t ("pdflatex" "lualatex")) 1235 | ("T1" "fontenc" t ("pdflatex")) 1236 | ;;Microtype 1237 | ;;- pdflatex: full microtype features, fast, however no fontspec 1238 | ;;- lualatex: good microtype feature support, however slow to compile 1239 | ;;- xelatex: only protrusion support, fast compilation 1240 | ("activate={true,nocompatibility},final,tracking=true,kerning=true,spacing=true,factor=1100,stretch=10,shrink=10" 1241 | "microtype" nil ("pdflatex")) 1242 | ("activate={true,nocompatibility},final,tracking=true,factor=1100,stretch=10,shrink=10" 1243 | "microtype" nil ("lualatex")) 1244 | ("protrusion={true,nocompatibility},final,factor=1100,stretch=10,shrink=10" 1245 | "microtype" nil ("xelatex")) 1246 | ("dvipsnames,svgnames" "xcolor" nil) 1247 | ("colorlinks=true, linkcolor=DarkBlue, citecolor=BrickRed, urlcolor=DarkGreen" "hyperref" nil)))) 1248 | #+end_src 1249 | 1250 | Add KOMA-scripts classes to org export: 1251 | 1252 | #+begin_src emacs-lisp 1253 | (after! ox 1254 | ;; Add KOMA-scripts classes to org export 1255 | (add-to-list 'org-latex-classes 1256 | '("koma-letter" "\\documentclass[11pt]{scrletter}" 1257 | ("\\section{%s}" . "\\section*{%s}") 1258 | ("\\subsection{%s}" . "\\subsection*{%s}") 1259 | ("\\subsubsection{%s}" . "\\subsubsection*{%s}") 1260 | ("\\paragraph{%s}" . "\\paragraph*{%s}") 1261 | ("\\subparagraph{%s}" . "\\subparagraph*{%s}"))) 1262 | 1263 | (add-to-list 'org-latex-classes 1264 | '("koma-article" "\\documentclass[11pt]{scrartcl}" 1265 | ("\\section{%s}" . "\\section*{%s}") 1266 | ("\\subsection{%s}" . "\\subsection*{%s}") 1267 | ("\\subsubsection{%s}" . "\\subsubsection*{%s}") 1268 | ("\\paragraph{%s}" . "\\paragraph*{%s}") 1269 | ("\\subparagraph{%s}" . "\\subparagraph*{%s}"))) 1270 | 1271 | (add-to-list 'org-latex-classes 1272 | '("koma-report" "\\documentclass[11pt]{scrreprt}" 1273 | ("\\part{%s}" . "\\part*{%s}") 1274 | ("\\chapter{%s}" . "\\chapter*{%s}") 1275 | ("\\section{%s}" . "\\section*{%s}") 1276 | ("\\subsection{%s}" . "\\subsection*{%s}") 1277 | ("\\subsubsection{%s}" . "\\subsubsection*{%s}"))) 1278 | 1279 | (add-to-list 'org-latex-classes 1280 | '("koma-book" "\\documentclass[11pt]{scrbook}" 1281 | ("\\part{%s}" . "\\part*{%s}") 1282 | ("\\chapter{%s}" . "\\chapter*{%s}") 1283 | ("\\section{%s}" . "\\section*{%s}") 1284 | ("\\subsection{%s}" . "\\subsection*{%s}") 1285 | ("\\subsubsection{%s}" . "\\subsubsection*{%s}"))) 1286 | 1287 | ;; Add the custom cheatsheet class 1288 | (add-to-list 'org-latex-classes 1289 | '("hpcheatsheet" "\\documentclass[columns=4,bgcolor=white,fgcolor=black]{hpcheatsheet}" 1290 | ("\\boxtitle{%s}" . "\\boxtitle{%s}") 1291 | ("\\sectiontitle{%s}" . "\\sectiontitle{%s}") 1292 | ("\\subsection{%s}" . "\\subsection*{%s}") 1293 | ("\\paragraph{%s}" . "\\paragraph*{%s}") 1294 | ("\\subparagraph{%s}" . "\\subparagraph*{%s}"))) 1295 | ) 1296 | 1297 | (setq org-latex-default-class "koma-article") 1298 | #+end_src 1299 | 1300 | This part controls how code blocks (verbatims) are handled. 1301 | In the past, this is done via a LaTeX package called =minted=, which gives =pygments=-style syntax highlighting to codes. 1302 | However, in recent changes, Org-mode provide its own highlighting backend -- =engraved= -- which translates Emacs' font-lock overlays to LaTeX, results in much better color schemes and "smarter" syntax highlighting, as this potentially works with the Language Server Protocol and =tree-sitter=. 1303 | 1304 | #+begin_src emacs-lisp 1305 | (after! ox-latex 1306 | (setq org-latex-src-block-backend 'engraved)) 1307 | #+end_src 1308 | **** Engrave-faces 1309 | 1310 | Add support for diff-faces 1311 | 1312 | #+begin_src emacs-lisp 1313 | (use-package! engrave-faces 1314 | :init 1315 | (setq engrave-faces-themes 1316 | '((default . 1317 | (;; faces.el --- excluding: bold, italic, bold-italic, underline, and some others 1318 | (default :short "default" :slug "D" :foreground "#000000" :background "#ffffff" :family "Monospace") 1319 | (variable-pitch :short "var-pitch" :slug "vp" :foreground "#000000" :family "Sans Serif") 1320 | (shadow :short "shadow" :slug "h" :foreground "#7f7f7f") 1321 | (success :short "success" :slug "sc" :foreground "#228b22" :weight bold) 1322 | (warning :short "warning" :slug "w" :foreground "#ff8e00" :weight bold) 1323 | (error :short "error" :slug "e" :foreground "#ff0000" :weight bold) 1324 | (link :short "link" :slug "l" :foreground "#ff0000") 1325 | (link-visited :short "link" :slug "lv" :foreground "#ff0000") 1326 | (highlight :short "link" :slug "hi" :foreground "#ff0000") 1327 | ;; font-lock.el 1328 | (font-lock-comment-face :short "fl-comment" :slug "c" :foreground "#b22222") 1329 | (font-lock-comment-delimiter-face :short "fl-comment-delim" :slug "cd" :foreground "#b22222") 1330 | (font-lock-string-face :short "fl-string" :slug "s" :foreground "#8b2252") 1331 | (font-lock-doc-face :short "fl-doc" :slug "d" :foreground "#8b2252") 1332 | (font-lock-doc-markup-face :short "fl-doc-markup" :slug "m" :foreground "#008b8b") 1333 | (font-lock-keyword-face :short "fl-keyword" :slug "k" :foreground "#9370db") 1334 | (font-lock-builtin-face :short "fl-builtin" :slug "b" :foreground "#483d8b") 1335 | (font-lock-function-name-face :short "fl-function" :slug "f" :foreground "#0000ff") 1336 | (font-lock-variable-name-face :short "fl-variable" :slug "v" :foreground "#a0522d") 1337 | (font-lock-type-face :short "fl-type" :slug "t" :foreground "#228b22") 1338 | (font-lock-constant-face :short "fl-constant" :slug "o" :foreground "#008b8b") 1339 | (font-lock-warning-face :short "fl-warning" :slug "wr" :foreground "#ff0000" :weight bold) 1340 | (font-lock-negation-char-face :short "fl-neg-char" :slug "nc") 1341 | (font-lock-preprocessor-face :short "fl-preprocessor" :slug "pp" :foreground "#483d8b") 1342 | (font-lock-regexp-grouping-construct :short "fl-regexp" :slug "rc" :weight bold) 1343 | (font-lock-regexp-grouping-backslash :short "fl-regexp-backslash" :slug "rb" :weight bold) 1344 | ;; org-faces.el 1345 | (org-block :short "org-block" :slug "ob") ; forcing no background is preferable 1346 | (org-block-begin-line :short "org-block-begin" :slug "obb") ; forcing no background is preferable 1347 | (org-block-end-line :short "org-block-end" :slug "obe") ; forcing no background is preferable 1348 | ;; outlines 1349 | (outline-1 :short "outline-1" :slug "Oa" :foreground "#0000ff") 1350 | (outline-2 :short "outline-2" :slug "Ob" :foreground "#a0522d") 1351 | (outline-3 :short "outline-3" :slug "Oc" :foreground "#a020f0") 1352 | (outline-4 :short "outline-4" :slug "Od" :foreground "#b22222") 1353 | (outline-5 :short "outline-5" :slug "Oe" :foreground "#228b22") 1354 | (outline-6 :short "outline-6" :slug "Of" :foreground "#008b8b") 1355 | (outline-7 :short "outline-7" :slug "Og" :foreground "#483d8b") 1356 | (outline-8 :short "outline-8" :slug "Oh" :foreground "#8b2252") 1357 | ;; highlight-numbers.el 1358 | (highlight-numbers-number :short "hl-number" :slug "hn" :foreground "#008b8b") 1359 | ;; highlight-quoted.el 1360 | (highlight-quoted-quote :short "hl-qquote" :slug "hq" :foreground "#9370db") 1361 | (highlight-quoted-symbol :short "hl-qsymbol" :slug "hs" :foreground "#008b8b") 1362 | ;; rainbow-delimiters.el 1363 | (rainbow-delimiters-depth-1-face :short "rd-1" :slug "rda" :foreground "#707183") 1364 | (rainbow-delimiters-depth-2-face :short "rd-2" :slug "rdb" :foreground "#7388d6") 1365 | (rainbow-delimiters-depth-3-face :short "rd-3" :slug "rdc" :foreground "#909183") 1366 | (rainbow-delimiters-depth-4-face :short "rd-4" :slug "rdd" :foreground "#709870") 1367 | (rainbow-delimiters-depth-5-face :short "rd-5" :slug "rde" :foreground "#907373") 1368 | (rainbow-delimiters-depth-6-face :short "rd-6" :slug "rdf" :foreground "#6276ba") 1369 | (rainbow-delimiters-depth-7-face :short "rd-7" :slug "rdg" :foreground "#858580") 1370 | (rainbow-delimiters-depth-8-face :short "rd-8" :slug "rdh" :foreground "#80a880") 1371 | (rainbow-delimiters-depth-9-face :short "rd-9" :slug "rdi" :foreground "#887070") 1372 | ;; Diffs 1373 | (diff-added :short "diff-added" :slug "diffa" :foreground "#4F894C") 1374 | (diff-changed :short "diff-changed" :slug "diffc" :foreground "#842879") 1375 | (diff-context :short "diff-context" :slug "diffco" :foreground "#525866") 1376 | (diff-removed :short "diff-removed" :slug "diffr" :foreground "#99324B") 1377 | (diff-header :short "diff-header" :slug "diffh" :foreground "#398EAC") 1378 | (diff-file-header :short "diff-file-header" :slug "difffh" :foreground "#3B6EA8") 1379 | (diff-hunk-header :short "diff-hunk-header" :slug "diffhh" :foreground "#842879") 1380 | ))))) 1381 | #+end_src 1382 | 1383 | **** Export to website with =ox-hugo= 1384 | ***** General config :ignore: 1385 | #+begin_src emacs-lisp 1386 | (use-package! ox-hugo 1387 | :config 1388 | (setq org-hugo-use-code-for-kbd t 1389 | org-use-tag-inheritance t 1390 | org-hugo-paired-shortcodes "sidenote marginnote notice" 1391 | org-hugo-base-dir (concat dropbox-directory "Blogs/hieutkt"))) 1392 | #+end_src 1393 | ***** Linking between different Org-roam files 1394 | 1395 | #+begin_src emacs-lisp 1396 | (setq org-id-extra-files (directory-files-recursively org-roam-directory "\.org$")) 1397 | #+end_src 1398 | 1399 | ***** Exporting footnotes as sidenotes 1400 | My website features Tufte-CSS-style sidenotes. 1401 | With =hugo=, this is implemented by wrapping text around the =sidenote= shortcode. 1402 | It would be nice if footnotes are exported as sidenotes here for Hugo export and as regular footnotes elsewhere[fn:1]. 1403 | Here's the code to implement this, based on [[https://takeonrules.com/2023/01/22/hacking-org-mode-export-for-footnotes-as-sidenotes/][this blog post]] with some modifications. 1404 | 1405 | #+begin_src emacs-lisp 1406 | (defun hp/org-hugo-export-footnote-as-sidenote (footnote-reference _contents info) 1407 | "Transcode a FOOTNOTE-REFERENCE element from Org to Markdown. 1408 | CONTENTS is nil. INFO is a plist used as a communication 1409 | channel." 1410 | (let* ((n (org-export-get-footnote-number footnote-reference info)) 1411 | (def (org-export-get-footnote-definition footnote-reference info)) 1412 | (def-exported (when def (org-export-data def info)))) 1413 | (format "{{< sidenote >}}%s{{< /sidenote >}}" def-exported))) 1414 | 1415 | ;; Over-write the custom blackfriday export for footnote links. 1416 | (advice-add #'org-blackfriday-footnote-reference 1417 | :override #'hp/org-hugo-export-footnote-as-sidenote 1418 | '((name . "wrapper"))) 1419 | 1420 | ;; Don't render the section for export 1421 | (advice-add #'org-blackfriday-footnote-section 1422 | :override (lambda (&rest rest) ()) 1423 | '((name . "wrapper"))) 1424 | #+end_src 1425 | 1426 | **** Exporting behavior of special blocks 1427 | ***** General behaviors 1428 | #+begin_src emacs-lisp 1429 | (use-package! org-special-block-extras 1430 | :after org 1431 | :hook (org-mode . org-special-block-extras-mode) 1432 | :config 1433 | (setq org-special-block-add-html-extra nil)) 1434 | #+end_src 1435 | 1436 | ***** Theorems, proof, definitions 1437 | #+begin_src emacs-lisp 1438 | (after! org-special-block-extras 1439 | ;; Theorem 1440 | (org-defblock theorem 1441 | (name "") 1442 | (format (pcase backend 1443 | (`latex "\\begin{theorem}%s\n%s\n\\end{theorem}") 1444 | (_ "{{< notice info \"Theorem: %s\" >}}\n%s\n{{< /notice >}}")) 1445 | (if (eq name "") "" (format "[%s]" name)) contents)) 1446 | ;; Proposition 1447 | (org-defblock proposition 1448 | (name "") 1449 | (format (pcase backend 1450 | (`latex "\\begin{proposition}%s\n%s\n\\end{proposition}") 1451 | (_ "{{< notice info \"Proposition: %s\" >}}\n%s\n{{< /notice >}}")) 1452 | (if (eq name "") "" (format "[%s]" name)) contents)) 1453 | ;; Lemma 1454 | (org-defblock lemma 1455 | (name "") 1456 | (format (pcase backend 1457 | (`latex "\\begin{lemma}%s\n%s\n\\end{lemma}") 1458 | (_ "{{< notice info \"Lemma: %s\" >}}\n%s\n{{< /notice >}}")) 1459 | (if (eq name "") "" (format "[%s]" name)) contents)) 1460 | ;;Definitions 1461 | (org-defblock definition 1462 | (name "") 1463 | (format (pcase backend 1464 | (`latex "\\begin{definition}%s\n%s\n\\end{definition}") 1465 | (_ "{{< notice info \"Definition: %s\" >}}\n%s\n{{< /notice >}}")) 1466 | (if (eq name "") "" (format "[%s]" name)) contents)) 1467 | ) 1468 | #+end_src 1469 | ***** Simpler =details= blocks 1470 | 1471 | #+begin_src emacs-lisp 1472 | (after! org-special-block-extras 1473 | (org-defblock detail-summary 1474 | (title "") 1475 | (format (pcase backend 1476 | (_ "
\n%s%s
")) 1477 | title contents))) 1478 | #+end_src 1479 | ***** Notices 1480 | 1481 | #+begin_src emacs-lisp 1482 | (after! org-special-block-extras 1483 | (org-defblock warning 1484 | (frame-title "Warning") 1485 | (format 1486 | (pcase backend 1487 | (`latex "\\begin{mdframed}[ 1488 | frametitlebackgroundcolor=DarkRed!15, backgroundcolor=DarkRed!5, 1489 | hidealllines=true, innertopmargin=\\topskip, roundcorner=5pt, 1490 | frametitlefont=\\sffamily\\color{DarkRed!60!black}, frametitle=%s] 1491 | %s 1492 | \\end{mdframed}") 1493 | (_ "{{< notice warning \"%s\" >}}\n%s\n{{< /notice >}}")) 1494 | frame-title contents)) 1495 | 1496 | 1497 | (org-defblock info 1498 | (frame-title "Info") 1499 | (format 1500 | (pcase backend 1501 | (`latex "\\begin{mdframed}[ 1502 | frametitlebackgroundcolor=Teal!15, backgroundcolor=Teal!5, 1503 | hidealllines=true, innertopmargin=\\topskip, roundcorner=5pt, 1504 | frametitlefont=\\sffamily\\color{Teal!60!black}, frametitle=%s] 1505 | %s 1506 | \\end{mdframed}") 1507 | (_ "{{< notice info \"%s\" >}}\n%s\n{{< /notice >}}")) 1508 | frame-title contents)) 1509 | 1510 | 1511 | (org-defblock tips 1512 | (frame-title "Tips") 1513 | (format 1514 | (pcase backend 1515 | (`latex "\\begin{mdframed}[ 1516 | frametitlebackgroundcolor=ForestGreen!15, backgroundcolor=ForestGreen!5, 1517 | hidealllines=true, innertopmargin=\\topskip, roundcorner=5pt, 1518 | frametitlefont=\\sffamily\\color{ForestGreen!60!black}, frametitle=%s] 1519 | %s 1520 | \\end{mdframed}") 1521 | (_ "{{< notice tip \"%s\" >}}\n%s\n{{< /notice >}}")) 1522 | frame-title contents)) 1523 | ) 1524 | #+end_src 1525 | 1526 | **** Block color overlays 1527 | Since we're are overdoing it, let's make these blocks /slightly colorful/! 1528 | 1529 | #+begin_src emacs-lisp 1530 | (after! org-special-block-extras 1531 | (defface hp/org-special-blocks-tips-face 1532 | `((t :background ,(doom-blend (doom-color 'teal) (doom-color 'bg) 0.1) :extend t)) 1533 | "Face for tip blocks") 1534 | (defface hp/org-special-blocks-info-face 1535 | `((t :background ,(doom-blend (doom-color 'blue) (doom-color 'bg) 0.1) :extend t)) 1536 | "Face for info blocks") 1537 | (defface hp/org-special-blocks-warning-face 1538 | `((t :background ,(doom-blend (doom-color 'orange) (doom-color 'bg) 0.1) :extend t)) 1539 | "Face for warning blocks") 1540 | (defface hp/org-special-blocks-note-face 1541 | `((t :background ,(doom-blend (doom-color 'violet) (doom-color 'bg) 0.1) :extend t)) 1542 | "Face for warning blocks") 1543 | (defface hp/org-special-blocks-question-face 1544 | `((t :background ,(doom-blend (doom-color 'green) (doom-color 'bg) 0.1) :extend t)) 1545 | "Face for warning blocks") 1546 | (defface hp/org-special-blocks-error-face 1547 | `((t :background ,(doom-blend (doom-color 'red) (doom-color 'bg) 0.1) :extend t)) 1548 | "Face for warning blocks") 1549 | 1550 | (defun hp/org-add-overlay-tips-blocks () 1551 | "Apply overlays to #+begin_tips blocks in the current buffer." 1552 | (save-excursion 1553 | (goto-char (point-min)) 1554 | (while (re-search-forward "^\\(\\#\\+begin_tips\\)" nil t) 1555 | (let* ((beg (match-beginning 0)) 1556 | (end (if (re-search-forward "^\\(\\#\\+end_tips\\)" nil t) 1557 | (1+ (line-end-position)) 1558 | (point-max))) 1559 | (ov (make-overlay beg end))) 1560 | (overlay-put ov 'face 'hp/org-special-blocks-tips-face))))) 1561 | 1562 | (defun hp/org-add-overlay-info-blocks () 1563 | "Apply overlays to #+begin_info blocks in the current buffer." 1564 | (save-excursion 1565 | (goto-char (point-min)) 1566 | (while (re-search-forward "^\\(\\#\\+begin_\\(?:info\\|theorem\\)\\)" nil t) 1567 | (let* ((beg (match-beginning 0)) 1568 | (end (if (re-search-forward "^\\(\\#\\+end_\\(?:info\\|theorem\\)\\)" nil t) 1569 | (1+ (line-end-position)) 1570 | (point-max))) 1571 | (ov (make-overlay beg end))) 1572 | (overlay-put ov 'face 'hp/org-special-blocks-info-face))))) 1573 | 1574 | (defun hp/org-add-overlay-warning-blocks () 1575 | "Apply overlays to #+begin_warning blocks in the current buffer." 1576 | (save-excursion 1577 | (goto-char (point-min)) 1578 | (while (re-search-forward "^\\(\\#\\+begin_warning\\)" nil t) 1579 | (let* ((beg (match-beginning 0)) 1580 | (end (if (re-search-forward "^\\(\\#\\+end_warning\\)" nil t) 1581 | (1+ (line-end-position)) 1582 | (point-max))) 1583 | (ov (make-overlay beg end))) 1584 | (overlay-put ov 'face 'hp/org-special-blocks-warning-face))))) 1585 | 1586 | (defun hp/org-add-overlay-note-blocks () 1587 | "Apply overlays to #+begin_note blocks in the current buffer." 1588 | (save-excursion 1589 | (goto-char (point-min)) 1590 | (while (re-search-forward "^\\(\\#\\+begin_\\(?:note\\|definition\\)\\)" nil t) 1591 | (let* ((beg (match-beginning 0)) 1592 | (end (if (re-search-forward "^\\(\\#\\+end_\\(?:note\\|definition\\)\\)" nil t) 1593 | (1+ (line-end-position)) 1594 | (point-max))) 1595 | (ov (make-overlay beg end))) 1596 | (overlay-put ov 'face 'hp/org-special-blocks-note-face))))) 1597 | 1598 | (defun hp/org-add-overlay-question-blocks () 1599 | "Apply overlays to #+begin_question blocks in the current buffer." 1600 | (save-excursion 1601 | (goto-char (point-min)) 1602 | (while (re-search-forward "^\\(\\#\\+begin_\\(?:question\\|proposition\\)\\)" nil t) 1603 | (let* ((beg (match-beginning 0)) 1604 | (end (if (re-search-forward "^\\(\\#\\+end_\\(?:question\\|proposition\\)\\)" nil t) 1605 | (1+ (line-end-position)) 1606 | (point-max))) 1607 | (ov (make-overlay beg end))) 1608 | (overlay-put ov 'face 'hp/org-special-blocks-question-face))))) 1609 | 1610 | 1611 | (add-hook! '(org-mode-hook yas-after-exit-snippet-hook) 1612 | '(hp/org-add-overlay-tips-blocks 1613 | hp/org-add-overlay-info-blocks 1614 | hp/org-add-overlay-warning-blocks 1615 | hp/org-add-overlay-note-blocks 1616 | hp/org-add-overlay-question-blocks))) 1617 | #+end_src 1618 | 1619 | *** Org-agenda 1620 | **** General setup 1621 | #+begin_src emacs-lisp 1622 | (use-package! org-agenda 1623 | :config 1624 | ;; Setting the TODO keywords 1625 | (setq org-todo-keywords 1626 | '((sequence 1627 | "TODO(t)" ;What needs to be done 1628 | "NEXT(n)" ;A project without NEXTs is stuck 1629 | "|" 1630 | "DONE(d)") 1631 | (sequence 1632 | "REPEAT(e)" ;Repeating tasks 1633 | "|" 1634 | "DONE") 1635 | (sequence 1636 | "HOLD(h)" ;Task is on hold because of me 1637 | "PROJ(p)" ;Contains sub-tasks 1638 | "WAIT(w)" ;Tasks delegated to others 1639 | "REVIEW(r)" ;Daily notes that need reviews 1640 | "IDEA(i)" ;Daily notes that need reviews 1641 | "|" 1642 | "STOP(c)" ;Stopped/cancelled 1643 | "EVENT(m)" ;Meetings 1644 | )) 1645 | org-todo-keyword-faces 1646 | '(("[-]" . +org-todo-active) 1647 | ("NEXT" . +org-todo-active) 1648 | ("[?]" . +org-todo-onhold) 1649 | ("REVIEW" . +org-todo-onhold) 1650 | ("HOLD" . +org-todo-cancel) 1651 | ("PROJ" . +org-todo-project) 1652 | ("DONE" . +org-todo-cancel) 1653 | ("STOP" . +org-todo-cancel))) 1654 | ;; Todo/checkbox statistic to count nested headings 1655 | (setq org-hierarchical-todo-statistics nil) 1656 | ;; Appearance 1657 | (setq org-agenda-span 20 1658 | org-agenda-prefix-format " %i %?-2 t%s" 1659 | org-agenda-todo-keyword-format "%-6s" 1660 | org-agenda-current-time-string "ᐊ┈┈┈┈┈┈┈ Now" 1661 | org-agenda-time-grid '((today require-timed remove-match) 1662 | (0900 1200 1400 1700 2100) 1663 | " " 1664 | "┈┈┈┈┈┈┈┈┈┈┈┈┈") 1665 | ) 1666 | ;; Clocking 1667 | (setq org-clock-persist 'history 1668 | org-columns-default-format "%50ITEM(Task) %10CLOCKSUM %16TIMESTAMP_IA" 1669 | org-agenda-start-with-log-mode t) 1670 | (org-clock-persistence-insinuate)) 1671 | 1672 | 1673 | (use-package! org-habit 1674 | :config 1675 | (setq org-habit-show-all-today t)) 1676 | #+end_src 1677 | 1678 | **** Org-super-agenda 1679 | 1680 | #+begin_src emacs-lisp 1681 | (use-package! org-super-agenda 1682 | :after org-agenda 1683 | :config 1684 | ;; Enable org-super-agenda 1685 | (org-super-agenda-mode) 1686 | (setq org-agenda-block-separator ?―) 1687 | ;; Customise the agenda view 1688 | (setq org-agenda-custom-commands 1689 | '(("o" "Overview" 1690 | ((agenda "") 1691 | ;; (todo "NEXT" 1692 | ;; ((org-super-agenda-groups 1693 | ;; '((:auto-map hp/agenda-auto-group-title-olp))))) 1694 | (todo "TODO|HOLD|NEXT|WAIT" 1695 | ((org-agenda-overriding-header 1696 | "Every TASKS under the sun") 1697 | (org-super-agenda-groups 1698 | '((:auto-map hp/agenda-auto-group-title-olp))))) 1699 | (todo "REVIEW" 1700 | ((org-agenda-overriding-header "Study") 1701 | (org-super-agenda-groups 1702 | '((:auto-map hp/agenda-auto-group-title-olp))))) 1703 | (tags-todo "writings|blog" 1704 | ((org-agenda-overriding-header "Writings") 1705 | (org-super-agenda-groups 1706 | '((:auto-map hp/agenda-auto-group-title-olp))))) 1707 | (todo "IDEA" 1708 | ((org-agenda-overriding-header "Ideas") 1709 | (org-super-agenda-groups 1710 | '((:auto-map hp/agenda-auto-group-title-olp))))) 1711 | )))) 1712 | 1713 | (defun hp/agenda-auto-group-title-olp (item) 1714 | (-when-let* ((marker (or (get-text-property 0 'org-marker item) 1715 | (get-text-property 0 'org-hd-marker item))) 1716 | (buffer (->> marker marker-buffer )) 1717 | (title (cadar (org-collect-keywords '("title")))) 1718 | (filledtitle (if (> (length title) 70) 1719 | (concat (substring title 0 70) "...") title)) 1720 | (tags (org-get-tags)) 1721 | (olp (org-super-agenda--when-with-marker-buffer 1722 | (org-super-agenda--get-marker item) 1723 | (s-join " → " (org-get-outline-path))))) 1724 | (concat (if (not (member "journal" tags)) 1725 | (concat "「" filledtitle "」" ) " ") olp))) 1726 | 1727 | ;; Make evil keymaps works on org-super-agenda headers 1728 | (after! evil-org-agenda 1729 | (setq org-super-agenda-header-map (copy-keymap evil-org-agenda-mode-map))) 1730 | ;; Change header face to make it standout more 1731 | (custom-set-faces! 1732 | `(org-super-agenda-header 1733 | :inherit 'variable-pitch 1734 | :weight bold :foreground ,(doom-color 'cyan)) 1735 | `(org-agenda-structure 1736 | :inherit 'variable-pitch 1737 | :weight bold :foreground ,(doom-color 'blue)))) 1738 | #+end_src 1739 | 1740 | *** Org-capture 1741 | #+begin_src emacs-lisp 1742 | (use-package! org-capture 1743 | :config 1744 | ;;CAPTURE TEMPLATES 1745 | ;;Create IDs on certain capture 1746 | (defun hp/org-capture-maybe-create-id () 1747 | (when (org-capture-get :create-id) 1748 | (org-id-get-create))) 1749 | (add-hook 'org-capture-mode-hook #'hp/org-capture-maybe-create-id) 1750 | ;;Auxiliary functions 1751 | (defun hp/capture-ox-hugo-post (lang) 1752 | (setq hp/ox-hugo-post--title (read-from-minibuffer "Post Title: ") 1753 | hp/ox-hugo-post--fname (org-hugo-slug hp/ox-hugo-post--title) 1754 | hp/ox-hugo-post--fdate (format-time-string "%Y-%m-%d")) 1755 | (expand-file-name (format "%s_%s.%s.org" hp/ox-hugo-post--fdate hp/ox-hugo-post--fname lang) 1756 | (concat dropbox-directory "/Notes/Org-roam/writings/"))) 1757 | ;; Capture templates 1758 | (setq org-capture-templates 1759 | `(("i" "Inbox" entry (file ,(concat org-directory "/Agenda/inbox.org")) 1760 | "* TODO %?\n %i\n") 1761 | ("m" "Meeting" entry (file ,(concat org-directory "/Agenda/inbox.org")) 1762 | "* MEETING with %? :meeting:\n%t" :clock-in t :clock-resume t) 1763 | ;; Capture template for new blog posts 1764 | ("b" "New blog post") 1765 | ("be" "English" plain (file (lambda () (hp/capture-ox-hugo-post "en"))) 1766 | ,(string-join 1767 | '("#+title: %(eval hp/ox-hugo-post--title)" 1768 | "#+subtitle:" 1769 | "#+author: %n" 1770 | "#+filetags: blog" 1771 | "#+date: %(eval hp/ox-hugo-post--fdate)" 1772 | "#+hugo_base_dir: ~/Dropbox/Blogs/hieutkt/" 1773 | "#+hugo_section: ./posts/" 1774 | "#+hugo_tags: %?" 1775 | "#+hugo_url: ./%(eval hp/ox-hugo-post--fname)" 1776 | "#+hugo_slug: %(eval hp/ox-hugo-post--fname)" 1777 | "#+hugo_custom_front_matter:" 1778 | "#+hugo_draft: false" 1779 | "#+startup: content" 1780 | "#+options: toc:2 num:t") 1781 | "\n") 1782 | :create-id t 1783 | :immediate-finish t 1784 | :jump-to-captured t) 1785 | ("bv" "Vietnamese" plain (file (lambda () (hp/capture-ox-hugo-post "vi"))) 1786 | ,(string-join 1787 | '("#+title: %(eval hp/ox-hugo-post--title)" 1788 | "#+subtitle:" 1789 | "#+author: %n" 1790 | "#+filetags: blog" 1791 | "#+date: %(eval hp/ox-hugo-post--fdate)" 1792 | "#+hugo_base_dir: ~/Dropbox/Blogs/hieutkt/" 1793 | "#+hugo_section: ./posts/" 1794 | "#+hugo_tags: %?" 1795 | "#+hugo_url: ./%(eval hp/ox-hugo-post--fname)" 1796 | "#+hugo_slug: %(eval hp/ox-hugo-post--fname)" 1797 | "#+hugo_custom_front_matter:" 1798 | "#+hugo_draft: false" 1799 | "#+startup: content" 1800 | "#+options: toc:2 num:t") 1801 | "\n") 1802 | :create-id t 1803 | :immediate-finish t 1804 | :jump-to-captured t)))) 1805 | #+end_src 1806 | 1807 | *** Org-babel 1808 | Org-babel might be nice, but editing inside an Org-buffer means that you have to give up all the nice functionalities of the individual language's major more. 1809 | Luckily, we have =org-edit-special= (bound to ~SPC m '~ in Doom Emacs). 1810 | 1811 | #+begin_src emacs-lisp 1812 | (setq org-src-window-setup 'current-window) 1813 | #+end_src 1814 | 1815 | Now, to set this up for different languages: 1816 | 1817 | #+begin_src emacs-lisp 1818 | (use-package! ob-julia 1819 | :commands org-babel-execute:julia) 1820 | #+end_src 1821 | 1822 | *** Org-cite 1823 | #+begin_src emacs-lisp 1824 | (use-package! oc 1825 | :config 1826 | (setq org-cite-csl-styles-dir (concat dropbox-directory "Documents/Zotero/styles/") 1827 | org-cite-export-processors '((latex . (biblatex "ext-authoryear")) 1828 | (t . (csl "chicago-author-date.csl"))))) 1829 | #+end_src 1830 | 1831 | *** Org-roam 1832 | **** Fundamental settings 1833 | ***** Customizing main interface 1834 | #+begin_src emacs-lisp 1835 | (use-package! org-roam 1836 | :after org 1837 | :init 1838 | (setq org-roam-directory (concat org-directory "/Org-roam/") 1839 | org-roam-completion-everywhere nil 1840 | ;;Functions tags are special types of tags which tells what the node are for 1841 | ;;In the future, this should probably be replaced by categories 1842 | hp/org-roam-function-tags '("compilation" "argument" "journal" "concept" "tool" "data" "bio" "literature" "event" "website")) 1843 | :config 1844 | ;; Org-roam interface 1845 | (cl-defmethod org-roam-node-hierarchy ((node org-roam-node)) 1846 | "Return the node's TITLE, as well as it's HIERACHY." 1847 | (let* ((title (org-roam-node-title node)) 1848 | (olp (mapcar (lambda (s) (if (> (length s) 10) (concat (substring s 0 10) "...") s)) (org-roam-node-olp node))) 1849 | (level (org-roam-node-level node)) 1850 | (filetitle (org-roam-get-keyword "TITLE" (org-roam-node-file node))) 1851 | (filetitle-or-name (if filetitle filetitle (file-name-nondirectory (org-roam-node-file node)))) 1852 | (shortentitle (if (> (length filetitle-or-name) 20) (concat (substring filetitle-or-name 0 20) "...") filetitle-or-name)) 1853 | (separator (concat " " (nerd-icons-octicon "nf-oct-chevron_right") " "))) 1854 | (cond 1855 | ((= level 1) (concat (propertize (format "=level:%d=" level) 'display 1856 | (nerd-icons-faicon "nf-fa-file" :face 'nerd-icons-dyellow)) 1857 | (propertize shortentitle 'face 'org-roam-olp) separator title)) 1858 | ((= level 2) (concat (propertize (format "=level:%d=" level) 'display 1859 | (nerd-icons-faicon "nf-fa-file" :face 'nerd-icons-dsilver)) 1860 | (propertize (concat shortentitle separator (string-join olp separator)) 'face 'org-roam-olp) 1861 | separator title)) 1862 | ((> level 2) (concat (propertize (format "=level:%d=" level) 'display 1863 | (nerd-icons-faicon "nf-fa-file" :face 'org-roam-olp)) 1864 | (propertize (concat shortentitle separator (string-join olp separator)) 'face 'org-roam-olp) separator title)) 1865 | (t (concat (propertize (format "=level:%d=" level) 'display 1866 | (nerd-icons-faicon "nf-fa-file" :face 'nerd-icons-yellow)) 1867 | (if filetitle title (propertize filetitle-or-name 'face 'nerd-icons-dyellow))))))) 1868 | 1869 | (cl-defmethod org-roam-node-functiontag ((node org-roam-node)) 1870 | "Return the FUNCTION TAG for each node. These tags are intended to be unique to each file, and represent the note's function. 1871 | journal data literature" 1872 | (let* ((tags (seq-filter (lambda (tag) (not (string= tag "ATTACH"))) (org-roam-node-tags node)))) 1873 | (concat 1874 | ;; Argument or compilation 1875 | (cond 1876 | ((member "argument" tags) 1877 | (propertize "=f:argument=" 'display 1878 | (nerd-icons-mdicon "nf-md-forum" :face 'nerd-icons-dred))) 1879 | ((member "compilation" tags) 1880 | (propertize "=f:compilation=" 'display 1881 | (nerd-icons-mdicon "nf-md-format_list_text" :face 'nerd-icons-dyellow))) 1882 | (t (propertize "=f:empty=" 'display 1883 | (nerd-icons-codicon "nf-cod-remove" :face 'org-hide)))) 1884 | ;; concept, bio, data or event 1885 | (cond 1886 | ((member "concept" tags) 1887 | (propertize "=f:concept=" 'display 1888 | (nerd-icons-mdicon "nf-md-blur" :face 'nerd-icons-dblue))) 1889 | ((member "tool" tags) 1890 | (propertize "=f:tool=" 'display 1891 | (nerd-icons-mdicon "nf-md-tools" :face 'nerd-icons-dblue))) 1892 | ((member "bio" tags) 1893 | (propertize "=f:bio=" 'display 1894 | (nerd-icons-octicon "nf-oct-people" :face 'nerd-icons-dblue))) 1895 | ((member "event" tags) 1896 | (propertize "=f:event=" 'display 1897 | (nerd-icons-codicon "nf-cod-symbol_event" :face 'nerd-icons-dblue))) 1898 | ((member "data" tags) 1899 | (propertize "=f:data=" 'display 1900 | (nerd-icons-mdicon "nf-md-chart_arc" :face 'nerd-icons-dblue))) 1901 | (t (propertize "=f:nothing=" 'display 1902 | (nerd-icons-codicon "nf-cod-remove" :face 'org-hide)))) 1903 | ;; literature 1904 | (cond 1905 | ((member "literature" tags) 1906 | (propertize "=f:literature=" 'display 1907 | (nerd-icons-mdicon "nf-md-bookshelf" :face 'nerd-icons-dcyan))) 1908 | ((member "website" tags) 1909 | (propertize "=f:website=" 'display 1910 | (nerd-icons-mdicon "nf-md-web" :face 'nerd-icons-dsilver))) 1911 | (t (propertize "=f:nothing=" 'display 1912 | (nerd-icons-codicon "nf-cod-remove" :face 'org-hide)))) 1913 | ;; journal 1914 | ))) 1915 | 1916 | (cl-defmethod org-roam-node-othertags ((node org-roam-node)) 1917 | "Return the OTHER TAGS of each notes." 1918 | (let* ((tags (seq-filter (lambda (tag) (not (string= tag "ATTACH"))) (org-roam-node-tags node))) 1919 | (specialtags hp/org-roam-function-tags) 1920 | (othertags (seq-difference tags specialtags 'string=))) 1921 | (propertize 1922 | (string-join 1923 | (append '(" ") othertags) 1924 | (propertize "#" 'display 1925 | (nerd-icons-faicon "nf-fa-hashtag" :face 'nerd-icons-dgreen))) 1926 | 'face 'nerd-icons-dgreen))) 1927 | 1928 | (cl-defmethod org-roam-node-backlinkscount ((node org-roam-node)) 1929 | (let* ((count (caar (org-roam-db-query 1930 | [:select (funcall count source) 1931 | :from links 1932 | :where (= dest $s1) 1933 | :and (= type "id")] 1934 | (org-roam-node-id node))))) 1935 | (if (> count 0) 1936 | (concat (propertize "=has:backlinks=" 'display 1937 | (nerd-icons-mdicon "nf-md-link" :face 'nerd-icons-blue)) (format "%d" count)) 1938 | (concat " " (propertize "=not-backlinks=" 'display 1939 | (nerd-icons-mdicon "nf-md-link" :face 'org-hide)) " ")))) 1940 | 1941 | (cl-defmethod org-roam-node-directories ((node org-roam-node)) 1942 | (if-let ((dirs (file-name-directory (file-relative-name (org-roam-node-file node) org-roam-directory)))) 1943 | (concat 1944 | (if (string= "journal/" dirs) 1945 | (nerd-icons-mdicon "nf-md-fountain_pen_tip" :face 'nerd-icons-dsilver) 1946 | (nerd-icons-mdicon "nf-md-folder" :face 'nerd-icons-dsilver)) 1947 | (propertize (string-join (f-split dirs) "/") 'face 'nerd-icons-dsilver) " ") 1948 | "")) 1949 | 1950 | (defun +marginalia--time-colorful (time) 1951 | (let* ((seconds (float-time (time-subtract (current-time) time))) 1952 | (color (doom-blend 1953 | (face-attribute 'marginalia-on :foreground nil t) 1954 | (face-attribute 'marginalia-off :foreground nil t) 1955 | (/ 1.0 (log (+ 3 (/ (+ 1 seconds) 345600.0))))))) 1956 | ;; 1 - log(3 + 1/(days + 1)) % grey 1957 | (propertize (marginalia--time time) 'face (list :foreground color :slant 'italic)))) 1958 | 1959 | (setq org-roam-node-display-template 1960 | (concat "${backlinkscount:16} ${functiontag} ${directories}${hierarchy}${othertags} ") 1961 | org-roam-node-annotation-function 1962 | (lambda (node) (+marginalia--time-colorful (org-roam-node-file-mtime node)))) 1963 | ) 1964 | #+end_src 1965 | 1966 | Sorting =org-roam-node-find= by last modified time seems the most intuitive for me. 1967 | 1968 | #+begin_src emacs-lisp :tangle no 1969 | (defun org-roam-node-find-by-mtime () 1970 | (find-file 1971 | (org-roam-node-file 1972 | (org-roam-node-read nil nil #'org-roam-node-read-sort-by-file-mtime)))) 1973 | 1974 | (advice-add 'org-roam-node-find :override #'org-roam-node-find-by-mtime) 1975 | #+end_src 1976 | 1977 | ***** Capture templates 1978 | #+begin_src emacs-lisp 1979 | (use-package! org-roam-capture 1980 | :config 1981 | (setq org-roam-capture-templates 1982 | `(("d" "default" plain "%?" 1983 | :target 1984 | (file+head "${slug}_%<%Y-%m-%d--%H-%M-%S>.org" 1985 | "#+title: ${title}\n#+created: %U\n#+filetags: %(completing-read \"Function tags: \" hp/org-roam-function-tags)\n#+startup: overview") 1986 | :unnarrowed t)))) 1987 | 1988 | (use-package! org-roam-dailies 1989 | :config 1990 | (setq org-roam-dailies-directory "journal/" 1991 | org-roam-dailies-capture-templates 1992 | '(("d" "daily" entry "* %?" 1993 | :target 1994 | (file+head "%<%Y-%m-%d>.org" 1995 | "#+title: %<%Y-%m-%d %a>\n#+filetags: journal\n#+startup: content\n#+created: %U\n\n") 1996 | :immediate-finish t))) 1997 | (map! :leader 1998 | :prefix "n" 1999 | (:prefix ("j" . "journal") 2000 | :desc "Arbitrary date" "d" #'org-roam-dailies-goto-date 2001 | :desc "Today" "j" #'org-roam-dailies-goto-today 2002 | :desc "Tomorrow" "m" #'org-roam-dailies-goto-tomorrow 2003 | :desc "Yesterday" "y" #'org-roam-dailies-goto-yesterday))) 2004 | 2005 | (use-package! websocket 2006 | :after org-roam) 2007 | 2008 | (use-package! org-roam-ui 2009 | :after org-roam 2010 | :commands (org-roam-ui-mode)) 2011 | #+end_src 2012 | 2013 | ***** Workspace creation 2014 | This is to automate creating a workspace for Org-roam 2015 | 2016 | #+begin_src emacs-lisp 2017 | (after! (org-roam) 2018 | (defadvice! yeet/org-roam-in-own-workspace-a (&rest _) 2019 | "Open all roam buffers in there own workspace." 2020 | :before #'org-roam-node-find 2021 | :before #'org-roam-node-random 2022 | :before #'org-roam-buffer-display-dedicated 2023 | :before #'org-roam-buffer-toggle 2024 | :before #'org-roam-dailies-goto-today 2025 | (when (modulep! :ui workspaces) 2026 | (+workspace-switch "Org-roam" t)))) 2027 | #+end_src 2028 | 2029 | ***** Org-roam-protocol 2030 | 2031 | #+begin_src emacs-lisp 2032 | (use-package! org-roam-protocol 2033 | :after (org-roam org-roam-dailies org-protocol) 2034 | :config 2035 | (add-to-list 2036 | 'org-roam-capture-ref-templates 2037 | `(;; Browser bookletmark template: 2038 | ;; javascript:location.href = 2039 | ;; 'org-protocol://roam-ref?template=w&ref=' 2040 | ;; + encodeURIComponent(location.href) 2041 | ;; + '&title=' 2042 | ;; + encodeURIComponent(document.getElementsByTagName("h1")[0].innerText) 2043 | ;; + '&hostname=' 2044 | ;; + encodeURIComponent(location.hostname) 2045 | ("w" "webref" entry "* ${title} ([[${ref}][${hostname}]])\n%?" 2046 | :target 2047 | (file+head 2048 | ,(concat org-roam-dailies-directory "%<%Y-%m>.org") 2049 | ,(string-join 2050 | '(":properties:" 2051 | ":roam_refs: %^{Key}" 2052 | ":end:" 2053 | "#+title: %<%Y-%m>" 2054 | "#+filetags: journal" 2055 | "#+startup: overview" 2056 | "#+created: %U" 2057 | "") "\n")) 2058 | :unnarrowed t)))) 2059 | #+end_src 2060 | **** Org-roam and Org-agenda itegration 2061 | Integrating Org-roam and Org-agenda might be complicated, since Org-roam pushes us towards making many =.org= files, and Org-agenda works best with a few, big =.org= files. 2062 | 2063 | The solution proposed in [[https://d12frosted.io/posts/2021-01-16-task-management-with-roam-vol5.html][this blog post]] is to dynamically update the variable =org-agenda-files=, so that Org-agenda only check for Org-roam files that contains certain tags. 2064 | In my case, the tags that are marked for inspection are =tasked= and =schedule=. 2065 | Org-roam files are automatically marked with =tasked= as long as it has any =TODO= heading. 2066 | Files with =schedule= tags are designated manually. 2067 | 2068 | #+begin_src emacs-lisp 2069 | (after! (org-agenda org-roam) 2070 | (defun vulpea-task-p () 2071 | "Return non-nil if current buffer has any todo entry. 2072 | 2073 | TODO entries marked as done are ignored, meaning the this 2074 | function returns nil if current buffer contains only completed 2075 | tasks." 2076 | (seq-find ; (3) 2077 | (lambda (type) 2078 | (eq type 'todo)) 2079 | (org-element-map ; (2) 2080 | (org-element-parse-buffer 'headline) ; (1) 2081 | 'headline 2082 | (lambda (h) 2083 | (org-element-property :todo-type h))))) 2084 | 2085 | (defun vulpea-task-update-tag () 2086 | "Update task tag in the current buffer." 2087 | (when (and (not (active-minibuffer-window)) 2088 | (vulpea-buffer-p)) 2089 | (save-excursion 2090 | (goto-char (point-min)) 2091 | (let* ((tags (vulpea-buffer-tags-get)) 2092 | (original-tags tags)) 2093 | (if (vulpea-task-p) 2094 | (setq tags (cons "task" tags)) 2095 | (setq tags (remove "task" tags))) 2096 | 2097 | ;; cleanup duplicates 2098 | (setq tags (seq-uniq tags)) 2099 | 2100 | ;; update tags if changed 2101 | (when (or (seq-difference tags original-tags) 2102 | (seq-difference original-tags tags)) 2103 | (apply #'vulpea-buffer-tags-set tags)))))) 2104 | 2105 | (defun vulpea-buffer-p () 2106 | "Return non-nil if the currently visited buffer is a note." 2107 | (and buffer-file-name 2108 | (string-prefix-p 2109 | (expand-file-name (file-name-as-directory org-roam-directory)) 2110 | (file-name-directory buffer-file-name)))) 2111 | 2112 | (defun vulpea-task-files () 2113 | "Return a list of note files containing 'task' tag." ; 2114 | (seq-uniq 2115 | (seq-map 2116 | #'car 2117 | (org-roam-db-query 2118 | [:select [nodes:file] 2119 | :from tags 2120 | :left-join nodes 2121 | :on (= tags:node-id nodes:id) 2122 | :where (or (like tag (quote "%\"task\"%")) 2123 | (like tag (quote "%\"schedule\"%")))])))) 2124 | 2125 | (defun vulpea-agenda-files-update (&rest _) 2126 | "Update the value of `org-agenda-files'." 2127 | (setq org-agenda-files (vulpea-task-files))) 2128 | 2129 | (add-hook 'find-file-hook #'vulpea-task-update-tag) 2130 | (add-hook 'before-save-hook #'vulpea-task-update-tag) 2131 | 2132 | (advice-add 'org-agenda :before #'vulpea-agenda-files-update) 2133 | (advice-add 'org-todo-list :before #'vulpea-agenda-files-update) 2134 | 2135 | ;; functions borrowed from `vulpea' library 2136 | ;; https://github.com/d12frosted/vulpea/blob/6a735c34f1f64e1f70da77989e9ce8da7864e5ff/vulpea-buffer.el 2137 | 2138 | (defun vulpea-buffer-tags-get () 2139 | "Return filetags value in current buffer." 2140 | (vulpea-buffer-prop-get-list "filetags" "[ :]")) 2141 | 2142 | (defun vulpea-buffer-tags-set (&rest tags) 2143 | "Set TAGS in current buffer. 2144 | 2145 | If filetags value is already set, replace it." 2146 | (if tags 2147 | (vulpea-buffer-prop-set 2148 | "filetags" (concat ":" (string-join tags ":") ":")) 2149 | (vulpea-buffer-prop-remove "filetags"))) 2150 | 2151 | (defun vulpea-buffer-tags-add (tag) 2152 | "Add a TAG to filetags in current buffer." 2153 | (let* ((tags (vulpea-buffer-tags-get)) 2154 | (tags (append tags (list tag)))) 2155 | (apply #'vulpea-buffer-tags-set tags))) 2156 | 2157 | (defun vulpea-buffer-tags-remove (tag) 2158 | "Remove a TAG from filetags in current buffer." 2159 | (let* ((tags (vulpea-buffer-tags-get)) 2160 | (tags (delete tag tags))) 2161 | (apply #'vulpea-buffer-tags-set tags))) 2162 | 2163 | (defun vulpea-buffer-prop-set (name value) 2164 | "Set a file property called NAME to VALUE in buffer file. 2165 | If the property is already set, replace its value." 2166 | (setq name (downcase name)) 2167 | (org-with-point-at 1 2168 | (let ((case-fold-search t)) 2169 | (if (re-search-forward (concat "^#\\+" name ":\\(.*\\)") 2170 | (point-max) t) 2171 | (replace-match (concat "#+" name ": " value) 'fixedcase) 2172 | (while (and (not (eobp)) 2173 | (looking-at "^[#:]")) 2174 | (if (save-excursion (end-of-line) (eobp)) 2175 | (progn 2176 | (end-of-line) 2177 | (insert "\n")) 2178 | (forward-line) 2179 | (beginning-of-line))) 2180 | (insert "#+" name ": " value "\n"))))) 2181 | 2182 | (defun vulpea-buffer-prop-set-list (name values &optional separators) 2183 | "Set a file property called NAME to VALUES in current buffer. 2184 | VALUES are quoted and combined into single string using 2185 | `combine-and-quote-strings'. 2186 | If SEPARATORS is non-nil, it should be a regular expression 2187 | matching text that separates, but is not part of, the substrings. 2188 | If nil it defaults to `split-string-default-separators', normally 2189 | \"[ \f\t\n\r\v]+\", and OMIT-NULLS is forced to t. 2190 | If the property is already set, replace its value." 2191 | (vulpea-buffer-prop-set 2192 | name (combine-and-quote-strings values separators))) 2193 | 2194 | (defun vulpea-buffer-prop-get (name) 2195 | "Get a buffer property called NAME as a string." 2196 | (org-with-point-at 1 2197 | (when (re-search-forward (concat "^#\\+" name ": \\(.*\\)") 2198 | (point-max) t) 2199 | (buffer-substring-no-properties 2200 | (match-beginning 1) 2201 | (match-end 1))))) 2202 | 2203 | (defun vulpea-buffer-prop-get-list (name &optional separators) 2204 | "Get a buffer property NAME as a list using SEPARATORS. 2205 | If SEPARATORS is non-nil, it should be a regular expression 2206 | matching text that separates, but is not part of, the substrings. 2207 | If nil it defaults to `split-string-default-separators', normally 2208 | \"[ \f\t\n\r\v]+\", and OMIT-NULLS is forced to t." 2209 | (let ((value (vulpea-buffer-prop-get name))) 2210 | (when (and value (not (string-empty-p value))) 2211 | (split-string-and-unquote value separators)))) 2212 | 2213 | (defun vulpea-buffer-prop-remove (name) 2214 | "Remove a buffer property called NAME." 2215 | (org-with-point-at 1 2216 | (when (re-search-forward (concat "\\(^#\\+" name ":.*\n?\\)") 2217 | (point-max) t) 2218 | (replace-match "")))) 2219 | ) 2220 | #+end_src 2221 | 2222 | **** Org-roam and citar integration 2223 | Citar integrates with Org-roam via =citar-org-roam.el=. 2224 | This makes the comand =citar-open-notes= (bind to ~SPC n b~) use Org-roam's template system. 2225 | The bibliography notes created this way will be set up with proper =ID= and =ROAM_REFS= properties. 2226 | The integration also comes with a nice inteface when following an org citation 2227 | 2228 | #+caption: Following a citation in Org-mode, with Citar and Org-roam integraion 2229 | [[file:pics/citar-org-roam-follow.png]] 2230 | 2231 | Here's the relevent part: 2232 | 2233 | #+begin_src emacs-lisp 2234 | (use-package citar-org-roam 2235 | :after citar org-roam 2236 | :no-require 2237 | :config 2238 | (setq citar-org-roam-subdir "literature" 2239 | citar-org-roam-note-title-template 2240 | (string-join 2241 | '("${author editor} (${year issued date}) ${title}" 2242 | "#+filetags: literature" 2243 | "#+startup: overview" 2244 | "#+options: toc:2 num:t" 2245 | "#+hugo_base_dir: ~/Dropbox/Blogs/hieutkt/" 2246 | "#+hugo_section: ./notes" 2247 | "#+hugo_custom_front_matter: :exclude true :math true" 2248 | "#+hugo_custom_front_matter: :bibinfo '((doi .\"${doi}\") (isbn . \"${isbn}\") (url . \"${url}\") (year . \"${year}\") (month . \"${month}\") (date . \"${date}\") (author . \"${author}\") (journal . \"${journal}\"))" 2249 | "#+hugo_series: \"Reading notes\"" 2250 | "#+hugo_tags:" 2251 | "" 2252 | "* What?" 2253 | "* Why?" 2254 | "* How?" 2255 | "* And?" 2256 | ) "\n")) 2257 | (citar-org-roam-mode)) 2258 | #+end_src 2259 | **** Backlinks count display 2260 | 2261 | #+begin_src emacs-lisp 2262 | (defface hp/org-roam-count-overlay-face 2263 | '((t :inherit org-list-dt :height 0.8)) 2264 | "Face for Org Roam count overlay.") 2265 | 2266 | (defun hp/org-roam--count-overlay-make (pos count) 2267 | (let* ((overlay-value (propertize 2268 | (concat "·" (format "%d" count) " ") 2269 | 'face 'hp/org-roam-count-overlay-face 'display '(raise 0.2))) 2270 | (ov (make-overlay pos pos (current-buffer) nil t))) 2271 | (overlay-put ov 'roam-backlinks-count count) 2272 | (overlay-put ov 'priority 1) 2273 | (overlay-put ov 'after-string overlay-value))) 2274 | 2275 | (defun hp/org-roam--count-overlay-remove-all () 2276 | (dolist (ov (overlays-in (point-min) (point-max))) 2277 | (when (overlay-get ov 'roam-backlinks-count) 2278 | (delete-overlay ov)))) 2279 | 2280 | (defun hp/org-roam--count-overlay-make-all () 2281 | (hp/org-roam--count-overlay-remove-all) 2282 | (org-element-map (org-element-parse-buffer) 'link 2283 | (lambda (elem) 2284 | (when (string-equal (org-element-property :type elem) "id") 2285 | (let* ((id (org-element-property :path elem)) 2286 | (count (caar 2287 | (org-roam-db-query 2288 | [:select (funcall count source) 2289 | :from links 2290 | :where (= dest $s1) 2291 | :and (= type "id")] 2292 | id)))) 2293 | (when (< 0 count) 2294 | (hp/org-roam--count-overlay-make 2295 | (org-element-property :end elem) 2296 | count))))))) 2297 | 2298 | (define-minor-mode hp/org-roam-count-overlay-mode 2299 | "Display backlink count for org-roam links." 2300 | :after-hook 2301 | (if hp/org-roam-count-overlay-mode 2302 | (progn 2303 | (hp/org-roam--count-overlay-make-all) 2304 | (add-hook 'after-save-hook #'hp/org-roam--count-overlay-make-all nil t)) 2305 | (hp/org-roam--count-overlay-remove-all) 2306 | (remove-hook 'after-save-hook #'hp/org-roam--count-overlay-remove-all t))) 2307 | 2308 | (add-hook 'org-mode-hook #'hp/org-roam-count-overlay-mode) 2309 | #+end_src 2310 | **** Carrying todos forwards 2311 | =org-roam-daily.el= provides a nice interface for daily journaling/note-taking in Emacs. 2312 | However, I want to make two related improvements. 2313 | 2314 | The first is that, due to habitual behavior, I've ended up with an excessive number of empty journal files. 2315 | We write a handy command to automatically search for empty Org-files in a folder and delete them. 2316 | 2317 | #+begin_src emacs-lisp 2318 | (defun hp/delete-empty-org-files (directory) 2319 | "Delete Org files in DIRECTORY that contain only drawers or keywords. 2320 | This function is meant to clean out empty org-roam-dailies files." 2321 | (interactive "DDirectory: ") 2322 | (let ((files (directory-files-recursively directory "\\.org$"))) 2323 | (dolist (file files) 2324 | (with-temp-buffer 2325 | (insert-file-contents file) 2326 | (goto-char (point-min)) 2327 | ;; Check if the file contains only drawers and keywords 2328 | (if (not (re-search-forward "^[^#+:].+$" nil t)) 2329 | (delete-file file)))))) 2330 | #+end_src 2331 | 2332 | The second problem is something I want from Org-journal: =org-journal-carryover-items= which moves all TODO headings from a previous journal entry to today's. 2333 | We are going to implement that by advising =org-roam-dailies-goto-today=. 2334 | 2335 | #+begin_src emacs-lisp 2336 | (defun hp/org-roam-get-previous-dailies-file () 2337 | "Get the file name for the most recent previous day's Org-roam dailies file." 2338 | (let ((files (org-roam-dailies--list-files)) 2339 | (today (format-time-string "%Y-%m-%d"))) 2340 | (cond ((> (length files) 1) 2341 | ;; Get the last and second-last files 2342 | (let ((last-file (nth (- (length files) 1) files)) 2343 | (second-last-file (nth (- (length files) 2) files))) 2344 | ;; Check if the last file is for today 2345 | (if (string-suffix-p (concat today ".org") last-file) 2346 | second-last-file 2347 | last-file))) 2348 | (t nil)))) ; Return nil if there's only one file (or none). 2349 | 2350 | 2351 | (defun hp/org-roam-migrate-todos (&rest _) 2352 | "Migrate TODOs from the previous day's Org-roam file to today's file." 2353 | (interactive) 2354 | (let ((yesterday-file (hp/org-roam-get-previous-dailies-file)) 2355 | (today-file (buffer-file-name)) 2356 | (todo-regexp (concat "^\\*+ " (regexp-opt org-not-done-keywords)))) 2357 | (when (and yesterday-file (file-exists-p yesterday-file)) 2358 | (with-current-buffer (find-file-noselect yesterday-file) 2359 | (goto-char (point-min)) 2360 | (while (re-search-forward todo-regexp nil t) 2361 | (let ((element (org-element-at-point))) 2362 | (when (eq (car element) 'headline) 2363 | (let ((tree (buffer-substring (org-element-property :begin element) 2364 | (org-element-property :end element)))) 2365 | (with-current-buffer (find-file-noselect today-file) 2366 | (goto-char (point-max)) 2367 | (insert "\n" tree) 2368 | (save-buffer)) 2369 | ;; After inserting, delete the tree from the original file 2370 | (delete-region (org-element-property :begin element) 2371 | (org-element-property :end element))))) 2372 | (save-buffer) 2373 | ;; Delete the empty file if needed 2374 | (hp/delete-empty-org-files (file-name-directory yesterday-file)) 2375 | (message " Found TODO(s) from the last journal entry... carried them over!")))) 2376 | (save-buffer))) 2377 | #+end_src 2378 | 2379 | After carrying all todos forwards, this advise delete the previous journal entry if they ended up in an empty state. 2380 | 2381 | #+begin_src emacs-lisp :tangle no 2382 | (advice-add 'org-roam-dailies-goto-today :after #'hp/org-roam-migrate-todos) 2383 | #+end_src 2384 | 2385 | **** Handy command for journaling 2386 | The following comand create a new heading and add current time to it. 2387 | 2388 | #+begin_src emacs-lisp 2389 | (defun hp/org-insert-timestamped-bullet () 2390 | "Insert a new bullet in Org-mode with the current timestamp." 2391 | (interactive) 2392 | (+org/insert-item-below 1) 2393 | (insert (format-time-string "%H:%M "))) 2394 | 2395 | ;; Bind the function to a key combination, for example C-c t 2396 | (define-key org-mode-map (kbd "C-c t") 'hp/org-insert-timestamped-bullet) 2397 | #+end_src 2398 | 2399 | #+RESULTS: 2400 | : hp/org-insert-timestamped-bullet 2401 | *** Org-download 2402 | 2403 | #+begin_src emacs-lisp 2404 | (use-package! org-download 2405 | :config 2406 | (add-hook 'dired-mode-hook 'org-download-enable) 2407 | ;; Change how inline images are displayed 2408 | (setq org-download-display-inline-images nil)) 2409 | #+end_src 2410 | 2411 | ** R 2412 | First programming language that I learnt. 2413 | Most of the time, the interation provided by ESS-mode is excellent and I can be productive with it. 2414 | Syntax-highlighting in =ess-r-mode= is not so spectacular, however. 2415 | Hopefully this will get better once =tree-sitter= is better integrated into Emacs. 2416 | 2417 | #+begin_src emacs-lisp 2418 | (use-package! ess 2419 | :config 2420 | (set-popup-rules! 2421 | '(("^\\*R:*\\*$" :side right :size 0.5 :ttl nil))) 2422 | (setq ess-R-font-lock-keywords 2423 | '((ess-R-fl-keyword:keywords . t) 2424 | (ess-R-fl-keyword:constants . t) 2425 | (ess-R-fl-keyword:modifiers . t) 2426 | (ess-R-fl-keyword:fun-defs . t) 2427 | (ess-R-fl-keyword:assign-ops . t) 2428 | (ess-R-fl-keyword:%op% . t) 2429 | (ess-fl-keyword:fun-calls . t) 2430 | (ess-fl-keyword:numbers . t) 2431 | (ess-fl-keyword:operators . t) 2432 | (ess-fl-keyword:delimiters . t) 2433 | (ess-fl-keyword:= . t) 2434 | (ess-R-fl-keyword:F&T . t))) 2435 | (map! (:map (ess-mode-map inferior-ess-mode-map) 2436 | :g ";" #'ess-insert-assign))) 2437 | #+end_src 2438 | 2439 | ** Stata 2440 | Even though I try to use Stata as little as I can, sometimes it's unavoidable, especially in collaboration with applied economists. 2441 | I usually use the [[https://github.com/kylebarron/stata_kernel][Jupyter Stata kernel]] in these situations and it's decent, but sometimes I really miss the excellent editing environment that I have in Emacs. 2442 | In preparation, here's the little configurations if I ever decide to use Stata in Emacs: 2443 | 2444 | #+begin_src emacs-lisp 2445 | (use-package! ess-stata-mode 2446 | :after ess 2447 | :config 2448 | (setq inferior-STA-start-args "" 2449 | inferior-STA-program (executable-find "stata") 2450 | inferior-STA-program-name (executable-find "stata")) 2451 | (add-to-list 'org-src-lang-modes '("jupyter-stata" . stata))) 2452 | #+end_src 2453 | 2454 | ** Python 2455 | Python is widely used and thus is extensively supported everywhere. 2456 | While I prefer Julia for numerical computing and R for econometrics and data visualization, Python is good in pretty much everything else. 2457 | I am happy with most the defaults given in Doom Emacs, so my custom configuration in this section is only minimal. 2458 | 2459 | #+begin_src emacs-lisp 2460 | (use-package! python 2461 | :config 2462 | (set-popup-rules! 2463 | '(("^\\*Python:*\\*$" :side right :size 0.5 :ttl nil)))) 2464 | #+end_src 2465 | 2466 | ** Julia 2467 | =lsp-julia= tries to do the smart thing of auto-detecting the project environment as well as the correct path to the =LanguageServer.jl=. 2468 | I want it to do the dumb-but-simple thing of using the global installation of =LanguageServer.jl=. 2469 | 2470 | #+begin_src emacs-lisp 2471 | (after! lsp-julia 2472 | (setq lsp-julia-flags '("--startup-file=no" "--history-file=no"))) 2473 | #+end_src 2474 | 2475 | The rest of the configurations is straight forward. 2476 | 2477 | #+begin_src emacs-lisp 2478 | (after! julia-mode 2479 | (add-hook 'julia-mode-hook #'rainbow-delimiters-mode-enable)) 2480 | 2481 | (use-package! ob-julia 2482 | :config 2483 | (setq org-babel-julia-backend 'julia-snail)) 2484 | #+end_src 2485 | 2486 | Julia-snail is good. 2487 | 2488 | #+begin_src emacs-lisp 2489 | (after! julia-snail 2490 | (map! :map julia-snail-mode-map 2491 | :g "C-c C-z" #'julia-snail 2492 | :g "C-c C-l" #'julia-snail-send-line 2493 | :map julia-repl-mode-map 2494 | "C-c C-a" nil ;julia-snail-package-activate 2495 | "C-c C-z" nil ;julia-snail 2496 | "C-c C-c" nil ;julia-snail-send-top-level-form 2497 | "C-c C-d" nil ;julia-snail-doc-lookup 2498 | "C-c C-e" nil ;julia-snail-send-dwim 2499 | "C-c C-k" nil ;julia-snail-send-buffer-file 2500 | "C-c C-l" nil ;julia-snail-send-line 2501 | :map vterm-mode-map 2502 | :i "C-c C-z" nil 2503 | :map markdown-view-mode-map 2504 | :n "q" #'kill-this-buffer)) 2505 | #+end_src 2506 | 2507 | Some popup rules to make workflows more consistent. 2508 | 2509 | #+begin_src emacs-lisp 2510 | (after! julia-repl 2511 | (set-popup-rules! 2512 | '(("^\\*julia.*\\*$" :side right :size 0.5 :ttl nil :quit nil) 2513 | ("^\\*julia.*\\* documentation" :side bottom :size 0.4 :ttl nil) 2514 | ("^\\*julia.*\\* mm" :select t :size #'+popup-shrink-to-fit :modeline t)))) 2515 | #+end_src 2516 | ** MATLAB 2517 | Rudimentary =matlab-mode= setups. 2518 | 2519 | #+begin_src emacs-lisp 2520 | (use-package! matlab 2521 | :commands (matlab-shell matlab-mode) 2522 | :mode ("\\.m\\'" . matlab-mode) 2523 | :hook (matlab-mode . rainbow-delimiters-mode) 2524 | :config 2525 | ;; LSP integration 2526 | (add-to-list 'lsp-language-id-configuration '(matlab-mode . "matlab")) 2527 | ;; setup matlab-shell 2528 | (setq matlab-shell-command (executable-find "matlab")) 2529 | (setq matlab-shell-command-switches '("-nodesktop")) 2530 | ;; popup rules 2531 | (set-popup-rules! 2532 | '(("^\\*MATLAB.*\\*$" :side right :size 0.5 :ttl nil :quit nil))) 2533 | ;; Keybindings 2534 | (map! :map matlab-mode-map 2535 | :g "C-c C-z" #'matlab-show-matlab-shell-buffer 2536 | :map matlab-shell-mode-map 2537 | :i "C-c C-z" #'other-window)) 2538 | #+end_src 2539 | 2540 | ** LaTeX 2541 | A good bulk of any good research should go into writing, and once your writing topic gets slightly technical, you need the goodness of LaTeX. 2542 | These days I don't really write =.tex= files directly in Emacs and from what I hear, the built-in [[https://www.gnu.org/software/auctex/][AUCTeX]] is awesome for that. 2543 | Most of my writings in Emacs is done in Org-mode. 2544 | However, Org-mode inherits quite a few things from LaTeX-mode, so some configuration is needed here, most of which relates to syntax-highlighting of LaTeX fragments and snippets for fast insertion of math equations. 2545 | *** Better defaults 2546 | #+begin_src emacs-lisp 2547 | (after! tex 2548 | (setq-default TeX-master nil 2549 | TeX-view-program-list '(("Evince" "evince --page-index=%(outpage) %o")) 2550 | TeX-view-program-selection '((output-pdf "Evince")))) 2551 | #+end_src 2552 | *** Turning off script fontification 2553 | Subscript and superscript fontification looks janky to me, so let's turn them off. 2554 | 2555 | #+begin_src emacs-lisp 2556 | (setq font-latex-fontify-script nil) 2557 | #+end_src 2558 | 2559 | *** Make math mode delimiters less visible. 2560 | We're going to apply this to Org-mode as well. 2561 | 2562 | #+begin_src emacs-lisp 2563 | (defface unimportant-latex-face 2564 | '((t :inherit font-lock-comment-face :weight extra-light)) 2565 | "Face used to make \\(\\), \\[\\] less visible." 2566 | :group 'LaTeX-math) 2567 | 2568 | (font-lock-add-keywords 'latex-mode `(("\\\\[]()[]" 0 'unimportant-latex-face prepend)) 'end) 2569 | (font-lock-add-keywords 'org-mode `(("\\\\[]()[]" 0 'unimportant-latex-face prepend)) 'end) 2570 | #+end_src 2571 | 2572 | *** CDLatex-mode and LaTeX-auto-activating-snippets 2573 | =cdlatex-mode= is useful when writing math equations. 2574 | It support Org-mode out of the box. 2575 | 2576 | #+begin_src emacs-lisp 2577 | (after! cdlatex 2578 | (setq cdlatex-math-modify-alist 2579 | '((?d "\\mathbb" nil t nil nil) 2580 | (?D "\\mathbbm" nil t nil nil)) 2581 | cdlatex-env-alist 2582 | '(("cases" "\\begin{cases} ? \\end{cases}" nil) 2583 | ("matrix" "\\begin{matrix} ? \\end{matrix}" nil) 2584 | ("pmatrix (parenthesis)" "\\begin{pmatrix} ? \\end{pmatrix}" nil) 2585 | ("bmatrix [braces]" "\\begin{bmatrix} ? \\end{bmatrix}" nil)))) 2586 | #+end_src 2587 | 2588 | =laas-mode= automates /even more/. 2589 | The list of snippets enabled by this package is enormous, best to check their README if you have any doubt. 2590 | 2591 | #+begin_src emacs-lisp 2592 | (use-package! laas 2593 | :hook (org-mode . laas-mode) 2594 | :config 2595 | (setq laas-enable-auto-space nil) 2596 | ;; ;; For some reason (texmathp) returns t everywhere in org buffer 2597 | ;; ;; which is not every useful, so here's a fix 2598 | ;; (add-hook 'org-cdlatex-mode-hook 2599 | ;; (lambda () (advice-remove 'texmathp 'org--math-always-on))) 2600 | ;;More snippets 2601 | (aas-set-snippets 'laas-mode 2602 | ;; Condition: Not in math environment and not in a middle of a word 2603 | :cond (lambda nil (and (not (laas-org-mathp)) (memq (char-before) '(10 40 32)))) 2604 | "mk" (lambda () (interactive) (yas-expand-snippet "\\\\( $0 \\\\)")) 2605 | "mmk" (lambda () (interactive) (yas-expand-snippet "\\[ $0 \\]")) 2606 | "citet" (lambda () (interactive) (yas-expand-snippet "\[cite/t:@$0\]")) 2607 | ";>" "\\( \\rightarrow \\)" 2608 | ;; Condition: Math environment 2609 | :cond #'laas-org-mathp 2610 | "qed" "\\blacksquare" 2611 | ",," "\\,," 2612 | ".," "\\,." 2613 | ";0" "\\emptyset" 2614 | ";." "\\cdot" 2615 | ",." nil ;disable the annoying \vec{} modifier 2616 | "||" nil 2617 | "lr||" (lambda () (interactive) (yas-expand-snippet "\\lVert $0 \\rVert")) 2618 | "pdv" (lambda () (interactive) (yas-expand-snippet "\\frac{\\partial $1}{\\partial $2}")) 2619 | "dd" (lambda () (interactive) (yas-expand-snippet "~\\mathrm{d}")) 2620 | ;; Condition: Math environment, modify last object on the left 2621 | :cond #'laas-object-on-left-condition 2622 | "hat" (lambda () (interactive) (laas-wrap-previous-object "hat")) 2623 | "ubar" (lambda () (interactive) (laas-wrap-previous-object "underbar")) 2624 | "bar" (lambda () (interactive) (laas-wrap-previous-object "bar")) 2625 | "uline" (lambda () (interactive) (laas-wrap-previous-object "underline")) 2626 | "oline" (lambda () (interactive) (laas-wrap-previous-object "overline")) 2627 | "dot" (lambda () (interactive) (laas-wrap-previous-object "dot")) 2628 | "tilde" (lambda () (interactive) (laas-wrap-previous-object "tilde")) 2629 | "TXT" (lambda () (interactive) (laas-wrap-previous-object "text")) 2630 | "ON" (lambda () (interactive) (laas-wrap-previous-object "operatorname")) 2631 | "BON" (lambda () (interactive) (laas-wrap-previous-object 2632 | '("\\operatorname{\\mathbf{" . "}}"))) 2633 | "tt" "_{t}" 2634 | "tp1" "_{t+1}" 2635 | "tm1" "_{t-1}" 2636 | "**" "^{\\ast}")) 2637 | #+end_src 2638 | ** Elfeeds 2639 | 2640 | #+begin_src emacs-lisp 2641 | (use-package! elfeed 2642 | :commands (elfeed) 2643 | :custom 2644 | (rmh-elfeed-org-files (list (concat org-directory "/Feeds/elfeed.org"))) 2645 | (elfeed-db-directory (concat org-directory "/Feeds/elfeed.db/")) 2646 | (elfeed-goodies/wide-threshold 0.2) 2647 | :bind ("" . #'elfeed) 2648 | :config 2649 | ;; (defun hp/elfeed-entry-line-draw (entry) 2650 | ;; (insert (format "%s" (elfeed-meta--plist entry)))) 2651 | (defun hp/elfeed-entry-line-draw (entry) 2652 | "Print ENTRY to the buffer." 2653 | (let* ((date (elfeed-search-format-date (elfeed-entry-date entry))) 2654 | (title (or (elfeed-meta entry :title) (elfeed-entry-title entry) "")) 2655 | (title-faces (elfeed-search--faces (elfeed-entry-tags entry))) 2656 | (feed (elfeed-entry-feed entry)) 2657 | (feed-title 2658 | (when feed 2659 | (or (elfeed-meta feed :title) (elfeed-feed-title feed)))) 2660 | (tags (mapcar #'symbol-name (elfeed-entry-tags entry))) 2661 | (tags-str (concat "[" (mapconcat 'identity tags ",") "]")) 2662 | (title-width (- (window-width) elfeed-goodies/feed-source-column-width 2663 | elfeed-goodies/tag-column-width 4)) 2664 | (title-column (elfeed-format-column 2665 | title (elfeed-clamp 2666 | elfeed-search-title-min-width 2667 | title-width 2668 | title-width) 2669 | :left)) 2670 | (tag-column (elfeed-format-column 2671 | tags-str (elfeed-clamp (length tags-str) 2672 | elfeed-goodies/tag-column-width 2673 | elfeed-goodies/tag-column-width) 2674 | :left)) 2675 | (feed-column (elfeed-format-column 2676 | feed-title (elfeed-clamp elfeed-goodies/feed-source-column-width 2677 | elfeed-goodies/feed-source-column-width 2678 | elfeed-goodies/feed-source-column-width) 2679 | :left)) 2680 | (entry-score (elfeed-format-column (number-to-string (elfeed-score-scoring-get-score-from-entry entry)) 6 :left)) 2681 | ;; (entry-authors (concatenate-authors 2682 | ;; (elfeed-meta entry :authors))) 2683 | ;; (authors-column (elfeed-format-column entry-authors elfeed-goodies/tag-column-width :left)) 2684 | ) 2685 | (if (>= (window-width) (* (frame-width) elfeed-goodies/wide-threshold)) 2686 | (progn 2687 | (insert (propertize entry-score 'face 'elfeed-search-feed-face) " ") 2688 | (insert (propertize date 'face 'elfeed-search-date-face) " ") 2689 | (insert (propertize feed-column 'face 'elfeed-search-feed-face) " ") 2690 | (insert (propertize tag-column 'face 'elfeed-search-tag-face) " ") 2691 | ;; (insert (propertize authors-column 'face 'elfeed-search-tag-face) " ") 2692 | (insert (propertize title 'face title-faces 'kbd-help title)) 2693 | ) 2694 | (insert (propertize title 'face title-faces 'kbd-help title))))) 2695 | 2696 | (defun concatenate-authors (authors-list) 2697 | "Given AUTHORS-LIST, list of plists; return string of all authors concatenated." 2698 | (if (> (length authors-list) 1) 2699 | (format "%s et al." (plist-get (nth 0 authors-list) :name)) 2700 | (plist-get (nth 0 authors-list) :name))) 2701 | 2702 | (defun search-header/draw-wide (separator-left separator-right search-filter stats db-time) 2703 | (let* ((update (format-time-string "%Y-%m-%d %H:%M:%S %z" db-time)) 2704 | (lhs (list 2705 | (powerline-raw (-pad-string-to "Score" (- 5 5)) 'powerline-active1 'l) 2706 | (funcall separator-left 'powerline-active1 'powerline-active2) 2707 | (powerline-raw (-pad-string-to "Date" (- 9 4)) 'powerline-active2 'l) 2708 | (funcall separator-left 'powerline-active2 'powerline-active1) 2709 | (powerline-raw (-pad-string-to "Feed" (- elfeed-goodies/feed-source-column-width 4)) 'powerline-active1 'l) 2710 | (funcall separator-left 'powerline-active1 'powerline-active2) 2711 | (powerline-raw (-pad-string-to "Tags" (- elfeed-goodies/tag-column-width 6)) 'powerline-active2 'l) 2712 | (funcall separator-left 'powerline-active2 'mode-line) 2713 | (powerline-raw "Subject" 'mode-line 'l))) 2714 | (rhs (search-header/rhs separator-left separator-right search-filter stats update))) 2715 | (concat (powerline-render lhs) 2716 | (powerline-fill 'mode-line (powerline-width rhs)) 2717 | (powerline-render rhs)))) 2718 | 2719 | ;; Tag entry as read when open 2720 | (defadvice! hp/mark-read (&rest _) 2721 | :before 'elfeed-search-show-entry 2722 | :before 'elfeed-search-browse-url 2723 | (let* ((offset (- (line-number-at-pos) elfeed-search--offset)) 2724 | (current-entry (nth offset elfeed-search-entries))) 2725 | (elfeed-tag-1 current-entry 'read))) 2726 | 2727 | ;; Faces for diferent kinds of feeds 2728 | (defface hp/elfeed-blog 2729 | `((t :foreground ,(doom-color 'blue))) 2730 | "Marks a Elfeed blog.") 2731 | (push '(blog hp/elfeed-blog) 2732 | elfeed-search-face-alist) 2733 | (push '(read elfeed-search-title-face) 2734 | elfeed-search-face-alist) 2735 | 2736 | ;; Variables 2737 | (setq elfeed-search-print-entry-function 'hp/elfeed-entry-line-draw 2738 | elfeed-search-filter "@8-weeks-ago -bury ")) 2739 | 2740 | #+end_src 2741 | 2742 | Elfeed-score helps with keeping track of the more important entries. 2743 | 2744 | #+begin_src emacs-lisp 2745 | (use-package! elfeed-score 2746 | :after elfeed 2747 | :custom 2748 | (elfeed-score-score-file (concat org-directory "/Feeds/elfeed.score")) 2749 | :config 2750 | (map! :map elfeed-search-mode-map 2751 | :n "=" elfeed-score-map) 2752 | (elfeed-score-enable)) 2753 | #+end_src 2754 | 2755 | Like Org-roam, Elfeed should be opened in it's own workspace: 2756 | 2757 | #+begin_src emacs-lisp 2758 | (after! (elfeed) 2759 | (defadvice! hp/elfeed-in-own-workspace (&rest _) 2760 | "Open Elfeeds in its own workspace." 2761 | :before #'elfeed 2762 | (when (modulep! :ui workspaces) 2763 | (+workspace-switch "Elfeeds" t)))) 2764 | #+end_src 2765 | 2766 | * Footnotes 2767 | 2768 | [fn:1] For example, this is a footnote. But on website this should be rendered beside main text (as sidenotes). 2769 | --------------------------------------------------------------------------------