emacs-config/init.org

1924 lines
73 KiB
Org Mode
Raw Normal View History

#+TITLE: My Emacs Configuration
2024-08-07 09:51:49 +02:00
#+PROPERTY: header-args :tangle yes
#+PROPERTY: header-args:emacs-lisp :lexical yes :tangle yes :comments both :padline yes
* Literate Configuration for Emacs
2024-08-04 01:37:17 +02:00
** Goals
- Manage *init.el* as an org-file
- Use a simple robust tangling solution which works together with
Crafted Emacs
- Leverage inclusion of ~use-package~ in Emacs29 for configuration and
loading
- Reduce dependencies : read evaluate the value a package brings
before including it
- Refactor existing configuration
** Notes on Elpaca and dev versions of emacs.
Elpaca needs the build date of emacs to compare to package versions or
something. However it does not support all dev versions.
For guix emacs-next packages you can find the date with: ( <C-c C-c>
in the source block below:
#+BEGIN_SRC shell
stat /gnu/store/*emacs-next-[23]*.drv | rg Birth | cut -d' ' -f3 | tr -d '-'
#+END_SRC
#+RESULTS:
| 20240727 |
| 20240727 |
2024-08-02 14:49:05 +02:00
It is possible there are more so probably the most recent one is the
one to use.
2024-07-30 16:45:22 +02:00
** Inspiration Sources
2024-07-30 16:45:22 +02:00
- [[https://pages.sachachua.com/.emacs.d/Sacha.html][Sacha Chua's Emacs config]]
- [[https://github.com/jwiegley/use-package][GitHub repo for use-package]]
- [[https://github.com/jwiegley/dot-emacs/blob/master/init.org][John Wiegley's .emacs file]]
- [[https://github.com/TheBB/dotemacs/blob/master/init.el][Eivind Fonn's init.el]]
2024-08-04 01:37:17 +02:00
* First Things First
2024-08-02 14:49:05 +02:00
** Make tangled file read-only
Add a preamble to the file to ensure some global settings, most
importantly make the file read-only to avoid editing the tangled file by
accident instead of the source in the org-mode file.
#+BEGIN_SRC emacs-lisp
2024-08-04 01:37:17 +02:00
;; init.el --- Literate configuration for Emacs -*- lexical-binding: t; read-only-mode: t; -*-
;;
;;; Commentary:
;;
;; DO NOT EDIT!!!
;;
;; This file is automatically generated from the source in *init.org*.
;;
#+END_SRC
Also immediately set lexical binding mode.
** Set the garbage collector threshold, to avoid collections
#+begin_src emacs-lisp
(setq gc-cons-percentage 0.5
gc-cons-threshold (* 128 1024 1024))
#+end_src
** Report time spent loading the configuration
#+begin_src emacs-lisp
2024-08-04 01:37:17 +02:00
(defconst emacs-start-time (current-time))
(defun report-time-since-load (&optional suffix)
"Report the time since the file was init script was started.
2024-08-04 01:37:17 +02:00
If SUFFIX is provided, it is appended to the message."
(message "Loading init... (%.3fs) %s"
(float-time (time-subtract (current-time) emacs-start-time))
suffix))
2024-08-04 01:37:17 +02:00
(add-hook 'after-init-hook
#'(lambda () (report-time-since-load " [after-init]"))
t)
2024-08-07 09:51:49 +02:00
(report-time-since-load "start init file")
#+end_src
When looking for where the time goes, the `report-time-since-load`
with a string indicating the location in the init process can report
on the time since start.
*** Save customizations in a separate file
By default customization settings are saved at the end of the *init.el*
file. This wreaks havoc with managing the files in git and will not
work with the tangled version anyway as it will be removed/overwritten
each time the file is regenerated.
Here we set the location of the file to save the customizations and
then load it.
#+BEGIN_SRC emacs-lisp
;;; Code:
(setq custom-file (concat user-emacs-directory "custom.el"))
(when (and custom-file
(file-exists-p custom-file))
(load custom-file nil :nomessage))
#+END_SRC
** Utility Functions
** Get latest version of a github released project
Many projects nowadays use github to release their software. However
there is no easy way to get the latest version of a project
provided. This functions uses the releases API to get the latest
metadata and get the version number from the JSON.
#+BEGIN_SRC emacs-lisp
(defun pti-with-latest-github-version (repo f)
"Return the latest version of the releases for REPO.
The repo should be in the form of 'owner/repo'. The function F
will be called with the version in format 'vX.Y.Z' as the only argument."
(url-retrieve
(format "https://api.github.com/repos/%s/releases/latest" repo)
(lambda (events)
(message "Events: %s" events)
(goto-char url-http-end-of-headers)
(let ((json-object-type 'plist)
(json-key-type 'symbol)
(json-array-type 'vector))
(let ((result (json-read)))
(message "Latest version: %s" (plist-get result 'name))
(funcall f (plist-get result 'name))
)))))
#+END_SRC
* Crafted Emacs
When I could not solve the persistent slowness I experienced on
Windows using Doom Emacs, I wanted to switch to a more hands-on
minimal Emacs configuration. I just watched the 'Rational Emacs'
episode and that seemed as good a starting point as any. Then in
August '22 it was renamed to Crafted Emacs which prompted a
(minor) refactoring.
I used for a quite a long term Doom on Linux and Rational/Crafted on
Windows till I was comfortable enough and not started Doom anymore.
** Initialize Crafted Emacs
Loads the Crafted Emacs initialization configuration:
#+BEGIN_SRC emacs-lisp
2024-08-02 14:49:05 +02:00
;; initialize crafted-emacs
(setq crafted-startup-inhibit-splash t)
(load "~/.local/share/crafted-emacs/modules/crafted-init-config")
#+END_SRC
** Add and configure packages and load crafted and custom modules
Lists and installs a variety of packages and includes custom configurations for them:
#+BEGIN_SRC emacs-lisp
2024-08-02 14:49:05 +02:00
(require 'crafted-completion-packages)
(require 'crafted-evil-packages)
(require 'crafted-lisp-packages)
(require 'crafted-writing-packages)
(require 'crafted-ui-packages)
(message "loading packages")
(crafted-package-install-selected-packages)
(elpaca-wait)
(require 'crafted-defaults-config)
(require 'crafted-evil-config)
(require 'crafted-completion-config)
(require 'crafted-org-config)
(require 'crafted-lisp-config)
(require 'crafted-writing-config)
(require 'crafted-ui-config):
#+END_SRC
#+RESULTS:
: pti-org-config
* Integration with Environment
** Set default Coding System to Use UTF-8 Everywhere
Ensures UTF-8 is the default coding system everywhere.
#+BEGIN_SRC emacs-lisp
(set-default-coding-systems 'utf-8)
(set-language-environment 'utf-8)
(setq locale-coding-system 'utf-8)
(prefer-coding-system 'utf-8)
;; Treat clipboard input as UTF-8 string first; compound text next, etc.
(setq x-select-request-type '(UTF8_STRING COMPOUND_TEXT TEXT STRING))
#+END_SRC
Heres a breakdown of what each line does:
1. *(set-default-coding-systems 'utf-8)*
This line sets the default coding system for new buffers. When you
create a new buffer or open a file, Emacs will use UTF-8 encoding
by default. It will also set the default terminal and keyboard
coding systems. This applies to all internal operations where a
specific coding system has not been specified.
2. *(set-language-environment 'utf-8)*
This sets the language environment to UTF-8. Emacs uses the
language environment to guess the preferred coding systems for
reading and writing files and for other operations. Setting this to
UTF-8 ensures that UTF-8 is preferred in all language-related
contexts.
3. *(setq locale-coding-system 'utf-8)*
This sets the coding system for locale data, such as environment
variables and system messages. It ensures that Emacs correctly
interprets UTF-8 encoded data coming from the operating system.
4. *(prefer-coding-system 'utf-8)*
This makes UTF-8 the preferred coding system for any situation
where Emacs needs to choose an encoding. It ensures that Emacs
prefers UTF-8 over other encodings.
5. *(setq x-select-request-type ...)*
Treat clipboard input as UTF8_STRING first, compound text next,
etc... .
** Set Path from shell configuration
In order to get the paths in Emacs to be consistent with the ones in
the terminals we get them from a started shell instead of the current
environment which can be considerably different in X, Wayland or on
Mac because the shell initialization scripts have not run yet.
#+BEGIN_SRC emacs-lisp
;; set path from shell when started in UI-RELATED
(use-package exec-path-from-shell
:ensure t
:defer 1
:if (or (daemonp) (memq window-system '(mac ns x)))
:config (exec-path-from-shell-initialize))
#+END_SRC
** Setup backup directories
Configures where backup files are stored:
#+BEGIN_SRC emacs-lisp
;; setup backup directories
;; see https://www.emacswiki.org/emacs/BackupDirectory
(setq backup-directory-alist
`(("." . ,(file-name-concat user-emacs-directory "backups"))))
#+END_SRC
** Enable integration with the Unix Password Store aka *pass*
The *pass* command gives a super practical way to store secrets
encrypted using *gpg* and use them in *.envrc* files, batch scripts on the
command line and, of course, in *Emacs*.
#+BEGIN_SRC emacs-lisp
;; enable unix password-store
;;(use-package epg)
;;(setq epg-pinentry-mode 'loopback)
(auth-source-pass-enable)
#+END_SRC
This enables *pass* secrets to be used for all subsystems supporting
*auth-source* (which are probably all of them nowadays). It does require
some finagling to map the parts on the name in the pass system.
- [[https://www.passwordstore.org/][Pass Website]]
- [[info:auth#The Unix password store][auth#The Unix password store in the info pages]]
*** Use of Pass Secrets in ELisp
It is very convenient to get secrets from this store (once gpg is set
up, which a totally different can of worms). A function
`auth-source-pass-get` is provided :
#+BEGIN_SRC emacs-lisp :tangle no
(auth-source-pass-get 'secret "dummy/password")
#+END_SRC
#+RESULTS:
: shht!secret
** GUIX support
[[https://gitlab.com/emacs-guix/emacs-guix][Emacs-guix]] is a module to interact with the guix system and help
manage packages and profiles. It also offers support for creating
additional profiles and packages for Guix.
#+BEGIN_SRC emacs-lisp
(use-package guix
:ensure t
:defer 5)
#+END_SRC
I find it a bit a confusing module.
It provides a minor-mode
2024-08-04 01:37:17 +02:00
** Add a fully featured terminal emulator
#+BEGIN_SRC emacs-lisp
(use-package eat
:ensure t
:defer 5)
#+END_SRC
#+RESULTS:
: [nil 26285 20099 685413 nil elpaca-process-queues nil nil 626000 nil]
* Editor Features
** Evil Vim Keybindings
I configure all my apps to use vim keybindings if possible to maximise
muscle memory.
#+BEGIN_SRC emacs-lisp
;; some provided automations
(crafted-evil-discourage-arrow-keys)
(crafted-evil-vim-muscle-memory)
#+END_SRC
#+BEGIN_SRC emacs-lisp
;; set leader keys
(evil-set-leader nil (kbd "C-SPC"))
(evil-set-leader 'normal (kbd "SPC"))
(evil-set-leader 'normal "," t) ;; set localleader
#+END_SRC
#+BEGIN_SRC emacs-lisp
;; make vc commands available via leader key
(evil-define-key 'normal 'global
(kbd "<leader>v") vc-prefix-map)
#+END_SRC
#+RESULTS:
#+BEGIN_SRC emacs-lisp
;; make project commands available via leader key
(evil-define-key 'normal 'global
(kbd "<leader>p") project-prefix-map)
#+END_SRC
#+RESULTS:
#+BEGIN_SRC emacs-lisp
(evil-define-key 'normal 'global
(kbd "<leader>ff") #'find-file
(kbd "<leader>fo") #'recentf
(kbd "<leader>fr") #'revert-buffer
(kbd "<leader>fd") #'diff-buffer-with-file)
#+END_SRC
#+BEGIN_SRC emacs-lisp
;; dired integration
(evil-collection-dired-setup)
#+END_SRC
#+BEGIN_SRC emacs-lisp
(use-package harpoon
:ensure t
:init
(setq pti-harpoon-map (make-sparse-keymap))
(keymap-set pti-harpoon-map (kbd "h") 'harpoon-toggle-quick-menu)
(keymap-set pti-harpoon-map (kbd "a") 'harpoon-add-file)
(keymap-set pti-harpoon-map (kbd "f") 'harpoon-toggle-file)
(keymap-set pti-harpoon-map (kbd "j") 'harpoon-go-to-1)
(keymap-set pti-harpoon-map (kbd "k") 'harpoon-go-to-2)
(keymap-set pti-harpoon-map (kbd "l") 'harpoon-go-to-3)
(keymap-set pti-harpoon-map (kbd ";") 'harpoon-go-to-4)
(keymap-set pti-harpoon-map (kbd "h") 'harpoon-toggle-quick-menu)
:bind
(("<leader>h" . 'pti-harpoon-map)))
#+END_SRC
#+RESULTS:
: [nil 26284 54919 318035 nil elpaca-process-queues nil nil 339000 nil]
** User Interface
*** Do not show default splash screen
#+BEGIN_SRC emacs-lisp
;; Show splash screen
(unless crafted-startup-inhibit-splash
(setq initial-buffer-choice #'crafted-startup-screen))
#+END_SRC
****
#+BEGIN_SRC emacs-lisp
;; Profile emacs startup
(add-hook 'emacs-startup-hook
(lambda ()
(message "Emacs started in %s."
(emacs-init-time))))
#+END_SRC
*** Configure Fonts with Fontaine
#+BEGIN_SRC emacs-lisp
;;
;; Font configuration
;;
(defun pti-find-installed-font (fonts)
"Find the first font in FONTS which is installed on this system."
(seq-find
(lambda (f) (find-font (font-spec :family f)))
fonts))
2024-07-30 16:45:22 +02:00
(defun pti-configure-fonts (&optional frame)
2024-08-04 01:37:17 +02:00
"Set configuration of fonts based on display size.
The FRAME argument makes it possible to set the fonts for new frames by
adding this function to `after-make-frame-functions' which must have
this argument."
(defvar pti-font-size
(if (> (display-pixel-height) 1600) 22 14))
;; set startup and default fontsets to make org-bullet work
(set-fontset-font "fontset-startup" nil "DejaVu Sans Mono" nil)
(set-fontset-font "fontset-default" nil "DejaVu Sans Mono" nil)
(setq fontaine-presets
`((t
:default-family ,(pti-find-installed-font
'("FiraCode NF" "FiraCode Nerd Font"))
:default-weight regular
:default-height ,pti-font-size
:variable-pitch-family ,(pti-find-installed-font
'("Lato" "Literation Sans" "FiraCode NF Propo" "FiraCode Nerd Font Propo" "DejaVu Sans"))
:variable-pitch-height 1.33
:bold-weight heavy
:line-spacing 1
)
(regular
:default-height 100)
(small
:default-height 75)
(medium
:default-height 125)
(large
:default-height 150)))
(fontaine-set-preset 'regular))
(use-package fontaine
:ensure t
:if (display-graphic-p)
2024-08-04 01:37:17 +02:00
:demand t
:config (progn
(pti-configure-fonts)
(add-hook 'after-make-frame-functions #'pti-configure-fonts)))
#+END_SRC
*** Update configuration of each created frame
When running as daemon there is no graphical context. This means that
all graphical related settings cannot be set properly at initial
startup if we need to interrogate the capabilities of the current
screen.
#+BEGIN_SRC emacs-lisp
;;
;; remove chrome from the frames
;; also support client frames
;;
(defun pti-display-tweaks (&optional frame)
2024-08-04 01:37:17 +02:00
"Configure a newly created FRAME."
(interactive)
(unless frame
(setq frame (selected-frame)))
(when (display-graphic-p)
(with-selected-frame frame
;; remove chrome from UI
(menu-bar-mode -1)
(tool-bar-mode -1)
(scroll-bar-mode -1)
)))
(add-hook 'after-make-frame-functions #'pti-display-tweaks)
;; run it in the current frame, because the hooks have already fired
(pti-display-tweaks)
#+END_SRC
2024-08-04 01:37:17 +02:00
#+RESULTS:
*** Set Theme
#+BEGIN_SRC emacs-lisp
;; Set theme
(use-package modus-themes
:ensure t
:demand t
:custom
(modus-themes-italic-constructs t)
(modus-themes-bold-constructs t)
(modus-themes-mixed-fonts t "enable mixed fonts in org and markdown et al.")
(modus-themes-to-toggle '(modus-operandi-tinted modus-vivendi-tinted))
(modus-themes-completions '((matches . (extrabold background intense))
(selection . (semibold accented intense))))
(modus-themes-org-blocks 'tinted-background)
(modus-themes-mixed-fonts t)
(modus-themes-headings '((1 . (monochrome extrabold background overline variable-pitch 1.6))
(2 . (monochrome bold overline 1.4))
(3 . (monochrome semibold overline 1.3))
(4 . (monochrome 1.2))
(5 . (monochrome 1.1))
(agenda-date . (semilight 1.5))
(agenda-structure . (variable-pitch light 1.9))
(t . (monochrome light))))
:config
(load-theme 'modus-operandi-tinted :no-confirm)
:bind
(("<f5>" . #'modus-themes-toggle)))
#+END_SRC
There is a keybinding on *<F5>* to toggle between light and dark mode.
#+RESULTS:
: modus-themes-toggle
*** Limit Height of Selected Popup Windows
#+BEGIN_SRC emacs-lisp
(push '("\\*Occur\\*"
;; display-buffer functions, first one that succeeds is used
(display-buffer-reuse-mode-window
display-buffer-below-selected)
;; Parameters
2024-08-04 01:37:17 +02:00
(window-height . 10))
display-buffer-alist)
(push '("\\*Warnings\\*"
;; display-buffer functions, first one that succeeds is used
(display-buffer-reuse-mode-window
display-buffer-below-selected)
;; Parameters
2024-08-04 01:37:17 +02:00
(window-height . 10))
display-buffer-alist)
(push '("\\*Geiser Debug\\*"
;; display-buffer functions, first one that succeeds is used
(display-buffer-reuse-mode-window
display-buffer-below-selected)
;; Parameters
2024-08-04 01:37:17 +02:00
(window-height . 10))
display-buffer-alist)
;; set custom info folder
(add-to-list 'Info-default-directory-list "~/.local/share/info/")
#+END_SRC
#+RESULTS:
| ~/.local/share/info/ |
a quick way to test this it to generate a warning with
#+BEGIN_SRC emacs-lisp :tangle no
(warn "This is a warning")
#+END_SRC
#+RESULTS:
: t
** Yasnippet configuration
Enables and configures Yasnippet, a template system for Emacs:
#+BEGIN_SRC emacs-lisp
;; configure yasnippet
(use-package yasnippet
:ensure nil
:defer 5
:config
(yas-global-mode 1))
#+END_SRC
** Enable LLM access with Ellama
Configures access to language models using Ellama. I don't know
whether to put it under writing, comms or programming as it is equally
/useful(?)/ for either activity. So I promoted it to an Editor feature.
#+BEGIN_SRC emacs-lisp
2024-08-02 14:49:05 +02:00
(use-package llm
:ensure t
:commands (llm-chat llm-ask-about llm-ask-line llm-ask-selection)
)
(use-package llm-openai
:ensure nil
:commands (make-llm-openai)
:after llm)
;; enable LLM access with ellama
(use-package ellama
:ensure t
:commands (ellama-chat ellama-ask-about ellama-ask-line ellama-ask-selection)
:custom
(ellama-language "English")
(ellama-keymap-prefix "<leader>a")
(ellama-provider
(make-llm-openai
:key (auth-source-pass-get 'secret "snamellit/openai-api-key")
:chat-model "gpt-4o"
))
(ellama-sessions-directory (expand-file-name "~/Dropbox/ellama-sessions"))
:config
(message "Ellama is available"))
#+END_SRC
It seems the *gpt-4o* model provides better responses. I should
investigate local models more.
** Dired Configuration
Enables an alternative file navigation behavior in Dired, Emacs' directory editor:
#+BEGIN_SRC emacs-lisp
(put 'dired-find-alternate-file 'disabled nil)
#+END_SRC
** Use Magit for version control
#+BEGIN_SRC emacs-lisp
;; default to magit for version control
(use-package magit
:ensure t
:commands (magit-status)
:bind
(("C-x p v" . magit-status)
("<leader> p v" . magit-status)))
#+END_SRC
** Better EDiff support
#+BEGIN_SRC emacs-lisp
;; better ediff setting suggested by cwebber
(use-package ediff
:commands (ediff ediff-files ediff3 ediff-files3)
:config
(setq
ediff-window-setup-function 'ediff-setup-windows-plain
ediff-split-window-function 'split-window-horizontally))
#+END_SRC
#+RESULTS:
: t
** Replace normal Buffer Support with IBuffer
#+BEGIN_SRC emacs-lisp
;;; enhance ibuffer with ibuffer-project if it is available.
(defun crafted-ide-enhance-ibuffer-with-ibuffer-project ()
"Set up integration for `ibuffer' with `ibuffer-project'."
(setq ibuffer-filter-groups (ibuffer-project-generate-filter-groups))
(unless (eq ibuffer-sorting-mode 'project-file-relative)
(ibuffer-do-sort-by-project-file-relative)))
2024-08-04 01:37:17 +02:00
(use-package ibuffer-project
2024-08-04 01:37:17 +02:00
:ensure t
:hook
(ibuffer-mode . crafted-ide-enhance-ibuffer-with-ibuffer-project)
:bind (("C-x C-b" . #'ibuffer)))
#+END_SRC
** Enable EditorConfig Standard Support
#+BEGIN_SRC emacs-lisp
;;; load editorconfig support
(use-package editorconfig
:hook
(prog-mode . (lambda() (editorconfig-mode 1))))
#+END_SRC
#+RESULTS:
| editorconfig-mode |
** Enable Direnv Integration
Direnv is the key to have isolated project folders which maintain their own bubble to support whatever is done in that folder.
- set environment variables
- run prep scripts or start services
- load nix or guix profiles to provide support applications
Emacs' direnv module gives first class support to Emacs projects so they use the right tools for the project.
#+BEGIN_SRC emacs-lisp
;; enable direnv mode
(use-package direnv
:ensure t
:config
(direnv-mode))
#+END_SRC
This enables direnv globally.
* Writing
** Org Mode
*** Mixed Pitch Support by Default in Org
#+BEGIN_SRC emacs-lisp
(defun pti-org-mode-config ()
"Set options for better writing in org buffers."
(mixed-pitch-mode)
(visual-line-mode)
(turn-on-auto-fill)
)
(use-package org-bullets
:ensure t
:if (display-graphic-p)
:hook (org-mode . org-bullets-mode))
2024-08-04 01:37:17 +02:00
(use-package mixed-pitch
:ensure t
:if (display-graphic-p)
:hook
(org-mode . mixed-pitch-mode))
#+END_SRC
#+RESULTS:
: [nil 26279 36119 854408 nil elpaca-process-queues nil nil 376000 nil]
*** Org Configuration
#+BEGIN_SRC emacs-lisp
(use-package org
:ensure nil
:custom
(org-agenda-skip-scheduled-if-done t)
(org-agenda-skip-deadline-if-done t)
(org-todo-keywords '((sequence "TODO(t)" "NEXT(n)" "WAITING(w)" "SOMEDAY(s)" "PROJ(p)"
"|" "DONE(d)" "CANCELED(c)")
(sequence "[ ](T)" "[-](S)" "[?](W)"
"|" "[X](D)")
(sequence "|" "OKAY(o)" "YES(y)" "NO(n)")))
(org-todo-keywords-for-agenda '((sequence "NEXT(n)" "TODO(t)" "WAITING(w)" "SOMEDAY(s)" "PROJ(p)" "|" "DONE(d)" "CANCELED(c)")))
(line-spacing 0.1)
(left-margin-width 2)
(right-margin-width 2)
(org-hide-emphasis-markers t)
(org-agenda-files (list "~/Dropbox/org/" "~/org/snamellit/" "~/org/customer/" "~/org/personal/"))
(org-refile-targets '(
(org-agenda-files . (:level . 1))
("~/org/personal/bijen.org" . (:level . 1))
("~/org/personal/fitness.org" . (:level . 1))
))
2024-08-04 01:37:17 +02:00
:config
;; set files for agenda views
(setq +org-capture-todo-file "~/Dropbox/org/inbox.org"
+org-capture-notes-file "~/Dropbox/org/inbox.org"
+org-capture-journal-file "~/Dropbox/org/journal.org"
+org-capture-projects-file "~/Dropbox/org/projects.org")
:hook
(org-mode . pti-org-mode-config)
)
#+END_SRC
#+RESULTS:
*** Org Babel Support
I have a test file which has samples of babel features for easy
testing in my [[file:~/org/snamellit/testfile.org::*User Journey Graph][org babel test file.]]
**** Support REST calls in Babel Blocks
#+BEGIN_SRC emacs-lisp
;; enable verb package to do REST calls
(use-package verb
:ensure t
:defer 3
:bind
(:map org-mode-map
("C-c C-r" . verb-command-map)))
(use-package ob-verb
:after verb
:defer 3)
#+END_SRC
**** Support Mermaid Diagrams in Babel Blocks
#+BEGIN_SRC emacs-lisp
;; enable mermaid for org-babel
(use-package ob-mermaid
:ensure t
:defer 3)
#+END_SRC
Mermaid needs support of the mermaid-cli which is a node package. It
can be installed with
#+BEGIN_SRC shell :tangle no
npm install -g @mermaid-js/mermaid-cli
#+END_SRC
#+RESULTS:
| | | | | | |
| changed | 194 | packages | in | 6s | |
| | | | | | |
| 39 | packages | are | looking | for | funding |
| run | `npm | fund` | for | details | |
**** Support PlantUML Diagrams in Babel Blocks
Requires nothing special, other than *plantuml.jar* archive installed.
2024-08-05 10:44:37 +02:00
The following code block depends on [[*Get latest version of a github released project][Get latest version of a github
released project]] utility function. This block will check if there is a
plantuml.jar in the emacs config directory and if not download the
latest version from github. The `pti-download-latest-plantuml` is made
interactive to manually install the latest version if needed.
#+BEGIN_SRC emacs-lisp :lexical t
(defun pti-download-latest-plantuml ()
"Download the latest version of PlantUML.
This function is interactive to make it easy to upgrade to
the latest, current version with `M-x pti-download-latest-plantuml'."
(interactive)
(pti-with-latest-github-version
"plantuml/plantuml"
(lambda (version)
(let ((url (format
"https://github.com/plantuml/plantuml/releases/download/%s/plantuml-%s.jar"
version (substring version 1))))
(message "Downloading PlantUML version %s from %s" version url)
(url-copy-file
url
(concat user-emacs-directory "plantuml.jar")
))))
)
(let ((plantuml-jar (concat user-emacs-directory "plantuml.jar")))
(if (not (file-exists-p plantuml-jar))
(pti-download-latest-plantuml))
(setq org-plantuml-jar-path plantuml-jar))
#+END_SRC
#+RESULTS:
: /home/pti/.config/emacs/plantuml.jar
**** Configure Babel Languages
#+BEGIN_SRC emacs-lisp
;; configure babel languages
(use-package org-babel
:no-require
:after '(ob-verb ob-mermaid)
:config
(org-babel-do-load-languages
'org-babel-load-languages
'((emacs-lisp . t)
(shell . t)
(python . t)
(latex . t)
(verb . t)
(scheme . t)
(plantuml . t)
(mermaid . t)
(dot . t))))
#+END_SRC
**** Temporary Patches for Org Babel
***** Fix Scheme Babel Bug
#+BEGIN_SRC emacs-lisp
;; fix a bug in ob-scheme which causes an error to be thrown when evaluating
;; a cons cell or improper list
;; see https://list.orgmode.org/87bkk3x1bu.fsf@gajsin.name/T/
(defun org-babel-scheme--table-or-string (results)
"Convert RESULTS into an appropriate elisp value.
If the results look like a list or tuple, then convert them into an
Emacs-lisp table, otherwise return the results as a string."
(let ((res (org-babel-script-escape results)))
(cond ((proper-list-p res)
(mapcar (lambda (el)
(if (or (null el) (eq el 'null))
org-babel-scheme-null-to
el))
res))
(t res))))
#+END_SRC
#+RESULTS:
: org-babel-scheme--table-or-string
**** TODO Move babel test file to emacs config folder
Alternatively I might add a sample after each configured block to keep
it in the same context. Hmmm.... sounds even better.
*** Org Export
#+BEGIN_SRC emacs-lisp
(use-package ox
:ensure nil)
#+END_SRC
**** Org Export to Markdown
#+BEGIN_SRC emacs-lisp
(use-package ox-md
:ensure nil
:after ox
)
#+END_SRC
**** Org Latex Export
***** Syntax Highlighting requires Multiple Passes
#+BEGIN_SRC emacs-lisp
;; support for minted in LaTeX for code highlighting
(use-package ox-latex
:ensure nil
:after ox
:config
(setq org-latex-compiler "lualatex")
(setq org-latex-pdf-process
'("%latex -shell-escape -interaction nonstopmode -output-directory %o %f"
"%latex -interaction nonstopmode -output-directory %o %f"
"%latex -interaction nonstopmode -output-directory %o %f"))
)
#+END_SRC
***** Load Customer Latex Classes
#+BEGIN_SRC emacs-lisp
;; Customer LaTeX classes
(setq mlx-latex-classes
'(
("mlx-beamer" "\\documentclass{mlx-beamer}
[NO-DEFAULT-PACKAGES]
[NO-PACKAGES]"
("\\section{%s}" . "\\section*{%s}")
("\\subsection{%s}" . "\\subsection*{%s}")
("\\subsubsection{%s}" . "\\subsubsection*{%s}"))
("mlx-book" "\\documentclass{mlx-book}
[NO-DEFAULT-PACKAGES]
[NO-PACKAGES]"
("\\part{%s}" . "\\part*{%s}")
("\\chapter{%s}" . "\\chapter*{%s}")
("\\section{%s}" . "\\section*{%s}")
("\\subsection{%s}" . "\\subsection*{%s}")
("\\subsubsection{%s}" . "\\subsubsection*{%s}"))
("mlx-report" "\\documentclass{mlx-report}
[NO-DEFAULT-PACKAGES]
[NO-PACKAGES]"
("\\part{%s}" . "\\part*{%s}")
("\\chapter{%s}" . "\\chapter*{%s}")
("\\section{%s}" . "\\section*{%s}")
("\\subsection{%s}" . "\\subsection*{%s}")
("\\subsubsection{%s}" . "\\subsubsection*{%s}"))
("mlx-article" "\\documentclass{mlx-article}
[NO-DEFAULT-PACKAGES]
[NO-PACKAGES]"
("\\section{%s}" . "\\section*{%s}")
("\\subsection{%s}" . "\\subsection*{%s}")
("\\subsubsection{%s}" . "\\subsubsection*{%s}")
("\\paragraph{%s}" . "\\paragraph*{%s}")
("\\subparagraph{%s}" . "\\subparagraph*{%s}"))
("mlx-project-charter" "\\documentclass{mlx-project-charter}
[NO-DEFAULT-PACKAGES]
[NO-PACKAGES]"
("\\section{%s}
\\begin{mdframed}" "\\end{mdframed}")
("\\subsection{%s}" . "\\subsection*{%s}")
("\\subsubsection{%s}" . "\\subsubsection*{%s}")
("\\paragraph{%s}" . "\\paragraph*{%s}")
("\\subparagraph{%s}" . "\\subparagraph*{%s}"))))
#+END_SRC
***** Load My Latex Classes
#+BEGIN_SRC emacs-lisp
(setq snm-latex-classes
'(
("snm-beamer" "\\documentclass{snm-beamer}
[NO-DEFAULT-PACKAGES]
[NO-PACKAGES]"
("\\section{%s}" . "\\section*{%s}")
("\\subsection{%s}" . "\\subsection*{%s}")
("\\subsubsection{%s}" . "\\subsubsection*{%s}"))
("snm-book" "\\documentclass{snm-book}
[NO-DEFAULT-PACKAGES]
[NO-PACKAGES]"
("\\part{%s}" . "\\part*{%s}")
("\\chapter{%s}" . "\\chapter*{%s}")
("\\section{%s}" . "\\section*{%s}")
("\\subsection{%s}" . "\\subsection*{%s}")
("\\subsubsection{%s}" . "\\subsubsection*{%s}"))
("snm-report" "\\documentclass{snm-report}
[NO-DEFAULT-PACKAGES]
[NO-PACKAGES]"
("\\part{%s}" . "\\part*{%s}")
("\\chapter{%s}" . "\\chapter*{%s}")
("\\section{%s}" . "\\section*{%s}")
("\\subsection{%s}" . "\\subsection*{%s}")
("\\subsubsection{%s}" . "\\subsubsection*{%s}"))
("snm-invoice" "\\documentclass{snm-invoice}
[NO-DEFAULT-PACKAGES]
[NO-PACKAGES]"
("\\section{%s}" . "\\section*{%s}")
("\\subsection{%s}" . "\\subsection*{%s}")
("\\subsubsection{%s}" . "\\subsubsection*{%s}")
("\\paragraph{%s}" . "\\paragraph*{%s}")
("\\subparagraph{%s}" . "\\subparagraph*{%s}"))
("snm-article" "\\documentclass{snm-article}
[NO-DEFAULT-PACKAGES]
[NO-PACKAGES]"
("\\section{%s}" . "\\section*{%s}")
("\\subsection{%s}" . "\\subsection*{%s}")
("\\subsubsection{%s}" . "\\subsubsection*{%s}")
("\\paragraph{%s}" . "\\paragraph*{%s}")
("\\subparagraph{%s}" . "\\subparagraph*{%s}"))))
(with-eval-after-load 'ox-latex
(dolist
(class (append mlx-latex-classes snm-latex-classes))
(add-to-list 'org-latex-classes class)))
#+END_SRC
*** Org Blogging
#+BEGIN_SRC emacs-lisp
;; I started blogging again. To stimulate this let's add a template to get started
;; on a new post.
(defvar snamellit/blog-title "test-title")
(defun snamellit/capture-blog-post-file ()
"Create a new blog post file with a title and date."
(let* ((title (read-from-minibuffer "Post Title: "))
(slug (replace-regexp-in-string "[^a-z0-9]+" "-" (downcase title))))
(setq snamellit/blog-title title)
(format "~/src/website/content/blog/%s-%s.md"
(format-time-string "%Y-%m-%d" (current-time))
slug)))
#+END_SRC
*** Org Capture Customization
#+BEGIN_SRC emacs-lisp
(use-package org-capture
:ensure nil
:config (progn
(setq org-capture-templates '())
(add-to-list 'org-capture-templates
'("t" "Todo" entry (file+headline +org-capture-todo-file "Tasks")
"* TODO %?\n %i\n %a"))
(add-to-list 'org-capture-templates
'("n" "Note" entry (file+headline +org-capture-todo-file "Notes")
"* %?\n %i\n %a"))
(add-to-list 'org-capture-templates
'("j" "Journal" entry (file+datetree +org-capture-journal-file)
"* %?\nEntered on %U\n %i\n %a"))
(add-to-list 'org-capture-templates
'("b" "Blog Post" plain
(file snamellit/capture-blog-post-file)
"+++\ntitle = %(progn snamellit/blog-title)\ndate = %t\nauthor = Peter Tillemans\nemail = pti@snamellit.com\n[taxonomies]\ntags = []\ncategories = []\n+++\n\n%?"))
;; configure support for recipes
(add-to-list 'org-capture-templates
'("c" "Cookbook" entry (file "~/org/cookbook.org")
"%(org-chef-get-recipe-from-url)"
:empty-lines 1))
(add-to-list 'org-capture-templates
'("m" "Manual Cookbook" entry (file "~/org/cookbook.org")
"* %^{Recipe title: }\n :PROPERTIES:\n :source-url:\n :servings:\n :prep-time:\n :cook-time:\n :ready-in:\n :END:\n** Ingredients\n %?\n** Directions\n\n")))
:bind (("<leader>X" . org-capture)))
#+END_SRC
#+RESULTS:
: t
*** Evil Support for Org
Better keybinding for evil mode from [[https://github.com/Somelauw/evil-org-mode][the evil-org github repo]].
#+BEGIN_SRC emacs-lisp
(use-package evil-org
:ensure t
:after org
:hook
(org-mode . evil-org-mode)
:config
(require 'evil-org-agenda)
(evil-org-agenda-set-keys))
#+END_SRC
Here is a snapshot of the keybindings dd <2024-07-30 Tue>.
**** Quick overview
|----------------+---------------------------|
| key | explanation |
|----------------+---------------------------|
| gh, gj, gk, gl | navigate between elements |
| vae | select an element |
|----------------+---------------------------|
**** Headings and items
|--------------+------------------------|
| key | explanation |
|--------------+------------------------|
| M-ret | insert heading |
| <tab>, g TAB | fold / unfold headings |
| M-h or << | promote a heading |
| M-l or >> | demote a heading |
| M-k | move subtree up |
| M-j | move subtree down |
| M-S-h or <aR | promote a subtree |
| M-S-l or >aR | demote a subtree |
| vaR | select a subtree |
|--------------+------------------------|
**** Tables
|-----------+--------------------------------|
| key | explanation |
|-----------+--------------------------------|
| ( | previous table cell |
| ) | next table cell |
| { | beginning of table |
| } | end of table |
| M-h / M-l | move table column left / right |
| M-k / M-j | move table column up / down |
| vae | select table cell |
| vaE | select table row |
| var | select whole table |
|-----------+--------------------------------|
**** Agenda
|-------------------------+-------------------------+-----------------------------------------------------------------------------------|
| Evil key | Emacs key | explanation |
|-------------------------+-------------------------+-----------------------------------------------------------------------------------|
| gH | | Move cursor to the top of window |
| gM | | Move cursor to the middle of window |
| gL | | Move cursor to the bottom of window |
| <tab>, S-<return> | <tab> | go to the corresponding entry at point |
| g TAB | <tab> | go to the corresponding entry at point |
| <return> | <return> | go to the Org mode file which contains the item at point |
| M-<return> | L | Display Org file and center around the item |
| <space> | <space> | scroll up |
| <delete> or <backspace> | <delete> or <backspace> | scroll down |
| j, k | n, p | next, previous line |
| gj, gk, C-j, C-k | N, P | next, previous item |
| [, ] | b, f | previous, next week |
| J, K | -, +, S-down, S-up | down, up priority |
| H, L | S-left, S-right | modify date to earlier, later |
| t | t | cycle TODO keywords |
| M-j, M-k | M-down, M-up | drag line forward, backward |
| C-S-h, C-S-l | C-S-left, C-S-right | previous, next keyword |
| u | C-_, C-/ | undo |
| dd | C-k | delete item |
| da | a | ask and archive item |
| dA | $ | archive item |
| ct | : | set tags |
| ce | e | set effort |
| cT | ; | set timer |
| i | i | insert entry in diary |
| a | z | add note |
| A | A | append to agenda |
| C | k | capture |
| m | m | mark |
| * | * | toggle all marks |
| % | % | mark regexp |
| M | U | remove all marks |
| x | B | execute action on marks |
| gr | r | refresh agenda |
| gR | g | refresh all agendas |
| ZQ | x | exit agenda |
| ZZ | Q | quit agenda |
| gD | v | tweak display (deadlines, diary, follow/log-mode, entry text, grid, day/week/year |
| ZD | # | dim blocked tasks |
| sc, sr, se, st, s^ | <, =, _, /, ^ | filter by category, regexp, effort, tag, top headline |
| S | \vert | remove all filters |
| ss | ~ | filter/limit interactively |
| I | I | clock in |
| O | O | clock out |
| cg | J | jump to the currently clocked in task within the agenda |
| cc | X | cancel the current running clock |
| cr | R | toggle clocktable mode in an agenda buffer |
| . | . | go to today's date |
| gc | c | pop up calendar |
| gC | C | pop up date converter |
| p | > | pop up date selector |
| gh | H | pop up holiday calendar |
| gm | M | pop up phases of the moon |
| gs | S | pop up sunrise/sunset times |
| gt | T | pop up tag list |
| +, - | [, ] | manipulate the query by adding a search term with positive or negative selection |
|-------------------------+-------------------------+-----------------------------------------------------------------------------------|
*** Org GCal Support
#+BEGIN_SRC emacs-lisp
;; configure support for google calendar
(use-package org-gcal
:ensure t
2024-08-02 11:39:46 +02:00
:custom
(org-gcal-client-id (auth-source-pass-get 'secret "snamellit/org-gcal-client"))
(org-gcal-client-secret (auth-source-pass-get "id" "snamellit/org-gcal-client"))
(org-gcal-fetch-file-alist '(("pti@snamellit.com" . "~/org/schedule.org")))
:commands (org-gcal-sync org-gcal-fetch) )
#+END_SRC
2024-08-02 11:39:46 +02:00
#+RESULTS:
*** Org Jira Integration
#+BEGIN_SRC emacs-lisp
;; configure org-jira
(use-package org-jira
2024-08-07 09:51:49 +02:00
:ensure (:host github :repo "ptillemans/org-jira" :branch "pti_fix_getUsers")
2024-08-02 14:49:05 +02:00
:commands (org-jira-get-issues org-jira-get-projects)
2024-08-04 01:37:17 +02:00
:config
2024-08-07 09:51:49 +02:00
(setq org-jira-users '(("Peter Tillemans". "557058:bdf83521-663b-4ae6-9b71-487bb98e2add")))
(setq jiralib-agile-page-size 1000)
(setq org-jira-custom-jqls
'(
(:jql " assignee = currentUser() and (created > '2024-01-01' of updated > '2024-01-01) order by created DESC"
:limit 100 :filename "this-year")
))
(make-directory "~/.org-jira" 'parents)
(setq jiralib-url (auth-source-pass-get "url" "customer/jira"))
(jiralib-login
(auth-source-pass-get "user" "customer/jira")
(auth-source-pass-get 'secret "customer/jira")))
#+END_SRC
2024-08-02 14:49:05 +02:00
#+RESULTS:
: [nil 26283 18515 471588 nil elpaca-process-queues nil nil 496000 nil]
** Denote
#+BEGIN_SRC emacs-lisp
;; configure denote
(use-package denote
:ensure t
:config (progn
(setq denote-directory (file-name-concat (expand-file-name "~") "Dropbox/denote"))
(setq denote-dired-directories
(list denote-directory
(expand-file-name "~/Documents/denote"))))
:hook (dired-mode . denote-dired-mode-in-directories)
:bind (
("<leader> n d" . (lambda () (interactive) (dired denote-directory)))
("<leader> n n" . #'denote)
("<leader> n N" . #'denote-type)
("<leader> n c" . #'denote-link-or-create)
("<leader> n t" . #'denote-template)
("<leader> n z" . #'denote-signature)
("<leader> n l" . #'denote-link)
("<leader> n L" . #'denote-find-link)
("<leader> n k" . #'denote-keywords-add)
("<leader> n b" . #'denote-link-backlink)
("<leader> n B" . #'denote-find-backlink)
("<leader> n r" . #'denote-rename-file)
("<leader> n R" . #'denote-rename-file-using-front-matter)
("<leader> n f" . (lambda () (interactive) (consult-find denote-directory)))))
#+END_SRC
**** TODO explain what denote-dired-mode-in-directories does.
* Programming
** Programming Support Infrastructure
*** Integration with LSP Servers for language support
#+BEGIN_SRC emacs-lisp
;; configure eglot-mode
(use-package eglot
:config
(evil-define-key 'normal eglot-mode-map
(kbd "<leader>c a") 'eglot-code-actions
(kbd "<leader>c d") 'eglot-find-declaration
(kbd "<leader>c i") 'eglot-find-implementation
(kbd "<leader>c k") 'eglot-find-typeDefinition
(kbd "<leader>c f") 'eglot-format
(kbd "<leader>c F") 'eglot-format-buffer
(kbd "<leader>c r") 'eglot-rename
(kbd "<leader>c Q") 'eglot-shutdown
(kbd "<leader>c q") 'eglot-reconnect
(kbd "<leader>c n") 'flymake-goto-next-error
(kbd "<leader>c p") 'flymake-goto-prev-error)
;; Shutdown server when last managed buffer is killed
(setq eglot-autoshutdown t)
;; from https://www.reddit.com/r/emacs/comments/ye18nd/setting_up_eglot_for_python/
(add-to-list 'eglot-server-programs '(python-mode . ("pylsp")))
(setq-default eglot-workspace-configuration
'((:pylsp . (:configurationSources ["flake8"]
:plugins (:pycodestyle (:enabled nil)
:mccabe (:enabled nil)
:flake8 (:enabled t))))))
:hook
(go-ts-mode . eglot-ensure)
(rust-ts-mode . eglot-ensure)
(java-ts-mode . eglot-ensure)
(python-ts-mode . eglot-ensure)
)
#+END_SRC
*** Use Treesitter parser support
#+BEGIN_SRC emacs-lisp
;; set locations for treesitter grammars
(use-package treesit
:config
(setq treesit-language-source-alist
'((bash "https://github.com/tree-sitter/tree-sitter-bash")
(cmake "https://github.com/uyha/tree-sitter-cmake")
(css "https://github.com/tree-sitter/tree-sitter-css")
(elisp "https://github.com/Wilfred/tree-sitter-elisp")
(go "https://github.com/tree-sitter/tree-sitter-go")
(gomod "https://github.com/camdencheek/tree-sitter-go-mod.git")
(haskell "https://github.com/tree-sitter/tree-sitter-haskell" "master" "src" nil nil)
(html "https://github.com/tree-sitter/tree-sitter-html")
(java "https://github.com/tree-sitter/tree-sitter-java.git")
(javascript "https://github.com/tree-sitter/tree-sitter-javascript" "master" "src")
(json "https://github.com/tree-sitter/tree-sitter-json")
(lua "https://github.com/tjdevries/tree-sitter-lua")
(make "https://github.com/alemuller/tree-sitter-make")
(markdown "https://github.com/ikatyang/tree-sitter-markdown")
(ocaml "https://github.com/tree-sitter/tree-sitter-ocaml" nil "ocaml/src")
(python "https://github.com/tree-sitter/tree-sitter-python")
(toml "https://github.com/tree-sitter/tree-sitter-toml")
(tsx "https://github.com/tree-sitter/tree-sitter-typescript" "master" "tsx/src")
(typescript "https://github.com/tree-sitter/tree-sitter-typescript" "master" "typescript/src")
(yaml "https://github.com/ikatyang/tree-sitter-yaml")))
:hook
((prog . treesit-inspect-mode)))
2024-08-04 01:37:17 +02:00
(use-package treesit-auto
:ensure t
:defer 5
:custom
(treesit-auto-install 'prompt)
(treesit-auto-langs '(awk bash c c-sharp clojure cmake commonlisp cpp css
dart dockerfile elixir go gomod html java
javascript json julia kotlin lua make markdown nix nu
org perl proto python r ruby rust scala sql toml tsx
typescript vue yaml)) ; reduced langs list somewhat
:config
(treesit-auto-add-to-auto-mode-alist 'all)
(global-treesit-auto-mode))
#+END_SRC
#+RESULTS:
: [nil 26284 45426 709595 nil elpaca-process-queues nil nil 734000 nil]
I always get errors compiling support for *janet* so I pruned the
`treesit-auto-langs` to exclude it and other things I don't use.
***** TODO figure out why Latex does not want to install
***** TODO decide whether to keep using treesitter-auto at all or just plain treesit with some support functions.
**** Recompiling all Treesitter Grammars
To recompile all treesitter grammars, execute following block with
*C-c C-c*.
2024-08-04 01:37:17 +02:00
#+BEGIN_SRC emacs-lisp :tangle no
(mapc #'treesit-install-language-grammar (mapcar #'car treesit-language-source-alist))
#+END_SRC
#+RESULTS:
| bash | cmake | css | elisp | go | gomod | haskell | html | java | javascript | json | lua | make | markdown | ocaml | python | toml | tsx | typescript | yaml |
**** Treesitter Grammars for Windows
2024-08-04 01:37:17 +02:00
Windows does not come with a handy-dandy C-compiler available as *cc* or *gcc* or even *c99* and treesitter does not have a handy dandy feature to remap that to *zig cc* and similar.
However we can download it from the release page of the tree-sitter-langs repo or even better install the *tree-sitter-langs* package.
2024-08-04 01:37:17 +02:00
This will download the dynamic library to _user-emacs-directory_/elpa/tree-sitter-langs/bin
2024-08-04 01:37:17 +02:00
However they'll have the wrong filename and are not in the path where treesitter looks in.
2024-08-04 01:37:17 +02:00
- Open the folder with *dired*
- switch to writable with `C-xC-q` to enable *wdired*
- :%s/[a-z-]\*.dll/libtree-sitter-\\0/
- `C-cC-c` to confirm the changes if it looks ok
- `%m` to mark all files starting with libtree
- `R` them to the *~/.config/emacs/tree-sitter* folder
and they will now be installed.
2024-08-04 01:37:17 +02:00
Normally that should work for other OSes too, but there is no real need for it.
see also [[https://www.masteringemacs.org/article/how-to-get-started-tree-sitter][How to get started with tree sitter]] .
*** Create a folder to store downloaded LSP servers
#+BEGIN_SRC emacs-lisp
;; LSP support
(let ((lsp_dir (file-name-concat user-emacs-directory "lsp")))
(if (not (file-exists-p lsp_dir))
(mkdir lsp_dir t)))
#+END_SRC
** Configure Selected Languages
*** Rust Support
#+BEGIN_SRC emacs-lisp
;; configure rust support
(let* ((release-date "2023-10-30")
(os (pcase system-type
('darwin "x86_64-apple-darwin")
('gnu/linux "x86_64-unknown-linux-gnu")
('windows-nt "x86_64-pc-windows-msvc")
(_ "unknown")))
(releases-url "https://github.com/rust-lang/rust-analyzer/releases/download/")
(download-url (concat releases-url release-date "/rust-analyzer-" os ".gz"))
(rust-analyzer
(file-name-concat
user-emacs-directory
(concat "lsp/rust-analyzer" (if (eq system-type 'windows-nt) ".exe" "")))))
(if (not (file-exists-p rust-analyzer))
(let ((rust-analyzer-archive (concat rust-analyzer ".gz" )))
(message "install rust-analyzer from %s at %s" download-url rust-analyzer)
(url-copy-file download-url rust-analyzer-archive t)
(call-process "gzip" nil "*snam-install*" t "-d" (concat rust-analyzer ".gz"))
(call-process "chmod" nil "*snam-install*" t "+x" rust-analyzer)
(message "rust-analyzer installed at %s" rust-analyzer)))
(with-eval-after-load 'eglot
(add-to-list 'eglot-server-programs `((rust-mode) rust-analyzer))))
(use-package rustic
:ensure t
:init
(setq rustic-lsp-client 'eglot))
#+END_SRC
*** OCaml Support
#+BEGIN_SRC emacs-lisp
;; configure Ocaml support
(setq opam-emacs-dir (file-name-concat (expand-file-name "~") "/.opam/default/share/emacs/site-lisp"))
(use-package ocp-indent
:ensure t
:load-path opam-emacs-dir
:if (file-exists-p opam-emacs-dir))
(use-package tuareg
:ensure t
:hook
(tuareg-mode . eglot-ensure))
#+END_SRC
*** Go Support
#+BEGIN_SRC emacs-lisp :tangle no
;; configure go support
(use-package go-ts-mode
:init
(add-to-list 'auto-mode-alist '("\\.go\\'" . go-ts-mode))
:hook
2024-08-02 14:49:05 +02:00
(go-ts . eglot-ensure))
#+END_SRC
*** Javascript, Typescript, TSC, etc...
#+BEGIN_SRC emacs-lisp :tangle no
;; configure typescript support
(use-package typescript-ts-mode
:init
(add-to-list 'auto-mode-alist '("\\.ts\\'" . typescript-ts-mode))
:hook
(typescript-ts-mode . eglot-ensure))
;; configure javascript support
(use-package javascript-ts-mode
:init
(add-to-list 'auto-mode-alist '("\\.js\\'" . js-ts-mode))
:hook
(javascript-ts-mode . eglot-ensure))
;; configure react support
(use-package tsx-ts-mode
:init
(add-to-list 'auto-mode-alist '("\\.[jt]sx\\'" . tsx-ts-mode))
:hook
(tsx-ts-mode . eglot-ensure))
#+END_SRC
*** Java and other JVM languages
#+BEGIN_SRC emacs-lisp
;; configure java support
(use-package java-ts-mode
:hook
(javascript-ts-mode . eglot-ensure)
:config
(let* ((download-url"https://www.eclipse.org/downloads/download.php?file=/jdtls/snapshots/jdt-language-server-latest.tar.gz")
(jdtls-dir (file-name-concat user-emacs-directory "lsp/jdtls"))
(jdtls-prog (concat jdtls-dir "/bin/jdtls" (if (eq system-type 'windows-nt) ".bat" "")))
(archive (file-name-concat jdtls-dir "jdtls.tgz")))
(if (not (file-exists-p jdtls-dir))
(mkdir jdtls-dir t))
(if (not (file-exists-p jdtls-prog))
(progn
(message "install jdtls at %s" jdtls-dir)
(if (not (file-exists-p archive))
(url-copy-file download-url archive))
(call-process "tar" nil "*snam-install*" t "-C" jdtls-dir "-xzvf" archive )
(message "jdtls installed at %s" jdtls-prog)))
(with-eval-after-load 'eglot
(progn
(setenv "JAVA_HOME" (getenv "GUIX_PROFILE"))
(add-to-list 'eglot-server-programs `((java-mode java-ts-mode) ,jdtls-prog))))))
#+END_SRC
*** Lisp and Scheme Support
**** Enable ParEdit in lispy modes
#+BEGIN_SRC emacs-lisp
;; Lisp support
(use-package paredit
:ensure nil
:commands (enable-paredit-mode)
:init
(dolist (mode '(emacs-lisp-mode-hook
lisp-interaction-mode-hook
lisp-mode-hook
scheme-mode-hook))
(add-hook mode #'enable-paredit-mode)))
(use-package evil-paredit
:ensure t
:commands (evil-paredit-mode)
:init
(dolist (mode '(emacs-lisp-mode-hook
lisp-interaction-mode-hook
lisp-mode-hook
scheme-mode-hook))
(add-hook mode #'evil-paredit-mode)))
#+END_SRC
#+RESULTS:
***** TODO FIx paredit bug related to obsolete macro
evil-paredit relies on an obsolete (and no longer available method)
`evil-called-interactively-p`. So I define it here till evil-paredit
has implemented the new method.
#+BEGIN_SRC emacs-lisp
(defmacro evil-called-interactively-p ()
"Wrapper for `called-interactively-p'.
In older versions of Emacs, `called-interactively-p' takes
no arguments. In Emacs 23.2 and newer, it takes one argument."
(called-interactively-p 'any))
(make-obsolete 'evil-called-interactively-p
"please use (called-interactively-p 'any) instead."
"Git commit 222b791")
#+END_SRC
**** Enable Geiser Mode in Scheme Mode
Configure Geiser and Scheme
- map .scm file by default to Guile
#+BEGIN_SRC emacs-lisp
(use-package geiser
:ensure nil
:defer t
:commands (geiser-mode)
:config
(setq geiser-implementation-alist
,(add-to-list 'geiser-implementation-alist '((regexp "\\.scm\\'") guile))))
(use-package scheme-mode
:ensure nil
:defer t
:commands (scheme-mode)
:hook (scheme-mode . geiser-mode))
#+END_SRC
#+RESULTS:
| evil-paredit-mode | geiser-mode | enable-paredit-mode | aggressive-indent-mode | geiser-mode--maybe-activate |
*** Terraform Support
#+BEGIN_SRC emacs-lisp
;; configure terraform support
(use-package terraform-mode
:ensure t
:config
(setq
terraform-indent-level 2
terraform-format-on-save t)
(evil-define-key 'normal terraform-mode-map
(kbd "<leader>c k") #'terraform-open-doc
(kbd "<leader>c f") #'terraform-format
(kbd "<leader>c F") #'terraform-format-buffer
(kbd "<leader>c n") 'flymake-goto-next-error
(kbd "<leader>c p") 'flymake-goto-prev-error)
:hook
(terraform-mode . (lambda () (outline-minor-mode 1)))
:bind)
#+END_SRC
#+RESULTS:
: pti-ide-config
** Debugger Support
#+BEGIN_SRC emacs-lisp
;; install DAP servers
(setq snam-vscode-js-debug-dir (file-name-concat user-emacs-directory "dape/vscode-js-debug"))
(defun snam-install-vscode-js-debug ()
"Run installation procedure to install JS debugging support."
(interactive)
(mkdir snam-vscode-js-debug-dir t)
(let ((default-directory (expand-file-name snam-vscode-js-debug-dir)))
(vc-git-clone "https://github.com/microsoft/vscode-js-debug.git" "." nil)
(message "git repository created")
(call-process "npm" nil "*snam-install*" t "install")
(message "npm dependencies installed")
(call-process "npx" nil "*snam-install*" t "gulp" "dapDebugServer")
(message "vscode-js-debug installed")))
(setq snam-codelldb-dir (file-name-concat user-emacs-directory "dape/codelldb"))
(defun snam-install-codelldb ()
"Install Vadimcn.Vscode-Lldb DAP server for C/C++/RUST."
(interactive)
(let* ((default-directory snam-codelldb-dir)
(arch (car (split-string system-configuration "-" nil nil)))
(os (pcase system-type
('windows-nt "windows")
('gnu/linux "linux")
('darwin "darwin")
(_ "unknown")))
(version "1.10.0")
(release-url (concat "https://github.com/vadimcn/codelldb/releases/download/v" version "/codelldb-" arch "-" os ".vsix")))
(mkdir default-directory t)
(url-copy-file release-url "codelldb.zip" t)
(message "codelldb archive downloaded")
(call-process "unzip" nil "*snam-install*" t "codelldb.zip")
(message "codelldb installed")
))
;; configure dape (dap-mode)
(use-package dape
:ensure (dape :host github :repo "svaante/dape" :wait t)
:config (progn
;; Use n for next etc. in REPL
;; (setq dape-repl-use-shorthand t)
;; By default dape uses gdb keybinding prefix
;; (setq dape-key-prefix "<space>d")
(evil-define-key 'normal 'global (kbd "<leader>d") dape-global-map)
;; Kill compile buffer on build success
;; (add-hook 'dape-compile-compile-hooks 'kill-buffer)
;; Projectile users
;; (setq dape-cwd-fn 'projectile-project-root))
(add-to-list 'dape-configs
`(vscode-js-node
modes (js-mode js-ts-mode typescript-mode typescript-ts-mode)
host "localhost"
port 8123
command "node"
command-cwd ,(file-name-concat snam-vscode-js-debug-dir "dist")
command-args ("src/dapDebugServer.js" "8123")
:type "pwa-node"
:request "launch"
:cwd dape-cwd-fn
:program dape-find-file-buffer-default
:outputCapture "console"
:sourceMapRenames t
:pauseForSourceMap nil
:enableContentValidation t
:autoAttachChildProcesses t
:console "internalConsole"
:killBehavior "forceful"))
(add-to-list 'dape-configs
`(delve
modes (go-mode go-ts-mode)
command "dlv"
command-args ("dap" "--listen" "127.0.0.1:55878")
command-cwd dape-cwd-fn
host "127.0.0.1"
port 55878
:type "debug" ;; needed to set the adapterID correctly as a string type
:request "launch"
:cwd dape-cwd-fn
:program dape-cwd-fn))
(add-to-list 'dape-configs
`(codelldb
modes (c-mode c-ts-mode
c++-mode c++-ts-mode
rust-ts-mode rust-mode)
;; Replace vadimcn.vscode-lldb with the vsix directory you just extracted
command ,(expand-file-name
(file-name-concat
snam-codelldb-dir
(concat "extension/adapter/codelldb"
(if (eq system-type 'windows-nt)
".exe"
""))))
host "localhost"
port 5818
command-args ("--port" "5818")
:type "lldb"
:request "launch"
:cwd dape-cwd-fn
:program dape-find-file))
(add-to-list 'dape-configs
`(debugpy
modes (python-ts-mode python-mode)
command "python"
command-args ("-m" "debugpy.adapter")
:type "executable"
:request "launch"
:cwd dape-cwd-fn
:program dape-find-file-buffer-default))
))
#+END_SRC
#+RESULTS:
** Copilot Support
#+BEGIN_SRC emacs-lisp
2024-07-30 16:45:22 +02:00
(use-package copilot
:ensure (:host github :repo "zerolfx/copilot.el"
:branch "main"
:files ("dist" "*.el"))
:bind
(:map evil-insert-state-map
("C-y" . copilot-accept-completion))
:config
(add-to-list 'copilot-indentation-alist '(scheme-mode . 2))
(add-to-list 'copilot-indentation-alist '(emacs-lisp-mode . 2))
(add-to-list 'copilot-indentation-alist '(lisp-mode . 2))
:hook
(prog-mode . copilot-mode)
(org-mode . copilot-mode))
#+END_SRC
#+RESULTS:
2024-07-30 16:45:22 +02:00
: [nil 26280 62042 380168 nil elpaca-process-queues nil nil 930000 nil]
*** TODO move scheme configuration to the scheme section
or leave it here but move it to config, whatever makes most sense at
the moment.
** Flymake Error Diagnostics
#+BEGIN_SRC emacs-lisp
(evil-define-key 'normal #'eglot-mode-map
(kbd "]d") #'flymake-goto-next-error
(kbd "[d") #'flymake-goto-prev-error
)
#+END_SRC
** Electric Return
#+BEGIN_SRC emacs-lisp
;; enable electric return to automatically indent code
(defvar electrify-return-match
"[\]}\)\"]"
"The text after the cursor to do an \"electric\" return.")
(defun electrify-return-if-match (arg)
"Insert a newline, and indent it when needed.
If the text after the cursor matches `electrify-return-match' then
open and indent an empty line between the cursor and the text. Move the
cursor to the new line.
With a prefix argument ARG, insert that many newlines"
(interactive "P")
(let ((case-fold-search nil))
(if (looking-at electrify-return-match)
(save-excursion (newline-and-indent)))
(newline arg)
(indent-according-to-mode)))
;; Using local-set-key in a mode-hook is a better idea.
;(global-set-key (kbd "RET") 'electrify-return-if-match)
#+END_SRC
*** TODO Evaluate if Electric Return is still useful
* Communication and Interwebs
** ERC configuration
Set up ERC, an IRC client for Emacs, to automatically join specific channels and log in:
#+BEGIN_SRC emacs-lisp
(use-package erc
:ensure t
:defer 5
:config
(erc-services-mode 1)
(erc-autojoin-mode 1)
(erc-update-modules)
(setq erc-autojoin-channels-alist
'(('znc
"#emacs" "#erc" "#spritely" "#guix"
"#systemcrafters" "#systemcrafters-guix" "#systemcrafters-emacs"))))
#+END_SRC
#+RESULTS:
: t
Setup all channels which are joined by default.
*** ERC connect function
Define a function to login to ERC when needed. In principle ERC
reconnects after suspend, and sometimes it even works, but mostly I
run this function again to reconnect, which will update the buffers
with the rooms I joined.
#+BEGIN_SRC emacs-lisp
(defun snam-erc ()
"Login to znc bouncer and join rooms."
(interactive)
(erc-tls :id 'znc
:server (auth-source-pass-get "server" "snamellit/znc")
:port (auth-source-pass-get "port" "snamellit/znc")
:user (auth-source-pass-get "user" "snamellit/znc")
:nick (auth-source-pass-get "nick" "snamellit/znc")
:password (auth-source-pass-get 'secret "snamellit/znc")))
#+END_SRC
#+RESULTS:
: snam-erc
In practice this has proven to be a very useful function to reconnect
in all kind of weird situation.
**** TODO Figure out how to make ERC reconnect more reliably
Although running `snam-erc` is not a big deal, it should not be needed
so much. I should figure out in what cases reconnects fail or dropping
connections fail to be recognized.
I often see that ERC is _reconnecting_ however this seems to silently
fail. I dunno, there is something fishy here...
*** Integrate ERC with i3 Desktops
I like to have my IRC channels on workspace 5 in *i3*.
#+BEGIN_SRC emacs-lisp
(defun my-erc ()
"Create new frame and move to ws5 and launch erc."
(interactive)
(let ((frame (make-frame))
(channel "#systemcrafters"))
(call-process "i3" nil nil nil "move window to workspace 5")
(snam-erc)
(call-process "i3" nil nil nil "workspace 5")
(message "Waiting to connect to IRC...")
(dotimes (i 12)
(unless (member channel (mapcar 'buffer-name (erc-channel-list nil)))
(sleep-for 0.25)))
(set-window-buffer
(frame-selected-window frame) channel)))
#+END_SRC
#+RESULTS:
: my-erc
** Elfeed configuration
Configures Elfeed, an RSS feed reader for Emacs:
#+BEGIN_SRC emacs-lisp
;; configure elfeed
(use-package elfeed
:ensure t
:defer 5
:config
(setq elfeed-feeds
'("https://www.spritelyproject.org/feed.xml"
2024-08-05 22:59:21 +02:00
;; "https://www.emacswiki.org/emacs?action=rss"
;; "https://www.reddit.com/r/emacs.rss"
;; "https://www.reddit.com/r/guix.rss"
;; "https://www.reddit.com/r/linux.rss"
"https://sachachua.com/blog/feed/"
"https://blog.benoitj.ca/posts/index.xml"
"https://tylerdback.com/index.xml"
"https://www.snamellit.com/rss.xml"
"https://richarddavis.xyz/en/blog/rss.xml"
"https://protesilaos.com/master.xml"
)))
#+END_SRC
#+RESULTS:
: [nil 26279 35504 577569 nil elpaca-process-queues nil nil 161000 nil]
** Enable 0x0 to share snippets and files easily
Configures the ~0x0~ package for easily sharing code snippets and files:
#+BEGIN_SRC emacs-lisp
;; configure 0x0
(use-package 0x0
:ensure t
:config (setq 0x0-use-curl t))
#+END_SRC
#+RESULTS:
: [nil 26279 35497 183387 nil elpaca-process-queues nil nil 963000 nil]
The commands this package offers:
- ~0x0-dwim~ chooses the right one of the following, based on location or previous commands.
- ~0x0-upload-text~ will upload the active region or if not existent the entire buffer
- ~0x0-upload-file~ will upload a selected file
- ~0x0-upload-kill-ring~ uploads the content of the kill ring
- ~0x0-popup~ creates and displays a buffer, lets you upload its contents later
- ~0x0-shorten-uri~ prompts for a uri, shortens it
IMSMR the shorten-uri functionality is disabled in the 0x0 service
as apparently is was abused too much.
* Finishing Touches
** Tell the module system that *init.el* is available
Indicates the ~init.el~ file is provided by and ends here:
#+BEGIN_SRC emacs-lisp
(provide 'init)
;;; init.el ends here
#+END_SRC
2024-07-30 20:48:27 +02:00
* Future
** TODO Decide about general keybindings.
The *general* package offers enhanced support for *evil* keybindings,
notably it integrates with *use-package* to define keybindings which
will load the package if not loaded yet.
It considerably streamlines managing evil bindings so it should offer
enough value but I just removed it, so it'll have to wait a bit.
** TODO Add support for zola blogposts writing in org-mode
see [[https://github.com/gicrisf/ox-zola][ox-zola]] for exporting org-mode to zola markdown.
2024-08-04 01:37:17 +02:00
* Final Words
This is the end of the configuration file.
2024-08-04 01:37:17 +02:00
Add a variable so the file is tangling itself each time it is saved.
2024-08-04 01:37:17 +02:00
# Local Variables:
# eval: (add-hook 'after-save-hook #'org-babel-tangle t t)
# End: