For years I have been perfecting my Emacs configuration, it is now the essential part of my digital life. This is my personal configuration but you may also find it useful to your needs. Feel free to grab some of my config snippets to work for you.
Here is a list of incomplete features you get from this config set:
- Clean configuration with use-package
- Many programming language support: C/C++, Python, typescript, golang, glsl and many others.
- Some of my collected code snippets for different languages.
- Code auto-complete support via eglot and Company-mode.
- Completion framework using Ivy.
- keybinding look-up with which-key.
- FlySpell support via Hunspell.
- minimal LLM support through Ellama.
- many others.
Some of the packages requires not only Emacs-Lisp code but also other binaries to work. Here is the list of binaries required for all the features.
- Clang installation for LSP back-end.
- Compiler or Interpreter for the targeting programming languages.
- SQLite3 for org-roam.
- Latex installation for
org-latex-preview - ripgrep for refactoring support.
- Hunspell installation for flyspell.
- Ollama for ellama package.
Note that none of those are hard requirements, you will simply lose some features if you don’t have them.
On Linux, those run-time requires can be easily satisfied with package managers
like apt-get or dnf install. On windows, it’s another story, for this purpose,
I maintain a dotemacs-msbin for those dependencies on Windows.
On MacOS binaries can be installed using Homebrew. You can use the following commands to setup
# install emacs
brew tap d12frosted/emacs-plus
#by default install the Emacs29
brew install emacs-plus --with-imagemagick --with-native-comp
# the packages, binuitls may cause troubles on M4 (for windres)
brew install ripgrep aspell ninja binutils gnupg
#for org-downloads
brew install pngpaste
# the fonts
brew install --cask font-iosevka font-iosevka-aile font-fira-sans font-fira-code
# The texlive. For minimal install you can try the basicTex then the additional
# packages
brew install basictex ghostscript imagmagick
#dvipng is required for org-latex-preview
sudo tlmgr update --self && sudo tlmgr install dvipng wrapfig capt-of
# install vterm for aider
brew install libvterm
# install pinentry-mac for auth-info
brew install pinentry-mac
# finally start emacs service, recommand emacs-plus packages
brew services start emacs-plus@30There are also some adjustment settings I made for MacOS, it is totally personal, you do not need to apply to your situation.
All my functions are defined with prefix my/.
(defun my/dotfile-dir ()
(file-name-directory (or (buffer-file-name) load-file-name)))
(defun my/concat-path (&rest parts)
(cl-reduce (lambda (a b) (expand-file-name b a)) parts))
(defun my/merge-list-to-list (dst-list src-list)
(dolist (item src-list) (add-to-list dst-list item)))
(defun my/merge-list-buffer-local (dst-list src-list)
;;not that dst-list need to be quoted
(make-variable-buffer-local dst-list)
(my/merge-list-to-list dst-list src-list))
(defun my/filename ()
"Copy the filename of the current buffer."
(interactive)
(kill-new (buffer-name (window-buffer (minibuffer-selected-window)))))
(defun my/full-path ()
"Copy the full path of the current buffer."
(interactive)
(kill-new (buffer-file-name (window-buffer (minibuffer-selected-window)))))
(defun my/test-port-open (host port)
"Test if a TCP PORT at HOST is open."
(condition-case nil
(progn
(let ((stream (open-network-stream "test-stream" nil host port)))
(delete-process stream)
t))
(error nil)))
Sometimes you need to modify .dir-locals.el while editing. Following two functions helps you reload current buffer with modified .dir-locals.el
(defun my/reload-dir-locals-for-current-buffer ()
"reload dir locals for the current buffer"
(interactive)
(let ((enable-local-variables :all))
(hack-dir-local-variables-non-file-buffer)))
(defun my/reload-dir-locals-for-all-buffer-in-this-directory ()
"For every buffer with the same `default-directory` as the
current buffer's, reload dir-locals."
(interactive)
(let ((dir default-directory))
(dolist (buffer (buffer-list))
(with-current-buffer buffer
(when (equal default-directory dir)
(my/reload-dir-locals-for-current-buffer)))))) Emacs inherits your proxy ENV such as http_proxy and https_proxy. I have two functions when you need to toggle on/off proxies.
(defun my/disable-proxy ()
"Disable the proxy used in emacs"
(interactive)
(setq url-proxy-services
`(("http" . nil)
("https" . nil)
("no_proxy" . ,(getenv "no_proxy"))))
;;backup the proxy settings
(setenv "http_proxy_backup" (getenv "http_proxy"))
(setenv "https_proxy_backup" (getenv "https_proxy"))
(setenv "ftp_proxy_backup" (getenv "ftp_proxy"))
;;clean up the proxy settings
(setenv "http_proxy" nil)
(setenv "https_proxy" nil)
(setenv "ftp_proxy" nil)
)
(defun my/enable-proxy ()
"Re-enable proxy from environment variables"
(interactive)
(setenv "http_proxy" (getenv "http_proxy_backup"))
(setenv "https_proxy" (getenv "https_proxy_backup"))
(setenv "ftp_proxy" (getenv "ftp_proxy_backup"))
(setq url-proxy-services
`(("http" . ,(getenv "http_proxy"))
("https" . ,(getenv "https_proxy"))
("ftp_proxy" . ,(getenv "ftp_proxy"))
("no_proxy" . ,(getenv "no_proxy"))))
)
;;get the current proxy
(defun my/has-or-get-proxy()
(or (getenv "https_proxy")
(getenv "http_proxy")))(use-package uuidgen :ensure t :defer t :pin melpa
:init
(defun my/insert-uuid ()
"insert UUID at the point"
(interactive)
(insert (uuidgen-4)))
)(use-package perdev :load-path "lisp")This tool can be used by LLM
(use-package ytb-sub-dl
:load-path "lisp"
:when (executable-find "yt-dlp")
:commands (ytb-sub-dl-insert-transcript))menu bar configuration. I disable tool bar and scroll bar for a minimalist look. Also, disable the bell using visbible-bell and enable some global modes.
(display-time)
(when (or (display-graphic-p) (daemonp))
(tool-bar-mode -1)
(scroll-bar-mode -1)
(setq visible-bell 1))
;;enabled global modes
(save-place-mode 1)
(global-auto-revert-mode t)
(column-number-mode 1)
(delete-selection-mode 1)
;;default to text mode
(setq-default major-mode 'text-mode)
;;displaying line numbers
(add-hook 'prog-mode-hook 'display-line-numbers-mode)
(setq display-line-numbers-type 'relative)
;;highlight current line
(add-hook 'prog-mode-hook 'hl-line-mode)
(diminish 'hl-line-mode)
;; NOTE: do not Ignore case when completing file names, cause very annoying bugs in lldb that it cannot resolve the symbols.
(setq read-file-name-completion-ignore-case nil)(setenv "LC_CTYPE" "en_US.UTF-8")
(setenv "LANG" "en_US.UTF-8")
(set-language-environment "UTF-8")
(set-locale-environment "en_US.UTF-8")
(prefer-coding-system 'utf-8)
(set-default-coding-systems 'utf-8)
(set-terminal-coding-system 'utf-8-unix)Without proper LC_CTYPE locales, you will get config_get_locale_encoding error from python lsp servers.
copied from emacswiki
(setq
backup-by-copying t ; don't clobber symlinks
backup-directory-alist
'(("." . "~/.saves/")) ; don't litter my fs tree
delete-old-versions t
kept-new-versions 6
kept-old-versions 2
version-control t) ; use versioned backups(use-package recentf
:init
(setq
recentf-save-file (expand-file-name "recentf" user-emacs-directory)
recentf-max-saved-items 10000
recentf-max-menu-items 5000
)
(recentf-mode 1)
(run-at-time nil (* 5 60) 'recentf-save-list))(use-package pomodoro :defer t
:vc (:fetcher github :repo "baudtack/pomodoro.el")
:commands pomodoro-start
:custom
(pomodoro-work-start-sound (expand-file-name "assets/stop1.mp3" (my/dotfile-dir)))
(pomodoro-break-start-sound (expand-file-name "assets/rest.mp3" (my/dotfile-dir)))
:config
(pomodoro-add-to-mode-line)
(cond ((executable-find "mpg123") (setq pomodoro-sound-player "mpg123"))
((executable-find "mpv") (setq pomodoro-sound-player "mpv"))
((executable-find "vlc") (setq pomodoro-sound-player "vlc"))
((executable-find "mplayer") (setq pomodoro-sound-player "mplayer"))
((executable-find "cmus") (setq pomodoro-sound-player "cmus"))
;;if no player found, just skip it
(t (setq pomodoro-play-sounds nil))));; also set allow-loopback-entry in gpg-agent.conf and gpgconf --reload gpg-agent
;;
;; this means clients like Emacs can get the password in their own way and push to gpg
;; for this to work with Emacs, set epa-pinentry-mode to 'loopback in Emacs
(setq epg-pinentry-mode 'loopback)Devil Mode is a non-intrusive modifier-free editing experience, compared to evil mode or god mode. With this, I can actually forget about the keyboard mapping problems across windows and Mac.
(use-package devil :ensure t :diminish :disabled
:init
(global-devil-mode)
(devil-set-key (kbd ";")))See MacOS adjustments
(require 'org-funcs)
(defun my/org-dir-set (dir)
(and dir (not (string= dir "")) (file-exists-p dir)))
(defun my/org-file (path)
(expand-file-name path (perdev-get-evaluated-value
'my/default-org-dir
org-directory ;;default value
org-directory ;;argument
)))(use-package org :ensure t :defer t
:mode (("\\.org$" . org-mode))
:commands org-capture
:custom
(org-log-done 'time)
(org-clock-persist 'history)
(org-adapt-indentation nil)
(org-image-actual-width 300) ;;set to 300px
;;setup the column, this max length for the first level we can go, maybe we
;;can somehow calculate it?
(org-tags-column -54)
(org-image-actual-width 600)
;;getting rid of leading white-space in code indentation
(org-edit-src-content-indentation 0)
;; this removes the leading white space when you done editing
(org-src-preserve-indentation nil)
;;faces
(org-todo-keywords '((sequence "TODO" "DOIN" "|" "DONE" "PEND" "CANC")))
:hook
((org-after-todo-statistics . org-funcs-summary-todo)
(org-checkbox-statistics . org-funcs-checkbox-todo)
(org-mode . org-funcs-define-faces))
;; I am not sure this global key setting is good or not, capture stuff
;; globally is great
:bind (("C-c o a" . org-agenda)
("C-c o c" . org-capture)
<<ORG_KEYS>>
:map org-mode-map
("C-c o C-w" . org-refile)
("M-<left>" . org-metaleft)
("M-<right>" . org-metaright)
("M-<up>" . org-metaup)
("M-<down>" . org-metadown))
:init
<<ORG_SETUP>>
;; enable images
(setq org-startup-with-inline-images t)
;;activate babel languages
:config
;;note files
<<ORG_NOTE_AGENDA>>
;;latex setup
<<ORG_LATEX>>
(setf (cdr (assoc 'file org-link-frame-setup)) 'find-file)
(org-clock-persistence-insinuate)
;; I just use PEND to define stuck projects.
(setq org-stuck-projects
'("/-DONE-CANC" ("DOIN" "TODO") nil ""))
;;capture templates
(setq org-capture-templates
`(
<<ORG_CAPTURE>>
))
(org-funcs-load-babel-compiler))org-directory has to have trailing “/”
(setq org-directory (if (my/org-dir-set (getenv "ORG_DIR"))
(getenv "ORG_DIR")
"~/org/")) I divide my agenda files to the following:
(setq org-default-notes-file
(my/concat-path org-directory "notes.org"))
(setq org-agenda-files (perdev-get-evaluated-value
'my/org-agenda-files '() org-directory))Show unplanned tasks in global TODO list.
(setq org-deadline-warning-days 7)
;;ignore what's already done
(setq org-agenda-skip-scheduled-if-done t)
(setq org-agenda-skip-deadline-if-done t)
;;ignore todo items that has dead lines since they will appear in agenda.
(setq org-agenda-todo-ignore-deadlines 'near)
(setq org-agenda-todo-ignore-scheduled 'all)
;;avoid duplicates deadlines in agenda
(setq org-agenda-skip-scheduled-if-deadline-is-shown t)
;;this works when you place a schedule, so it will not duplicate
(setq org-agenda-skip-deadline-prewarning-if-scheduled t)log the agenda states into drawer, instead of insert inside org files.
(setq org-log-into-drawer t) It will prevent from inserting a state directly under headings.
- State “DONE” from “DOIN” [2024-02-26 Mon 08:50]
Instead it will be inside a :LOGBOOK:
;; misc tasks, moving coding or writing later?
("m" "Miscs" entry
(file+headline ,(my/org-file "miscs.org") "Captures")
"** TODO %?\n%i\n %a" :prepend t);; my ideas
("s" "Thoughts" entry
(file+headline ,(my/org-file "thoughts.org") "Ideas")
"* %?\n %i\n \n\n"
:prepend t);; Learning items
("r" "Reading" entry
(file+headline ,(my/org-file "reading.org") "Articles")
"** TODO %?\n%i\n %^L\n \n"
:prepend t) ;;why the linebreak didn't work?("p" "Review+Planning" entry
(file+headline ,(my/org-file "goals-habits.org") "Review+TODOs+Plan+Journal")
"**** On %t\n***** Planned:\n\n %i \n "
:prepend t)The template
("t" "Today" entry
(file+headline ,(my/org-file "writing.org") "Todays")
"** TODO %? \n SCHEDULED: %t\n%i"
:prepend t)Then we use special key bindings for going todays.
(defun my/org-capture-today () (interactive) (org-capture nil "t"))
(defun my/org-goto-today () (interactive) (org-capture-goto-target "t"))("C-c o t" . my/org-capture-today)
("C-c o T" . my/org-goto-today)(cond
;; 1: If we have dvipng, use this by default
((and (executable-find "dvipng") (executable-find "latex"))
(setq org-preview-latex-default-process 'dvipng))
;; 2: see if new magick is available
;;
;; old convert command is considered deprecated, in either case, you need the
;; ghostscript
((and (executable-find "magick") (executable-find "pdflatex") (executable-find "gs"))
;; add new item to associations
(prog2
(when (not (assoc 'magick org-preview-latex-process-alist))
(push '(magick
:programs ("latex" "magick")
:description "pdf > png"
:message "you need to install the programs: latex and imagemagick."
:image-input-type "pdf"
:image-output-type "png"
:image-size-adjust (1.0 . 1.0)
:latex-compiler ("pdflatex -interaction nonstopmode -output-directory %o %f")
:image-converter
("magick convert -density %D -trim -antialias %f -quality 100 %O"))
org-preview-latex-process-alist))
(setq org-preview-latex-default-process 'magick)))
;; 3: see if old convert is available
((and (executable-find "convert") (executable-find "pdflatex") (executable-find "gs"))
(setq org-preview-latex-default-process 'imagemagick)))
(setq org-preview-latex-image-directory
(my/concat-path temporary-file-directory
"ltximg/"))
;;set latex preview scale
(setq org-format-latex-options (plist-put
org-format-latex-options
:scale (perdev-get-value 'my/org-latex-scale1 1.0)))On archlinux, you need to install
- texlive-basic
- texlive-bin
- texlive-latex
- texlive-lateextra
- texlive-latexrecommanded
- texlive-pictures,
- texlive-plangeneric
allows you to use fixed pitch (for code) and variable pitch (everything else) IN THE BUFFER
(use-package mixed-pitch :ensure t :defer t
:hook
(org-mode . mixed-pitch-mode)
:custom
(mixed-pitch-variable-pitch-cursor 'box))(use-package org-modern :ensure t :after org
:hook
(org-mode . org-modern-mode)
(org-agenda-finalize . org-modern-agenda)
:custom
<<ORG_MODERN_STAR>>
(org-hide-emphasis-markers t)
(line-spaceing 0.3)
(org-fontify-done-headline t)
:config
(set-face-attribute 'org-modern-symbol nil
:family (perdev-get-value 'my/org-modern-symbol-font "Iosevka"))
(custom-theme-set-faces
'user
'(org-block ((t (:inherit fixed-pitch))))
'(org-code ((t (:inherit (shadow fixed-pitch)))))
'(org-document-info ((t (:foreground "dark orange"))))
'(org-document-info-keyword ((t (:inherit (shadow fixed-pitch)))))
'(org-link ((t (:foreground "royal blue" :underline t))))
'(org-meta-line ((t (:inherit (font-lock-comment-face fixed-pitch)))))
'(org-property-value ((t (:inherit fixed-pitch))) t)
'(org-special-keyword ((t (:inherit (font-lock-comment-face fixed-pitch)))))
'(org-table ((t (:inherit fixed-pitch :foreground "#83a598"))))
'(org-tag ((t (:inherit (shadow fixed-pitch) :weight bold :height 0.8))))
'(org-verbatim ((t (:inherit (shadow fixed-pitch))))))
)”*” are the leading character for each org-mode block. Org modern has a few configurations with it
;;org-modern actually don't like indent mode
(org-startup-indented t)
;; 'replace using ◉○◈◇✳. 'fold use "▶", the bug here is that it does not hide the line-prefix.. So we just disable to org-modern-star
(org-modern-star nil)
;; optionally you will be able to hide ORIGINAL * or not, leading means to hide the leading stars
(org-modern-hide-stars nil)(use-package org-modern-indent
:vc (:fetcher github :repo "jdtsmith/org-modern-indent")
:hook (org-modern-mode . org-modern-indent-mode)
)Setting the correct org-roam connector based on version. Emacs-29, which uses emacs builtin sqlite library, prior to that, it uses sqlite utilities from OS.
(if (version< emacs-version "29.0")
(setq org-roam-database-connector 'sqlite)
(setq org-roam-database-connector 'sqlite-builtin)) (use-package org-roam :ensure t :after org :defer t
:init
<<ROAM_SQLITE>>
;; disable org-roam warning
(setq org-roam-v2-ack t)
(defun my/roam-dir () (expand-file-name "pages" org-directory))
<<ROAM_VISIT>>
:custom
;; for some reason the (my/roam-dir) is not loaded correctly here
(org-roam-directory (expand-file-name "pages" org-directory))
(org-roam-completion-everywhere t)
(org-roam-db-update-on-save t)
;;template for v2
(org-roam-capture-templates
'(
<<ROAM_CAPTURES>>
))
;; displaying tags along with title for org roam
(org-roam-node-display-template
(concat "${title:*} " (propertize "${tags:10}" 'face 'org-tag)))
:bind (("C-c o f" . org-roam-node-find)
("C-c o C" . org-roam-capture)
("C-c o i" . org-roam-node-insert)
("C-c o g" . org-roam-ui-mode)
:map org-mode-map
("C-c o r" . org-roam-buffer-toggle) ;;toggle-back-links
:map org-roam-mode-map
;;NOTE alternatively, use C-u RET to visit in other window
("RET" . my/roam-visit))
:config
;;start db sync automatically, also you are able to refresh back link buffer,
;;alternatively you hook org-roam-db-auto-sync-mode to org-roam-mode
(org-roam-db-autosync-enable)
;;excluding org-roam-dailes from roam-database
;; configure org-roam-buffer
<<ROAM_CONFIG>>
)(use-package org-roam-dailies
:ensure nil ;; Do not enable, it is part of org-roam
:init
(define-prefix-command 'my/org-roam-dailies-map)
:bind
(("C-c o d" . my/org-roam-dailies-map)
:map my/org-roam-dailies-map
;; NOTE that Do not confuse org-roam-dailies capture with daily task-captures
("T" . org-roam-dailies-capture-today)
("t" . org-roam-dailies-goto-today)
("n" . org-roam-dailies-goto-date)
("y" . org-roam-dailies-goto-yesterday)))(setq org-roam-db-node-include-function
(lambda ()
(let ((path (buffer-file-name (buffer-base-buffer)))
;; org-roam-dailies-directory is a relative path
(dailies-dir (expand-file-name org-roam-dailies-directory
org-roam-directory)))
(not (file-in-directory-p path dailies-dir)))))Mostly I only use default template
("d" "default" plain "%?"
:if-new (file+head "${slug}.org"
"#+title: ${title}\n#+filetags: %^{org-roam-tags}\n#+created: %u\n")
:unnarrowed t
:jump-to-captured t) Optionally, create a note from clipboard.
("l" "clipboard" plain (function org-roam--capture-get-point)
"%c"
:file-name "${slug}"
:head "#+title: ${title}\n#+created: %u\n#+last_modified: %U\n\
#+ROAM_TAGS: %?\n"
:unnarrowed t
:prepend t
:jump-to-captured t) visiting roam pages using different other window. It’s most case what you want.
(defun my/roam-visit () (interactive) (org-roam-node-visit
(org-roam-node-at-point) 'other-window))(add-to-list 'display-buffer-alist
'("\\*org-roam\\*"
(display-buffer-in-direction)
(display-buffer-in-previous-window)
(direction . right)
(window-width . 0.33)
(window-height . fit-window-to-buffer)))(use-package org-roam-ui :ensure t :after org-roam
:diminish org-roam-ui-mode
:config
(setq org-roam-ui-sync-theme nil
org-roam-ui-follow t
org-roam-ui-update-on-save t
org-roam-ui-open-on-start t))
(use-package org-cliplink :ensure t :after org
:bind (:map org-mode-map
("C-c C-p i" . org-cliplink)
("C-c C-p l" . org-store-link)));; org-download;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(use-package org-download :ensure t :after org
:hook
;;this hook will run at-startup because of org-clock, and we do not have a
;;(buffer-file-name) then, so we need to error check it
(org-mode . (lambda ()
;;download into the "img dir of current org file directory"
(when (buffer-file-name)
(let ((currdir (file-name-directory (buffer-file-name))))
(set (make-local-variable 'org-download-image-dir)
(expand-file-name "imgs/" currdir))))))
:bind (:map org-mode-map
("C-c C-p s" . org-download-screenshot)
("C-c C-p y" . org-download-yank)
("C-c C-p c" . org-download-clipboard)))
using the IVY framework
(use-package ivy-bibtex :ensure t :after org
:init
(setq bibtex-completion-bibliography `,(my/org-file "bib/references.bib")))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; org-ref
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(use-package org-ref :ensure t :after org
:init
(require 'org-ref-arxiv)
(require 'org-ref-scopus)
(require 'org-ref-wos)
(require 'org-ref-ivy)
(setq org-ref-insert-link-function 'org-ref-insert-link-hydra/body
org-ref-insert-cite-function 'org-ref-cite-insert-ivy
org-ref-insert-label-function 'org-ref-insert-label-link
org-ref-insert-ref-function 'org-ref-insert-ref-link
org-ref-cite-onclick-function (lambda (_) (org-ref-citation-hydra/body)))
;; setup auto generating bibtex keys
(require 'bibtex)
(setq bibtex-autokey-year-length 4
bibtex-autokey-name-year-separator "-"
bibtex-autokey-year-title-separator "-"
bibtex-autokey-titleword-separator "-"
bibtex-autokey-titlewords 2
bibtex-autokey-titlewords-stretch 1
bibtex-autokey-titleword-length 5)
;; export to pdf with bibtex
;;this is when you don't have latexmk
(setq org-latex-pdf-process
(if (executable-find "latexmk")
;;when you have latexmk
(list "latexmk -shell-escape -bibtex -f -pdf %f")
;;when you don't have latexmk
'("pdflatex -interaction nonstopmode -output-directory %o %f"
"bibtex %b" ;;using bibtex here, or you can use biber
"pdflatex -interaction nonstopmode -output-directory %o %f"
"pdflatex -interaction nonstopmode -output-directory %o %f")))
:bind (:map org-mode-map
("C-c [" . org-ref-insert-link-hydra/body)
("C-c ]" . org-ref-insert-link))
)
(use-package org-contrib :ensure t :after org
:init
(require 'ox-groff))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; disabled-config
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; My synchronizer
;; (use-package org-msync :load-path "lisp/"
;; :hook ((org-mode . org-msync-after-save-hook)
;; (auto-save . org-msync-auto-save-hook))
;; :custom
;; (org-msync-local-dir org-directory)
;; (org-msync-remote-dir "~/Documents/org-remote/")
;; )The Org CV package helps manages the curriculum-vitae easily. The other custom option is following Aidan Scannell’s post.
(use-package ox-moderncv ;;TODO this is an exception to defer?
:vc (:fetcher github :repo "Titan-C/org-cv")
:init (require 'ox-moderncv))Adding Jira tickets to your Calendar
(use-package org-jira
:when (getenv "ORG_JIRA_URL")
:ensure t :after org :pin melpa
:commands (org-jira-get-issues)
:bind-keymap ("C-c o j" . org-jira-entry-mode-map)
:bind (:map org-jira-entry-mode-map
("ig" . org-jira-get-issues)
("ih" . org-jira-get-issues-headonly)
("iw" . org-jira-progress-issue)
("in" . org-jira-progress-issue-next))
:config
(setq org-jira-working-dir (expand-file-name ".org-jira" org-directory))
(unless (file-exists-p org-jira-working-dir)
(make-directory org-jira-working-dir))
;;jiralib.el is part of the package.
(setq jiralib-url (getenv "ORG_JIRA_URL"))
(setq jiralib-token
(cons "Authorization"
(concat "Bearer " (auth-source-pick-first-password
:host (getenv "ORG_JIRA_URL")))))
;; adding the files to org-agenda-files
(setq org-agenda-files
(append org-agenda-files (directory-files-recursively org-jira-working-dir "\\.org$")))
;; setup the workflow
(defconst org-jira-progress-issue-flow
'(("open" . "Ready for Development")
("Ready for Development" . "In Progress")
("In Progress" . "Closed")
("Closed" . "Reopened")))
)awesome package to convert web package into a org buffer/or entries.
(use-package org-web-tools :pin melpa :after org :ensure t :defer t
:bind
(("C-c o wr" . org-web-tools-read-url-as-org)
:map org-mode-map
("C-c o wi" . org-web-tools-insert-web-page-as-entry)))org-ql is a much powerful query tool for org mode
Note that org-ql-find does not work with ivy, thus I switched to vertico+consult+embark now.
(use-package org-ql
:ensure t :after org :pin melpa
:after org)("C-c o v" . org-ql-view)
("C-c o s" . org-ql-search)Markdown Mode for Emacs is popular with AI tools, Emacs has good support for it.
(use-package markdown-mode
:ensure t :defer t
:mode ("README\\.md\\'" . gfm-mode)
:init (setq markdown-command "multimarkdown")
:bind (:map markdown-mode-map
("C-c C-e" . markdown-do))
:hook
;;markdown has its own display, which key is kind of annoying here
(markdown-mode . (lambda () (which-key-mode -1)))
)
C-c C-l- inserting a link
C-c C-s- change the text style at the point.
b- bold the text
q- quote.
c- inline code
- H
- heading
-- insert
---------------------- f- foot note
C-c C-c- maintenance
- “e” : export
....
Define a backward kill a line:
(defun my/backward-kill-line (arg)
"Kill ARG line backwards"
(interactive "p")
(kill-line (- 1 arg)))
(define-key prog-mode-map (kbd "C-c u") 'my/backward-kill-line)Copy a line:
(defun my/copy-line ()
"copy current line, from the first character that is not \t or
' ', to the last of that line, this feature is from vim.
Case to use this feature:
- repeat similar lines in the code.
"
(interactive)
(save-excursion
(back-to-indentation)
(let* ((beg (point))
(end (line-end-position))
(mystr (buffer-substring beg end)))
(kill-ring-save beg end)
(message "%s" mystr)))
;;This is silly, find a way to print out last-kill.
)
(define-key prog-mode-map (kbd "C-c C-k") 'my/copy-line)move line up and down:
(defmacro save-column (&rest body)
`(let ((column (current-column)))
(unwind-protect
(progn ,@body)
(move-to-column column))))
(put 'save-column 'lisp-indent-function 0)
(defun my/move-line-up ()
(interactive)
(save-column
(transpose-lines 1)
(forward-line -2)))
(defun my/move-line-down ()
(interactive)
(save-column
(forward-line 1)
(transpose-lines 1)
(forward-line -1)))
(define-key prog-mode-map (kbd "M-<up>") 'my/move-line-up)
(define-key prog-mode-map (kbd "M-<down>") 'my/move-line-down) backward-forward package helps us jump back-forward in the mark ring.
(use-package backward-forward :ensure t
:demand
:config
(backward-forward-mode t)
:bind (:map backward-forward-mode-map
("<C-left>" . nil)
("<C-right>" . nil)
("C-c C-<left>" . backward-forward-previous-location)
("C-c C-<right>" . backward-forward-next-location)
("<mouse-8>" . backward-forward-previous-location)
("<mouse-9>" . backward-forward-next-location)))(global-set-key (kbd "C-x <up>") 'windmove-up)
(global-set-key (kbd "C-x <down>") 'windmove-down)
(global-set-key (kbd "C-x <left>") 'windmove-left)
(global-set-key (kbd "C-x <right>") 'windmove-right)winner mode has two default keybinding
- “C-c left” : for
winner-undo - “C-c right” : for
winner-redo
(use-package winner :defer t :ensure t
:diminish winner-mode
:hook ((prog-mode text-mode) . winner-mode))(global-set-key (kbd "\C-x r i") 'string-insert-rectangle) (use-package vertico :ensure t
:diminish vertico-mode
:custom
(vertico-count 20) ;; limit to a fixed size
(vertico-cycle t)
:hook
(after-init . vertico-mode)
:bind (:map vertico-map
;; Use page-up/down to scroll vertico buffer, like ivy does by default.
("<prior>" . 'vertico-scroll-down)
("<next>" . 'vertico-scroll-up))
)
(use-package vertico-directory
:after vertico
:ensure nil ;; no need to install, it comes with vertico
:bind (:map vertico-map
("DEL" . vertico-directory-delete-char))) (use-package marginalia :ensure t
:hook (vertico-mode . marginalia-mode))(use-package orderless :ensure t
:custom
;; Activate orderless completion
(completion-styles '(orderless basic))
;; Enable partial completion for file wildcard support
(completion-category-overrides '((file (styles partial-completion)))))(use-package consult :ensure t
:bind
(;;most basics
("C-x b" . consult-buffer)
("C-x C-f" . find-file)
("M-x" . execute-extended-command)
("C-x t" . consult-imenu)
("C-c y" . consult-yank-pop)
("C-s" . isearch-forward)
("C-r" . isearch-backward)
;; for git setup
("C-c g" . consult-grep)
("C-c j" . consult-git-grep)
("C-c r" . consult-ripgrep)
("C-c l" . consult-line)
;; in consult-line, we simply get a history in previous search by press
;; ctrl-s again. This is not a full replacement for swiper but it can reduce
;; quite bit of typing.
;; :map minibuffer-local-map
;; ("C-s" . (lambda () (interactive) (insert (car consult--line-history))))
))
(use-package embark :ensure t
:bind
(("C-." . embark-act) ;; Begin the embark process
("C-;" . embark-dwim) ;; good alternative: M-.
("C-h B" . embark-bindings)) ;; alternative for `describe-bindings'
:config
(use-package embark-consult :ensure t))the excellent fly-spell to correct my common typing mistakes.
(use-package flyspell
:if (or (executable-find "aspell")
(executable-find "hunspell")
(executable-find "ispell"))
:defer t
:hook ((prog-mode . flyspell-prog-mode)
(text-mode . flyspell-mode) ;;for markdown, org, nxml
;;also disable it for specific mode
(change-log-mode . (turn-off-flyspell)))
:init
;;for flyspell to work, you need to set LANG first
;; on windows, getenv has strange behavior, getenv-internal seems to work correctly.
;; (when (not (getenv-internal "LANG" initial-environment))
(setenv "LANG" "en_US")
;; priotize aspell over hunspell For Linux and Mac
:custom (ispell-program-name (or (executable-find "aspell")
(executable-find "hunspell")
(executable-find "ispell")))
;;:config
;;TODO flyspell language-tool
) Fly-spell correct: Vertico uses default completing-read interface, use shortcut key @s, @a, @p or @k to trigger the actions.
;; correcting word and save it to personal dictionary
(use-package flyspell-correct :ensure t :after flyspell
:bind (:map flyspell-mode-map ("C-c ;" . flyspell-correct-wrapper)))(defun my/isearch-mark-and-exit ()
"Mark the current search string. Can be used as building block for more
complex chain."
(interactive)
(push-mark isearch-other-end t 'activate)
(setq deactivate-mark nil)
(isearch-done))
;;press backspace to delete all mis-typings
(defun my/isearch-remove-failed-part-or-last-char ()
"Remove failed part of search string, or last char if successful.
Do nothing if search string is empty to start with."
(interactive)
(if (equal isearch-string "")
(isearch-update)
(if isearch-success
(isearch-delete-char)
(while (isearch-fail-pos (isearch-pop-state)))
(isearch-update))))(defun my/isearch-region (&optional not-regexp no-recursive-edit)
"If a region is active, make this the isearch default search pattern."
(interactive "P\np")
(when (use-region-p)
(let ((search (buffer-substring-no-properties
(region-beginning)
(region-end))))
(message "stribb/ir: %s %d %d" search (region-beginning) (region-end))
(setq deactivate-mark t)
(isearch-yank-string search))))
(advice-add 'isearch-forward-regexp :after 'my/isearch-region)
(advice-add 'isearch-forward :after 'my/isearch-region)
(advice-add 'isearch-backward-regexp :after 'my/isearch-region)
(advice-add 'isearch-backward :after 'my/isearch-region)(use-package transient-isearch
:load-path "lisp"
:bind (:map
isearch-mode-map
("DEL" . my/isearch-remove-failed-part-or-last-char)
("C-SPC" . my/isearch-mark-and-exit)
;;(M-s . isearch-common-prefix) ;;very powerful
("M-/" . transient-isearch-menu)))I tried to keep my keybindings intent and avoid conflicts. Right now there are too many packages fight for C-c.
org mode keys
org mode keys (especially globally bound keys) starts with (C-c o)
Eglot key bindings
There are currently two keybindings
C-c Reglot-renameC-c Heldoc
AI related tools starts with (C-c i)
I have tried a few themes, not satisfied with most of them. Either the contrast is too high, or they are plain ugly. Among them, I like these themes.
- spacemacs-theme : a well designed theme can be used for long time.
- apropospriate-theme : low contrast yet colorful.
- modus-themes: current choice. I like the tinted version of the theme, however I have to disable defer loading to make it work.
(use-package modus-themes
;; TODO have to disable defer to get circadian to work
:ensure t
:init
(setq modus-themes-mixed-fonts t)
(setq modus-themes-common-palette-overrides
`(
;; From the section "Make the mode line borderless"
(border-mode-line-active unspecified)
(border-mode-line-inactive unspecified)))) Now I setup my desired theme here
(setq appr-dark-theme-name 'modus-vivendi-tinted)
(setq appr-light-theme-name 'modus-operandi-tinted)
(setq appr-dark-theme-hour 17)
(setq appr-light-theme-hour 8)My setup uses run-with-timer every hour to check the if it’s time to change the theme, so it may not change the theme at desired time. NOTE: Originally I was using circadian.el but unfortunately that package has misuse of run-at-time that leads to heavy CPU spikes. See the issue for details. I would need to fix that bug if want to switch back to circadian.
ligature is a typographical method to combine two or more glyphs or letters to form a single glyph.
(use-package ligature
:defer t
:vc (:fetcher github :repo "mickeynp/ligature.el")
:if (string-match "HARFBUZZ" system-configuration-features)
:hook ((prog-mode text-mode) . ligature-mode)
:config
;; Enable "www" ligature in every possible major mode
(ligature-set-ligatures 't '("www"))) I created a small package to manage my fixed width font(with ligature), proportional font, CJK font and emoji font.
(use-package appr
:load-path "lisp"
:hook (after-init . appr)
:init
<<THEME>>
:custom
(appr-default-font-size (perdev-get-value 'my/default-font-size 13))
(appr-cjk-font-list '("WenQuanYi Micro Hei"
"WenQuanYi Zen Hei"
"Microsoft YaHei"
"Microsoft JhengHei"))
(appr-emoji-font-list '("Noto Color Emoji"
"Noto Emoji"
"Segoe UI Emoji"
"Symbola"
"Apple Color Emoji"))
(appr-variable-pitch-font-list '("Fira Sans"
"Iosevka Aile"))
) ;;sync
(use-package magit
:ensure t
:bind ("C-x g" . magit-status))
(use-package ssh-agency
:vc (:fetcher github :repo "magit/ssh-agency")
:hook (magit-credential . ssh-agency-ensure))There is not thing special here, we just add a key-map and use alien indexing method (git) across all platform.
(use-package projectile
:ensure t
:diminish projectile-mode
:init
(projectile-mode +1)
:bind (:map projectile-mode-map
("C-c p" . projectile-command-map)
:map projectile-command-map
;; the other commands are configure/packge/install/test/run
("c" . projectile-compile-project))
:custom
(projectile-enable-caching t)
(projectile-indexing-method 'alien))
(use-package projectile-ripgrep :ensure t :pin melpa :after projectile)(use-package color-rg
:vc (:fetcher github :repo "manateelazycat/color-rg")
:config (when (eq system-type 'windows-nt)
(setq color-rg-command-prefix "powershell"))
:custom (color-rg-search-no-ignore-file nil))(use-package ediff :ensure nil ;;built-in package
:custom
;;do not use the multi-frame in graphics mode
(ediff-window-setup-function #'ediff-setup-windows-plain)
(ediff-split-window-function #'split-window-vertically))fmo-mode for code re-formatting
prerequisite : format-all
(use-package format-all :ensure t :pin melpa :defer t)
(use-package difflib :ensure t :pin melpa :defer t)(use-package fmo-mode :ensure t :pin melpa
:custom (fmo-ensure-formatters t)
:hook (((c-mode c++-mode c-ts-mode c++-ts-mode) . fmo-mode)
((hlsl-mode glsl-mode azsl-mode ) . fmo-mode))
:config
<<FMO_LANGS>>
)(use-package whitespace-cleanup-mode
:ensure t
:diminish whitespace-cleanup-mode
:hook ((prog-mode . whitespace-cleanup-mode)))(use-package elec-pair
:diminish electric-pair-mode
:hook ((prog-mode text-mod outline-mode) . electric-pair-mode))(use-package paren
:ensure t
:diminish show-paren-mode
:hook (prog-mode . show-paren-mode)
:config (setq show-paren-style 'parenthesis))(use-package rainbow-delimiters
:ensure t :defer t
:hook ((emacs-lisp-mode lisp-interaction-mode) . rainbow-delimiters-mode)) (use-package paredit
:ensure t :defer t :pin melpa
:hook ( (emacs-lisp-mode lisp-interaction-mode) . paredit-mode)) (use-package fic-mode ;;show FIXME/TODO in comments
:vc (:fetcher github :repo "lewang/fic-mode")
:diminish fic-mode
:hook (prog-mode . fic-mode)
:custom (fic-highlighted-words '("FIXME" "TODO" "BUG" "NOTE")));; yasnippet
(use-package yasnippet-snippets
:ensure t
:config
(yas-reload-all)
:hook ((prog-mode outline-mode cmake-mode) . yas-minor-mode));; visual fill column
(use-package visual-fill-column
:ensure t
:init
(setq-default fill-column 79)
(setq-default visual-fill-column-width 120)
:hook
(prog-mode . turn-on-auto-fill)
(visual-line-mode . visual-fill-column-mode)
((text-mode outline-mode) . visual-line-mode));; diminish some builtin packages
(diminish 'eldoc-mode)
(diminish 'abbrev-mode)Hide show works by regex searching. You can customize it with hs-special-modes-alist. You define the START, END, COMMENT and a FORWARD-SEXP-FUNC. Which is a forward-sexp goes to end of the marching.
(use-package hideshow
:hook ((prog-mode . hs-minor-mode)
(nxml-mode . hs-minor-mode))
:diminish hs-minor-mode
:bind (;; the two map didn't work, polluting global map
("C-c C-h t" . hs-toggle-hiding)
("C-c C-h l" . hs-hide-level)
("C-c C-h a" . hs-hide-leafs)
("C-c C-h s" . hs-show-block)
)
:config
(setq hs-isearch-open t)
<<HS_MODES>>
:preface
<<HS_HIDE_LEAVES>>
)XML is very easy to setup, The BEGIN and END are simply the tag brackets, xml also has its own forward
(add-to-list 'hs-special-modes-alist
'(nxml-mode
"<!--\\|<[^/>]*[^/]>"
"-->\\|</[^/>]*[^/]>"
"<!--"
sgml-skip-tag-forward
nil))(defun hs-cmake-forward-sexp-once ()
"forward cmake s-expression once"
(when (looking-at hs-block-start-regexp)
(let ((matched-key-beg (match-string-no-properties 1))
(matched-key-end "")
end-point)
;; now we search for the end regular expression
(save-excursion
;; loop until we find the END that matches the START
(while (not (string= matched-key-beg matched-key-end))
;; search forward
(re-search-forward hs-block-end-regexp)
;; and updating the point and end match data.
(setq matched-key-end (match-string-no-properties 1))
(setq end-point (match-beginning 0)))
)
(goto-char end-point))))
(defun hs-cmake-forward-sexp (args)
"forward cmake s-expression ARGS times"
(dotimes (i args)
(hs-cmake-forward-sexp-once)))
(add-to-list 'hs-special-modes-alist
'(cmake-mode
;;match the begining
"^\\([:blank:]*\\)\\(?1:if\\|function\\|macro\\) *(.*)"
;match the end
"^\\([:blank:]*\\)end\\(?1:[A-Za-z]+\\) *(.*)"
;;comment
"#"
hs-cmake-forward-sexp
nil))Recursively run hide show on all leaves
(defun hs-hide-leafs-recursive (minp maxp)
"Hide blocks below point that do not contain further blocks in
region (MINP MAXP)."
(when (hs-find-block-beginning)
(setq minp (1+ (point)))
(funcall hs-forward-sexp-func 1)
(setq maxp (1- (point))))
(unless hs-allow-nesting
(hs-discard-overlays minp maxp))
(goto-char minp)
(let ((leaf t))
(while (progn
(forward-comment (buffer-size))
(and (< (point) maxp)
(re-search-forward hs-block-start-regexp maxp t)))
(setq pos (match-beginning hs-block-start-mdata-select))
(if (hs-hide-leafs-recursive minp maxp)
(save-excursion
(goto-char pos)
(hs-hide-block-at-point t)))
(setq leaf nil))
(goto-char maxp)
leaf))
(defun hs-hide-leafs ()
"Hide all blocks in the buffer that do not contain subordinate
blocks. The hook `hs-hide-hook' is run; see `run-hooks'."
(interactive)
(hs-life-goes-on
(save-excursion
(message "Hiding blocks ...")
(save-excursion
(goto-char (point-min))
(hs-hide-leafs-recursive (point-min) (point-max)))
(message "Hiding blocks ... done"))
(run-hooks 'hs-hide-hook)))Hide show does not work with #ifdef, the hideif package
(use-package hideif
:ensure t
:diminish hide-ifdef-mode
:hook ((c++-mode c++-ts-mode c-mode c-ts-mode) . hide-ifdef-mode)
:config
(setq hide-ifdef-read-only t)
)Tree-sitter is a new major mode managements package.
here is my custom rule just to disable namespace indentation (setq
treesit--indent-verbose t) to see if your rule works (treesit-check-indent
c++-mode) to check your rules against c++-mode.
(when (treesit-available-p)
(require 'treesit)
(defun my/indent-rules ()
`(
((n-p-gp "declaration" "declaration_list" "namespace_definition")
parent-bol 0)
((n-p-gp "comment" "declaration_list" "namespace_definition") parent-bol 0)
((n-p-gp "class_specifier" "declaration_list" "namespace_definition") parent-bol 0)
((n-p-gp "function_definition" "declaration_list" "namespace_definition")
parent-bol 0)
((n-p-gp "template_declaration" "declaration_list" "namespace_definition")
parent-bol 0)
,@(alist-get 'bsd (c-ts-mode--indent-styles 'cpp)))
))The difficult thing is to setup the indentations. See gnu archive and this blog-post is very useful.
treesit-auto does not work on windows at moment.
(use-package treesit-auto
:unless (or (eq system-type 'windows-nt)
(not (treesit-available-p)))
:ensure t
:custom
(c-ts-mode-indent-style #'my/indent-rules)
:hook ((after-init . treesit-auto-mode))
:config
(setq-default treesit-font-lock-level 3)
;;Error here if (treesit-auto-add-to-auto-mode-alist 'all) because the
;;extensions for glsl is nil. See
;;https://github.com/renzmann/treesit-auto/blob/main/treesit-auto.el#L161. We
;;end up adding a (nil . glsl-ts-mode) to auto-mode-alist. Which causes the
;;auto-mode to fail to load anything.
;; to fix this, we need to build a treesit-ready-langs which contains only ones has extensions
(setq treesit-ready-langs
(seq-map #'treesit-auto-recipe-lang
(seq-filter
(lambda (r) (if (and (treesit-auto-recipe-ext r) ;;has extensions
(fboundp (treesit-auto-recipe-ts-mode r))) ;;has mode
r nil))
treesit-auto-recipe-list)))
(treesit-auto-add-to-auto-mode-alist treesit-ready-langs)
(setq treesit-auto-install 'prompt))(use-package company-c-headers :ensure t :defer t)
;; (setq clang-known-modes '(c++-mode c-mode))(use-package company
:ensure t
:defer t
;;one-liner setups for languages
:hook (
<<COMPANY_HOOKS>>
)
:config
(setq company-minimum-prefix-length 2
company-idle-delay 0.1
company-async-timeout 10
company-backends '(company-files
company-keywords
company-yasnippet
company-capf)))CMake we just add company-dabbrev and company-cmake.
(cmake-mode . company-mode)
(cmake-mode . (lambda () (my/merge-list-buffer-local
'company-backends
(list 'company-cmake 'company-dabbrev))))- company-elisp
- is remove so we just rely on internal backends.
(emacs-lisp-mode . company-mode)- TEXT
- We add company-emoji to company backends.
(use-package company-emoji
:defer t
:ensure t
:after company)Outline mode includes org mode and markdowns
(outline-mode . company-mode) ;;enable for org mode
(outline-mode . (lambda () (my/merge-list-buffer-local
'company-backends
(list'company-dabbrev 'company-emoji))))simple text mode:
(text-mode . company-mode)
(text-mode . (lambda () (my/merge-list-buffer-local
'company-backends
(list 'company-dabbrev 'company-emoji))));; eglot configuration, switching to eglot after emacs 29
(use-package eglot
:ensure t :defer t
:hook (
<<EGLOT_HOOKS>>
)
:custom
(eglot-extend-to-xref t)
;;inlay-hints are annoying
(eglot-ignored-server-capabilities '(:inlayHintProvider))
:config
;;by default eglot forces company to only use company-capf, I lose a lot of
;;backends in this way
(setq eglot-stay-out-of '(company))
;;eldoc's multi-line mini buffer is really annoying, turn it off
(setq eldoc-echo-area-use-multiline-p nil)
;; clangd often mess around with headers, turn this off.
(add-to-list 'eglot-server-programs
'((c++-mode c-mode c-ts-mode c++-ts-mode)
. ("clangd"
"-j=4"
"--header-insertion=never"
"--header-insertion-decorators=0")))
;;C++ requires clangd, python requires python-language server
:bind (:map eglot-mode-map
;; we just use the default binding here, so comment it out
;; ("M-." . xref-find-definitions)
;; ("M-?" . xref-find-references)
;; ("M-," . xref-go-back)
("C-c R" . eglot-rename)
("C-c H" . eldoc))
)Debugging with debug adapter protocol
dape configs
(use-package dape :ensure t
:vc (:fetcher github :repo "svaante/dape")
:preface
;; By default dape shares the same keybinding prefix as `gud'
;; If you do not want to use any prefix, set it to nil.
(setq dape-key-prefix "\C-x\C-a")
:hook
;; Save breakpoints on quit
(kill-emacs . dape-breakpoint-save)
;; Load breakpoints on startup
(after-init . dape-breakpoint-load)
:config
;; Turn on global bindings for setting breakpoints with mouse
(dape-breakpoint-global-mode)
;; Info buffers to the right
(setq dape-buffer-window-arrangement 'right)
;; Pulse source line (performance hit)
;; (add-hook 'dape-display-source-hook 'pulse-momentary-highlight-one-line)
;; Showing inlay hints
(setq dape-inlay-hints t)
;; Save buffers on startup, useful for interpreted languages
;; (add-hook 'dape-start-hook (lambda () (save-some-buffers t t)))
;; Kill compile buffer on build success
(add-hook 'dape-compile-hook 'kill-buffer)
;; Projectile users
;; (setq dape-cwd-function 'projectile-project-root)
)
;; Enable repeat mode for more ergonomic `dape' use
(use-package repeat
:config
(repeat-mode))codelldb setup
So far I had the most success with codelldb, however there are couple of issues:
a quick hack is to use :stopOnEntry. You can setup dap-command on .dir-locals.el
(setq (dape-command (codelldb-cc :stopOnEntry t :args ["arg0" "args1"]))) lldb-dap installation
- On MacOS, you can find the
lldb-dapthroughxcrun -f lldb-dap, it is usually under:
/Applications/Xcode.app/Contents/Developer/usr/bin/lldb-dap
then you can link against it ln -s /Application//.../lldb-dap /usr/local/bin/lldb-dap
Rmsbolt mode
RMSBolt mode mimics Compiler Explorer in Emacs. Making changes with C-c C-c, which is bonded to :bind (:map rmsbolt-mode-map ("C-c C-c" . rmsbolt-compile))
;;compiler explorer in emacs
(use-package rmsbolt :ensure t :defer t
:init
<<RMSBOLT_COMPILERS>>
(defun my/cc-ts-rmsbolt-hook ()
;;setup languages
(cond
<<RMSBOLT_LANGS>>
))
:hook
(rmsbolt-mode . my/cc-ts-rmsbolt-hook));; C family
(use-package cc-mode
:mode (("\\.h\\(h?\\|xx\\|pp\\)\\'" . c++-mode)
("\\.m\\'" . c-mode)
("\\.mm\\'" . c++-mode)
("\\.inl\\'" . c++-mode))
:preface
(defun my/cmode-hook ()
;;default settings
(setq c-default-style "linux"
c-basic-offset 8)
(c-set-offset 'inextern-lang 0)
(c-set-offset 'innamespace 0)
(c-set-offset 'inline-open 0))
(defun my/cmode-company-hook ()
;;override default company backends because eglot not compatible with
;;company-clang
(set (make-local-variable 'company-backends)
'(company-capf
company-files
company-keywords
company-dabbrev
company-yasnippet)))
:config
(require 'cc-file-styles)
(c-add-style (car cc-file-style-o3de)
(cdr cc-file-style-o3de))
(c-add-style (car cc-file-style-sparroh)
(cdr cc-file-style-sparroh))
:hook
((c-mode-common . my/cmode-hook)
((c++-mode c++-ts-mode c-mode c-ts-mode) . my/cmode-company-hook)))((c++-mode c++-ts-mode c-mode c-ts-mode) . company-mode)((c++-mode c++-ts-mode) . eglot-ensure)
((c-mode c-ts-mode) . eglot-ensure)(use-package cppinsights
:when (executable-find "insights")
:pin melpa :defer t
:commands cppinsights-run
:custom
(cppinsights-program "insights")
)On mac it’s installed by brew install cppinsights which uses llvm@20. It does not work on mac mostly I almost always got this compilation issue:
In file included from /opt/homebrew/Cellar/llvm@20/20.1.8/include/c++/v1/cwchar:117:
In file included from /opt/homebrew/Cellar/llvm@20/20.1.8/include/c++/v1/wchar.h:122:
/opt/homebrew/Cellar/llvm@20/20.1.8/include/c++/v1/__mbstate_t.h:51:4: error: "We don't know how to get the definition of mbstate_t without <wchar.h> on your platform."
51 | # error "We don't know how to get the definition of mbstate_t without <wchar.h> on your platform."
The following trick did not work either.
(setq cppinsights-extra-args
'("-isystem/usr/local/include"
"-isystem/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1"
"-isystem/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/17/include"
"-isystem/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include"
"-isystem/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include")
)C and C++, we use clang++ and llvm-cxfilt. In this way it supports both Linux and windows
;; cond
((or (eq major-mode 'c-ts-mode)
(eq major-mode 'c-mode))
(setq rmsbolt-language-descriptor
(make-rmsbolt-lang :compile-cmd "clang"
:supports-asm t
:supports-disass t
:demangler "llvm-cxxfilt"
:compile-cmd-function #'rmsbolt--c-compile-cmd
:disass-hidden-funcs
rmsbolt--hidden-func-c)))
((or (eq major-mode 'c++-ts-mode)
(eq major-mode 'c++-mode))
(setq rmsbolt-language-descriptor
(make-rmsbolt-lang :compile-cmd "clang++"
:supports-asm t
:supports-disass t
:demangler "llvm-cxxfilt"
:compile-cmd-function #'rmsbolt--c-compile-cmd
:disass-hidden-funcs rmsbolt--hidden-func-c))) (use-package slang-mode
:when (executable-find "slangd")
:vc (:fetcher github :repo "K1ngst0m/slang-mode")
:defer t
:mode (("\\.slang\\'" . slang-mode)
("\\.sl\\'" . slang-mode)
("\\.slangh\\'" . slang-mode))
:hook
(slang-mode . company-mode)
(slang-mode . (lambda () (my/merge-list-buffer-local
'company-backends
(list 'company-dabbrev 'company-capf))))
:config
(require 'slang-lsp)
(slang-lsp-initialize)
)The crucial piece is to generate line-mapping information for the target assembly. Line mapping is done by parsing DebugLine instructions from NonSemantic.Shader.DebugInfo.100 and resolving OpConstant ID references to source line numbers.
(cl-defun my/rmsbolt-process-spirv-asm-lines (_src-buffer asm-lines)
"Process SPIR-V ASM-LINES to add source line properties.
Parses OpConstant definitions and DebugLine instructions from
NonSemantic.Shader.DebugInfo.100 to map assembly to source lines."
(let ((const-table (make-hash-table :test #'equal))
(source-linum nil)
(result nil))
;; Pass 1: build constant table from OpConstant lines
;; e.g. %uint_14 = OpConstant %uint 14
;; Note: (* space) needed because glslangValidator indents OpConstant
;; lines, while slangc emits them at column 0.
(dolist (line asm-lines)
(when (string-match
(rx bol (* space) ;; see comment above.
(group "%" (1+ (not (any space)))) ; %id
(1+ space) "=" (1+ space)
"OpConstant" (1+ space)
"%" (1+ (not (any space))) ; type like %uint or %int
(1+ space)
(group (opt "-") (1+ digit)) ; value (possibly negative)
eol)
line)
(puthash (match-string 1 line)
(string-to-number (match-string 2 line))
const-table)))
;; Pass 2: walk lines, track DebugLine and tag instructions
(dolist (line asm-lines)
(cond
;; DebugLine: extract Line Start (2nd operand after DebugLine)
;; e.g. %245 = OpExtInst %void %2 DebugLine %4 %uint_14 %uint_14 %uint_5 %uint_6
((string-match
(rx "DebugLine" (1+ space)
"%" (1+ (not (any space))) ; Source id
(1+ space)
(group "%" (1+ (not (any space)))); Line Start id
)
line)
(let ((line-start-id (match-string 1 line)))
(setq source-linum (gethash line-start-id const-table)))
(push line result))
;; DebugNoLine or OpFunctionEnd: reset line mapping
((or (string-match-p (rx "DebugNoLine") line)
(string-match-p (rx "DebugNoScope") line)
(string-match-p (rx bol "OpFunctionEnd") line))
(setq source-linum nil)
(push line result))
;; Regular instruction lines: tag with source line if known
(t
(when source-linum
(add-text-properties 0 (length line)
`(rmsbolt-src-line ,source-linum) line))
(push line result))))
(nreverse result)))Both slangc and glslangValidator compile commands support additional arguments via buffer-local variables. This is useful for passing project-specific flags (e.g. include paths, defines) from .dir-locals.el.
(defun my/rmsbolt-extra-args-safe-p (val)
"Return non-nil if VAL is nil, a string, or a list of strings."
(or (null val)
(stringp val)
(and (listp val) (cl-every #'stringp val))))
(defun my/rmsbolt-extra-args-to-list (args)
"Normalize ARGS to a list of strings.
ARGS can be nil, a string, or a list of strings."
(cond
((null args) nil)
((stringp args) (list args))
((listp args) args)))
(defvar-local rmsbolt-slang-extra-args nil
"Extra command-line arguments for slangc.
Either a single string or a list of strings.
Set via .dir-locals.el, e.g. \\='(\"-I./include\" \"-DFOO=1\").")
(put 'rmsbolt-slang-extra-args 'safe-local-variable #'my/rmsbolt-extra-args-safe-p)For example, in a project’s .dir-locals.el:
((slang-mode . ((rmsbolt-slang-extra-args . ("-I./shaders/include" "-DUSE_BINDLESS=1"))))
(glsl-mode . ((rmsbolt-glsl-extra-args . ("-I./shaders/include")))))Then we use slangc to compile to SPIR-V assembly with debug info (-target spirv-asm -g2).
(cl-defun my/slang-rmsbolt-compile-cmd (&key src-buffer)
"Process a compile command for slangc -> SPIR-V assembly.
Use SRC-BUFFER as buffer containing local variables."
(rmsbolt--with-local-files
src-buffer
(let* ((cmd (buffer-local-value 'rmsbolt-command src-buffer))
(extra (my/rmsbolt-extra-args-to-list
(buffer-local-value 'rmsbolt-slang-extra-args src-buffer)))
(cmd (string-join
(append (list cmd
src-filename
"-target" "spirv-asm"
"-g2")
extra
(list "-o" output-filename))
" ")))
cmd)))Finally we can define a front-end.
((eq major-mode 'slang-mode)
(setq rmsbolt-language-descriptor
(make-rmsbolt-lang :compile-cmd "slangc"
:supports-asm t
:supports-disass nil
:compile-cmd-function #'my/slang-rmsbolt-compile-cmd
:process-asm-custom-fn #'my/rmsbolt-process-spirv-asm-lines)))This does not work for now because 1: (language-id-buffer) does not take slang, 2: format-all does not take “SLANG” as language either. I need to make a PR. 3: clang-format’s hlsl mode is WIP.
(setf (alist-get "SLANG" format-all-default-formatters nil nil #'equal) '(slang-format))
(define-format-all-formatter slang-format
(:executable "clang-format")
(:install
(macos "brew install clang-format")
(windows "scoop install llvm"))
(:languages "SLANG")
(:features region)
(:format
(format-all--buffer-easy
executable
;; override slang to format as hlsl code
"-assume-filename" ".hlsl"
(when region
(list "--offset" (number-to-string (1- (car region)))
"--length" (number-to-string (- (cdr region) (car region))))))));; glsl
(use-package glsl-mode
:ensure t
:defer t
:init
(defun my/glsl-mode-hook ()
<<GLSL_LSP>>
)
:mode (("\\.glsl\\'" . glsl-mode)
("\\.vert\\'" . glsl-mode)
("\\.frag\\'" . glsl-mode)
("\\.geom\\'" . glsl-mode)
("\\.comp\\'" . glsl-mode)
("\\.rgen\\'" . glsl-mode)
("\\.rchit\\'" . glsl-mode)
("\\.rmiss\\'" . glsl-mode))
:hook
((glsl-mode . company-mode)
(glsl-mode . my/glsl-mode-hook)))GLSL has a language server company-glsl
(use-package company-glsl
:defer t
:ensure t
:after company)It depends on glslangValidator to be available.
(when (executable-find "glslangValidator")
(make-variable-buffer-local 'company-backends)
(add-to-list 'company-backends 'company-glsl))Let’s allow extra args in the loop
(defvar-local rmsbolt-glsl-extra-args nil
"Extra command-line arguments for glslangValidator.
Either a single string or a list of strings.
Set via .dir-locals.el, e.g. \\=(slang-mode . (\"-I./include\" \"-DFOO=1\").")
(put 'rmsbolt-glsl-extra-args 'safe-local-variable #'my/rmsbolt-extra-args-safe-p) (defun my/rmsbolt-glsl-compile-cmd (&key src-buffer)
"Process a compile command for glslangValidator.
Use SRC-BUFFER as buffer containing local variables."
(rmsbolt--with-local-files
src-buffer
(let* ( ;; Turn off passing the source file if we find compile_commands
(cmd (buffer-local-value 'rmsbolt-command src-buffer))
(extra (my/rmsbolt-extra-args-to-list
(buffer-local-value 'rmsbolt-glsl-extra-args src-buffer)))
(cmd (string-join
(append (list cmd
"-gV" "--spirv-dis")
extra
(list src-filename "-H"
">" output-filename))
" ")))
cmd)))((eq major-mode 'glsl-mode)
(setq rmsbolt-language-descriptor
(make-rmsbolt-lang :compile-cmd "glslangValidator"
:supports-asm t
:supports-disass nil
:compile-cmd-function #'my/rmsbolt-glsl-compile-cmd
:process-asm-custom-fn #'my/rmsbolt-process-spirv-asm-lines)))
;; hlsl
(use-package hlsl-mode
:vc (:fetcher github :repo "xeechou/hlsl-mode.el")
:defer t
:mode (("\\.fxh\\'" . hlsl-mode)
("\\.hlsl\\'" . hlsl-mode)
("\\.vs\\'" . hlsl-mode)
("\\.ps\\'" . hlsl-mode)
("\\.hs\\'" . hlsl-mode) ;;hull shader
("\\.ds\\'" . hlsl-mode) ;;domain shader
("\\.cs\\'" . hlsl-mode) ;;compute shader
("\\.ms\\'" . hlsl-mode) ;;mesh shader
("\\.as\\'" . hlsl-mode) ;;amplification shader
("\\.lib\\'" . hlsl-mode) ;;ray-tracing shader library
)
:init
(defun my/hlsl-mode-hook ()
<<HLSL_LSP>>
)
:hook ((hlsl-mode . company-mode)
;;TODO there is some bug on windows that :config block always run. I
;;have to put it in hook
(hlsl-mode . my/hlsl-mode-hook)))setup the slangd as language server if it’s available
(if (and (executable-find "slangd")
(eq major-mode 'hlsl-mode)) ;; only enables for hlsl-mode
;;available
(progn
(eglot-ensure)
(add-to-list 'eglot-server-programs
`(hlsl-mode . ("slangd")))
(add-to-list 'company-backends 'company-capf))
;; not available
(my/merge-list-buffer-local 'company-backends
(list 'company-keywords 'company-dabbrev)))Currently AZSL does not have a language server so no company-capf available. I only use company-dabbrev and company-keywords.
(use-package azsl-mode
:vc (:fetcher github :repo "xeechou/azsl-mode.el")
:defer t
:mode (("\\.azsl\\'" . azsl-mode)
("\\.azsli\\'" . azsl-mode))
:hook ((azsl-mode . company-mode)))(use-package shader-mode
:disabled
:mode (("\\.shader\\'" . hlsl-mode))) ((python-mode python-ts-mode) . company-mode)
;;don't forget adding company backends!!
((python-mode python-ts-mode) . (lambda () (my/merge-list-buffer-local
'company-backends
(list 'company-dabbrev 'company-capf))))((python-mode python-ts-mode) . eglot-ensure)Setup Python virtualenvs
You also need the language servers available for Emacs, Emacs has to know when to find the packages, the best way is to setup per-project virtual environments, or you can setup the global virtual environments
(use-package pyvenv :ensure t)In the .dir-locals.el, it can be setup like this
((python-mode . ((eval . (pyvenv-activate "~/.virtualenvs/venv"))
)))If using global pipx server, this can be done as well.
NOTE that for python I use pipx to install python-lsp-server. For the other packages installed with pipx. You need to inject them into python-lsp-server to have company-capf working.
pipx inject python-lsp-server [[your packages here]];;cmake
(use-package cmake-mode
:ensure t :defer t
:mode (("/CMakeLists\\.txt\\'" . cmake-mode)
("\\.cmake\\'" . cmake-mode)));;mesonbuild
(use-package meson-mode
:ensure t
:defer t
:mode (("/meson\\.build\\'" . meson-mode))
:hook ((meson-mode . company-mode))
)(use-package rust-mode :ensure t :defer t
:mode (("\\.rs\\'" . rust-mode))
:hook ((rust-mode rust-ts-mode) . company-mode)
:init
(when (executable-find "rust-analyzer")
(add-hook 'rust-mode-hook 'eglot-ensure)
(add-hook 'rust-ts-mode-hook 'eglot-ensure)));; golang
(use-package go-mode :ensure t :defer t
:mode (("\\.go\\'" . go-mode)
("\\.mode\\'" . go-mode))
:hook ((go-mode . (lambda () (add-hook 'before-save-hook 'gofmt-before-save nil t)))));;javascript
(use-package rjsx-mode
:ensure t
:defer t
:mode (("\\.js\\'" . rjsx-mode))
:config (setq js-indent-level 2)
)
(use-package web-mode
:ensure t
:defer t
:pin melpa
:mode ("\\.html?\\'" . web-mode))
;;typescript
(use-package typescript-mode
:ensure t
:defer t
:mode (("\\.ts\\'" . typescript-mode))
:config
(setq typescript-indent-level 2)
(setq-default indent-tabs-mode nil)
)
(use-package json-mode
:ensure t
:defer t
:pin melpa
:mode (("\\.json\\'" . json-mode)
;; O3DE passes and assets use json format
("\\.pass\\'" . json-mode)
("\\.azasset\\'" . json-mode)
("\\.setreg\\'" . json-mode)
("\\..setregpatch\\'" . json-mode)
("wireplumber\\.conf\\'" . json-mode)));;dart
(use-package dart-mode
:ensure t
:defer t
:mode (("\\.dart\\'" . dart-mode))
:config
(with-eval-after-load 'projectile
(projectile-register-project-type 'flutter '("pubspec.yaml")
:project-file "pubspec.yaml"
:compile "flutter build"
:test "flutter test"
:run "flutter run"
:src-dir "lib/"))
)The beancount is a text based double entry ledger system.
(use-package beancount
:when (executable-find "bean-check")
:vc (:fetcher github :repo "beancount/beancount-mode")
:defer t
:mode (("\\.beancount\\'" . beancount-mode))
:config
(setq-local electric-indent-chars nil)
:hook
((beancount-mode . outline-minor-mode)
(beancount-mode . flymake-bean-check-enable))
:bind (:map beancount-mode-map
("C-c C-n" . outline-next-visible-heading)
("C-c C-p" . outline-previous-visible-heading))
)Notes:
- The automatic indentation behavior
electric-indent-charsmay be undesired for beancount. Disable it - enable the
outline-minor-modefor managing large text
I use pipx to install beancount locally, you can follow the same process
# this creates a python venv for beancount
pipx install beancount
#inject fava to beancount venv and adding "fava" executable
pipx inject --include-apps beancount fava(use-package lean4-mode :ensure t :defer t
:commands lean4-mode
:vc (:fetcher "https://github.com/leanprover-community/lean4-mode.git"
:rev :last-release
;; Or, if you prefer the bleeding edge version of Lean4-Mode:
;; :rev :newest
)
:mode ("\\.lean\\'". lean4-mode))The org-babel is supported, see ob-lean4.el and org-funcs-load-babel-compiler at org-funcs.el
installing lean on Linux and Mac is through package manager
brew install elan-init #on Mac
paru -S elan-init #on ArchOn windows is not very straight-forward, since they just recommend using VsCode, but it is still doable
#download the installing script
curl -O --location https://elan.lean-lang.org/elan-init.ps1
#this install lean under $HOME/.elan/bin
powershell -ExecutionPolicy Bypass -f elan-init.ps1
#remeber to add $HOME/.elan/bin to $PATHlake is the lean package manager, you can use it create project and compile it
lake new /path/to/project
cd /path/to/project && lake buildOr after cloning the project, retrieve the packages
git clone https://github.com/<repository owner>/<repository name>.git /path/to/project
cd /path/to/project
lake exe cache getcreating a project depend on mathlib
lake +leanprover-community/mathlib4:lean-toolchain new mathliblean-tutorial math Updating the math-lib, somehow need to update it manually for now
curl -L https://raw.githubusercontent.com/leanprover-community/mathlib4/master/lean-toolchain -o lean-toolchain
lake update ;;lua
(use-package lua-mode :ensure t :defer t
:mode (("\\.lua\\'" . lua-mode))
:hook
((lua-mode . company-mode)
(lua-mode . (lambda ()
(when (executable-find "lua-language-server")
(eglot-ensure))))))
;;graphviz dot
(use-package graphviz-dot-mode :ensure t :defer t
:mode (("\\.dot\\'" . graphviz-dot-mode)))
(use-package gdscript-mode :ensure t :defer t
:mode (("\\.gd\\'" . gdscript-mode)))
(use-package octave :ensure t :defer t
:mode (("\\.m\\'" . octave-mode)))
(use-package yaml-mode :ensure t :defer t
:mode (("\\.yml\\'" . yaml-mode)))
(use-package powershell :ensure t :defer t
:mode (("\\.ps1\\'" . powershell-mode)))
;; jenkins
;; Jenkins/Groovy support
(use-package groovy-mode :ensure t :defer t)
(use-package jenkinsfile-mode
:after groovy-mode
:vc (:fetcher github :repo "john2x/jenkinsfile-mode")
:mode (("Jenkinsfile\\'" . jenkinsfile-mode)))
;;universal scene descriptor
(use-package usda-mode
:defer t
:load-path "lisp/"
:mode (("\\.usda\\'" . usda-mode)
("\\.usd\\'" . usda-mode))
;; disable fmo-mode because usda-mode inherit c-mode
:hook (usda-mode . (lambda () (fmo-mode -1))))
;; built-in
(use-package nxml-mode
:defer t
:mode (("\\.xml\\'" . nxml-mode)
("\\.ofx\\'" . nxml-mode)
;;quick book
("\\.qfx\\'" . nxml-mode)))personally I use a device local llm-api.el to register all my keys.
(let ((llm-api-file (expand-file-name "llm-api.el" (my/dotfile-dir))))
(when (file-exists-p llm-api-file)
(load llm-api-file)))(define-prefix-command 'my/llm-keymap)
(global-set-key (kbd "C-c i") 'my/llm-keymap)Aider (disabled in favor of eca)
Aider is a command line based project-wise AI chat bot, the features is like adding multiple files to aider like aider <file1> <file2> ... then you can ask it to add new features, tests. Fix bugs, refactoring code, update docs.
(use-package vterm :if (executable-find "vterm-ctrl") :ensure t :pin melpa)
(use-package aidermacs :disabled
:when (file-exists-p (expand-file-name "llm-api.el" (my/dotfile-dir)))
:pin melpa :ensure t :defer t
:bind (:map my/llm-keymap ("a" . aidermacs-transient-menu))
:init
(if (executable-find "vterm-ctrl")
(setq aidermacs-backend 'vterm))
:config
<<AIDER_BACKENDS>>
:custom
;; Disable auto-commits
(aidermacs-auto-commits nil)
)
The code gets evaluated sequentially, the later models gets the higher priorities.
(defun my/aider-use-openai ()
"changing the default model to use OpenAI"
(interactive)
(if (boundp 'my/llm-openai-key)
(progn (setenv "OPENAI_API_KEY" my/llm-openai-key)
(setq aidermacs-default-chat-mode 'architect)
(setq aidermacs-default-model "gpt-4o")
(message "Aider now use OpenAI"))
(message "OPENAI API Key not available")))(defun my/aider-use-claude-architect ()
"changing Aider to use Claude based model"
(interactive)
(if (boundp 'my/llm-anthropic-key)
(progn (setenv "ANTHROPIC_API_KEY" my/llm-anthropic-key)
;;setup corresponding models
(setq aidermacs-default-chat-mode 'architect)
(setq aidermacs-default-model "sonnet")
(setq aidermacs-editor-model "opus")
(message "Aider now use claude models"))
(message "Anthropic API key not available")))
(defun my/aider-use-claude-code ()
"changing Aider to use Claude based model Without architect mode"
(interactive)
(if (boundp 'my/llm-anthropic-key)
(progn (setenv "ANTHROPIC_API_KEY" my/llm-anthropic-key)
(setq aidermacs-default-chat-mode 'code)
(setq aidermacs-default-model "sonnet"))
(message "Anthropic API key not available")))Copilot supports openAI like API, the token is in your .config/github-copilot/app.json
Note that you need to get the key from JetBrain IDEs to avoid access to this endpoint is forbidden error.
(defun my/aider-use-copilot ()
"changing the Aider to use copilot based models"
(interactive)
(if (boundp 'my/llm-copilot-key)
(progn
(setenv "OPENAI_API_BASE" "https://api.githubcopilot.com")
(setenv "OPENAI_API_KEY" my/llm-copilot-key)
(setq aidermacs-default-chat-mode 'architect)
(setq aidermacs-default-model "openai/claude-sonnet-4")
(setq aidermacs-editor-model "openai/gpt-4o")
(message "Aider now use copilot"))
(message "Copilot API key not available")))
Enables Ollama when the port is enabled.
(when (and (my/test-port-open "127.0.0.1" 11434)
(boundp 'my/llm-ollama-model))
(setenv "OLLAMA_API_BASE" "http://127.0.0.1:11434")
(setq aidermacs-default-caht-mode 'ask)
(setq aidermacs-default-model (concat "ollama_chat/" my/llm-ollama-model))
)Firstly we need methods to securely store our api keys
(use-package gptel
:when (file-exists-p (expand-file-name "llm-api.el" (my/dotfile-dir)))
:pin melpa :ensure t :defer t
:custom
(gptel-default-mode 'org-mode)
:bind
(:map my/llm-keymap ("g" . gptel-menu))
:config
;;setup directives
(setq gptel-directives
(delete-dups
(append gptel-directives
'(
<<GPTEL_DIRECTIVES>>
))))
;;setup backends
<<GPTEL_BACKENDS>>
;;setup proxies
<<GPTEL_PROXY>>
;;tools
<<GPTEL_TOOLS>>
)(cliwiz . "You are a command line helper. Generate command line that do what requested. Without any additional description or explaination. Generate ONLY the command, I will edit before running")(explain . "Explain what this code does to a novice programmer")(commit . "You are an expert at writing Git commit messages.
Generate **only** the commit message, nothing else.
DECISION PROCESS:
1. Count changed files
2. If 1 file: check if change is simple or complex
3. Apply the appropriate format
FORMAT RULES:
A. Single File + Simple Change (one clear purpose):
* path/to/file: Description. (≤72 chars)
NO subject line, NO blank lines, JUST this one line.
B. Single File + Complex Change (multiple purposes/major refactor):
Subject line (≤50 chars, imperative mood, NO period)
Optional body paragraph explaining why (wrap at 72 chars).
* path/to/file (func1, func2): Description.
C. Multiple Files (2+ files changed):
Subject line (≤50 chars, imperative mood, NO period)
Optional body paragraph explaining why (wrap at 72 chars).
* path/to/file1 (func1): Description.
* path/to/file2 (func2): Another description.
D. Trivial Changes:
Add `; ` prefix for typos/comments/docs.
Example: `; * file: Fix typo.`
SIMPLE vs COMPLEX (single file):
- Simple: one function, one clear fix/addition
- Complex: multiple functions, refactoring, or architectural change")gptel wiki has a collection of tools, some of them are listed below, you can also check out llm-tool-collection repo.
(gptel-make-tool
:name "read_buffer" ; javascript-style snake_case name
:function (lambda (buffer) ; the function that will run
(unless (buffer-live-p (get-buffer buffer))
(error "error: buffer %s is not live." buffer))
(with-current-buffer buffer
(buffer-substring-no-properties (point-min) (point-max))))
:description "return the contents of an emacs buffer"
:args (list '(:name "buffer"
:type string ; :type value must be a symbol
:description "the name of the buffer whose contents are to be retrieved"))
:category "emacs") ; An arbitrary label for grouping(gptel-make-tool
:name "create_file" ; javascript-style snake_case name
:function (lambda (path filename content) ; the function that runs
(let ((full-path (expand-file-name filename path)))
(with-temp-buffer
(insert content)
(write-file full-path))
(format "Created file %s in %s" filename path)))
:description "Create a new file with the specified content"
:args (list '(:name "path" ; a list of argument specifications
:type string
:description "The directory where to create the file")
'(:name "filename"
:type string
:description "The name of the file to create")
'(:name "content"
:type string
:description "The content to write to the file"))
:category "filesystem") ; An arbitrary label for grouping
(gptel-make-tool
:function (lambda (filepath)
(with-temp-buffer
(insert-file-contents (expand-file-name filepath))
(buffer-string)))
:name "read_file"
:description "Read and display the contents of a file"
:args (list '(:name "filepath"
:type string
:description "Path to the file to read. Supports relative paths and ~."))
:category "filesystem")
(gptel-make-tool
:name "get_youtube_transcript"
:function (lambda (url)
(with-temp-buffer
(ytb-sub-dl-insert-transcript url)
(buffer-substring-no-properties (point-min) (point-max))))
:description "Getting the Youtube video transcript"
:args (list '(:name "url"
:type string
:description "The YouTube URL to download"))
:category "web")(require 'org-web-tools)
(gptel-make-tool
:name "read_url"
:function (lambda (url) (org-web-tools--url-as-readable-org url))
:description "Fetch and read the contents of a URL"
:args (list '(:name "url"
:type string
:description "The URL to read"))
:category "web")(when (boundp 'my/llm-openai-key)
(setq gptel-api-key my/llm-openai-key))I prefer the google genimi, set it to default if available
(when (boundp 'my/llm-gemini-key)
(gptel-make-gemini "Gemini"
:key "YOUR_GEMINI_API_KEY"
:key my/llm-gemini-key
:stream t))(when (boundp 'my/llm-anthropic-key)
(gptel-make-anthropic "Claude"
:stream t
:key my/llm-anthropic-key))Ollama backend runs locally on your pc
(when (and (my/test-port-open "127.0.0.1" 11434)
(boundp 'my/llm-ollama-model))
(gptel-make-ollama "Ollama"
:host "localhost:11434"
:stream t
:models `(,my/llm-ollama-model)))(when (boundp 'my/llm-copilot-key)
(gptel-make-gh-copilot "Copilot"))Unfortunately my proxy requires some additional arguments for curl -k or --ssl-no-revoke.
;;initialize the proxy
(when (and gptel-use-curl (my/has-or-get-proxy))
(setq gptel-proxy (my/has-or-get-proxy)))
(defun my/gptel-toggle-proxy ()
"Toggle the gptel proxy based on the environment."
(interactive)
(when (and gptel-use-curl (my/has-or-get-proxy))
(if (string= gptel-proxy "")
(setq gptel-proxy (my/has-or-get-proxy))
(setq gptel-proxy ""))))We add additional backend for when using proxy
;; openai
(when (and (my/has-or-get-proxy) (boundp 'my/llm-openai-key))
(gptel-make-openai "ChatGPT-no-ssl-check"
:key 'my/llm-openai-key
:stream t
:models '("gpt-3.5-turbo" "gpt-3.5-turbo-16k" "gpt-4"
"gpt-4-turbo-preview" "gpt-4-32k" "gpt-4-1106-preview"
"gpt-4-0125-preview")
:curl-args '("-k")))ECA is LSP protocol for llms
(use-package eca :ensure t :pin melpa
:config
(when (boundp 'my/llm-anthropic-key)
(setenv "ANTHROPIC_API_KEY" my/llm-anthropic-key))
(when (boundp 'my/llm-openai-key)
(setenv "OPENAI_API_KEY" my/llm-openai-key)))ECA can call external tools, most likely it is some kind of command line tool. For example, this tool use trafilatura to download the web page and convert that into markdown files.
{
"customTools": {
"web-search": {
"description": "Fetches the content of a URL and returns it in Markdown format.",
"command": "trafilatura --output-format=markdown -u {{url}}",
"schema": {
"properties": {
"url": {
"type": "string",
"description": "The URL to fetch content from."
}
},
"required": ["url"]
}
}
}
}Install trafilatura with pipx install ~trafilatura.
;; install required inheritenv dependency:
(use-package inheritenv
:vc (:fetcher github :repo "purcell/inheritenv" :rev :newest))
;;Monet is an Emacs package that implements the (undocumented) Claude Code IDE
;;protocol, enabling Claude to interact with your Emacs environment through a
;;WebSocket connection.
(use-package monet
:vc (:fetcher github :repo "stevemolitor/monet" :rev :newest))
;; for eat terminal backend:
(use-package eat :ensure t)
(use-package claude-code :ensure t
:when (executable-find "claude")
:vc (:fetcher github :repo "stevemolitor/claude-code.el" :rev :newest)
:config
(setq claude-code-terminal-backend 'eat)
;; optional IDE integration with Monet
(add-hook 'claude-code-process-environment-functions #'monet-start-server-function)
(monet-mode 1)
(claude-code-mode)
:bind-keymap
("C-c i c" . claude-code-command-map)
;; Optionally define a repeat map so that "M" will cycle thru Claude
;; auto-accept/plan/confirm modes after invoking claude-code-cycle-mode / C-c
;; M.
:bind
(:repeat-map my-claude-code-map ("M" . claude-code-cycle-mode))
)openwith to open external program for file types.
(use-package openwith
:vc (:fetcher github :repo "garberw/openwith" :rev "master")
:init (openwith-mode 1)
:config (setq openwith-associations '(("\\.pdf\\'" "sioyek" (file))))) (use-package which-key :ensure t
:diminish which-key-mode
:hook ((prog-mode text-mode outline-mode) . which-key-mode))pdftools disabled
;; pdf-tools, only run this on windows
(use-package pdf-tools
:if (eq system-type 'windows-nt)
:disabled
:defer t
:pin manual
:magic ("%PDF" . pdf-view-mode)
:config
(pdf-tools-install)
(setq-default pdf-view-display-size 'fit-width)
(define-key pdf-view-mode-map (kbd "C-s") 'isearch-forward)
:custom
(pdf-annot-activate-created-annotations t "automatically annotate highlights"))
we configure Emacs with elfeed to read the news.
(use-package elfeed :ensure t :defer t
:config (elfeed-set-max-connections 32))Here are some of the key bindings for elfeed:
- RET
- view selected entry in a buffer
- b
- open selected entries in your browser (browse-url)
- y
- copy selected entries URL to the clipboard
- r
- mark selected entries as read
- u
- mark selected entries as unread
- g
elfeed-update-forceupdating the feeds after modified “elfeed.org”- s
- search/filter, however it also sort the feed by source.
- +
- add a specific tag to selected entries
- -
- remove a specific tag from selected entries
We load elfeed-org and elfeed-goodies as well.
(use-package elfeed-org
:when (file-exists-p (expand-file-name "elfeed.org" org-directory))
:ensure t
:after elfeed
:config
(elfeed-org)
(setq rmh-elfeed-org-files
(list
(expand-file-name "elfeed.org" org-directory))))The elfeed goodies sort the feed based alphabetically, and reorder the layout
(use-package elfeed-goodies
:ensure t
:after elfeed
:config
(elfeed-goodies/setup))I would like to open feed buffer using elfeed-webkit but my Emacs does not support xWidgets.