#+TITLE: Emacs configuration file
#+AUTHOR: Marc
#+BABEL: :cache yes
#+PROPERTY: header-args :tangle init.el
#+OPTIONS: ^:nil
* TODOS
- early-init.el? What to outsource here?
- Paket exec-path-from-shell, um PATH aus Linux auch in emacs zu haben
- Smart mode line?
- Theme
- flymake instead of flycheck?
- Hydra
- General
- (defalias 'list-buffers 'ibuffer)  ;; change default to ibuffer
- ido?
- treemacs (for linux)
 windmove?
- tramp (in linux)
- visual-regexp
- org configuration: paths
- org custom agenda
- org-ql (related to org agendas)
- org configuration: everything else
- beancount configuration from config.org
- CONTINUE TODO from config.org at Programming
- all-the-icons?
- lispy? [[https://github.com/abo-abo/lispy]]

* Header
Emacs variables are dynamically scoped. That's unusual for most languages, so disable it here, too
#+begin_src emacs-lisp
;;; init.el --- -*- lexical-binding: t -*-
#+end_src

* First start
These functions updates config.el whenever changes in config.org are made. The update will be active after saving.

#+BEGIN_SRC emacs-lisp
(defun my/tangle-config ()
  "Export code blocks from the literate config file."
  (interactive)
  ;; prevent emacs from killing until tangle-process has finished
  (add-to-list 'kill-emacs-query-functions
               (lambda ()
                 (or (not (process-live-p (get-process "tangle-process")))
                     (y-or-n-p "\"my/tangle-config\" is running; kill it? "))))
  (org-babel-tangle-file config-org init-el)
  (message "reloading user-init-file")
  (load-file init-el))

(add-hook 'org-mode-hook
          (lambda ()
            (if (equal (buffer-file-name) config-org)
                (my--add-local-hook 'after-save-hook 'my/tangle-config))))

(defun my--add-local-hook (hook function)
  "Add buffer-local hook."
  (add-hook hook function :local t))
#+END_SRC

A small function to measure start up time.
Compare that to
emacs -q --eval='(message "%s" (emacs-init-time))'
(roughly 0.27s)
https://blog.d46.us/advanced-emacs-startup/
#+begin_src emacs-lisp
(add-hook 'emacs-startup-hook
          (lambda ()
            (message "Emacs ready in %s with %d garbage collections."
                     (format "%.2f seconds"
                             (float-time
                              (time-subtract after-init-time before-init-time)))
                     gcs-done)))

;(setq gc-cons-threshold (* 50 1000 1000))
#+end_src
* Default settings
** paths
#+BEGIN_SRC emacs-lisp
(defconst *sys/gui*
  (display-graphic-p)
  "Is emacs running in a gui?")

(defconst *sys/linux*
  (string-equal system-type 'gnu/linux)
  "Is the system running Linux?")

(defconst *sys/windows*
  (string-equal system-type 'windows-nt)
  "Is the system running Windows?")

(defconst *home_desktop*
  (string-equal (system-name) "marc")
  "Is emacs running on my desktop?")

(defconst *home_laptop*
  (string-equal (system-name) "laptop")
  "Is emacs running on my laptop?")

(defconst *work_local*
  (string-equal (system-name) "PMPCNEU08")
  "Is emacs running at work on the local system?")

(defconst *work_remote*
  (or (string-equal (system-name) "PMTS01")
      (string-equal (system-name) "PMTSNEU01"))
  "Is emacs running at work on the remote system?")
#+END_SRC

#+BEGIN_SRC emacs-lisp
(defvar MY--PATH_USER_LOCAL  (concat user-emacs-directory "user-local/"))
(defvar MY--PATH_USER_GLOBAL (concat user-emacs-directory "user-global/"))

(add-to-list 'custom-theme-load-path (concat MY--PATH_USER_GLOBAL "themes"))

(when *sys/linux*
  (defconst MY--PATH_ORG_FILES        (expand-file-name "~/archiv/notes/"))
  (defconst MY--PATH_ORG_FILES_MOBILE (expand-file-name "~/archiv/notes/mobile/"))
  (defconst MY--PATH_ORG_JOURNAl      (expand-file-name "~/archiv/notes/Journal/")))
;  (defconst MY--PATH_ORG_ROAM         (file-truename "~/archiv/Organisieren/notes/")))
(when *work_remote*
  (defconst MY--PATH_ORG_FILES        "p:/Eigene Dateien/Notizen/")
  (defconst MY--PATH_ORG_FILES_MOBILE nil)  ;; hacky way to prevent "free variable" compiler error
  (defconst MY--PATH_ORG_JOURNAL      nil)  ;; hacky way to prevent "free variable" compiler error
  (defconst MY--PATH_START            "p:/Eigene Dateien/Notizen/")
  (defconst MY--PATH_ORG_ROAM         (expand-file-name "p:/Eigene Dateien/Notizen/")))

(setq custom-file (concat MY--PATH_USER_LOCAL "custom.el"))  ;; don't spam init.e with saved customization settings
(setq backup-directory-alist `((".*" . ,temporary-file-directory)))
(setq auto-save-file-name-transforms `((".*" ,temporary-file-directory)))
(customize-set-variable 'auth-sources (list (concat MY--PATH_USER_LOCAL "authinfo")
                                            (concat MY--PATH_USER_LOCAL "authinfo.gpg")
                                            (concat MY--PATH_USER_LOCAL "netrc")))
#+end_src


** Browser
#+begin_src emacs-lisp
(setq browse-url-function 'browse-url-generic
      browse-url-generic-program "firefox")
#+end_src* Package Management
** Elpaca
Boilerplate for Elpaca
#+begin_src emacs-lisp
(defvar elpaca-installer-version 0.11)
(defvar elpaca-directory (expand-file-name "elpaca/" user-emacs-directory))
(defvar elpaca-builds-directory (expand-file-name "builds/" elpaca-directory))
(defvar elpaca-repos-directory (expand-file-name "repos/" elpaca-directory))
(defvar elpaca-order '(elpaca :repo "https://github.com/progfolio/elpaca.git"
                              :ref nil :depth 1 :inherit ignore
                              :files (:defaults "elpaca-test.el" (:exclude "extensions"))
                              :build (:not elpaca--activate-package)))
(let* ((repo  (expand-file-name "elpaca/" elpaca-repos-directory))
       (build (expand-file-name "elpaca/" elpaca-builds-directory))
       (order (cdr elpaca-order))
       (default-directory repo))
  (add-to-list 'load-path (if (file-exists-p build) build repo))
  (unless (file-exists-p repo)
    (make-directory repo t)
    (when (< emacs-major-version 28) (require 'subr-x))
    (condition-case-unless-debug err
        (if-let* ((buffer (pop-to-buffer-same-window "*elpaca-bootstrap*"))
                  ((zerop (apply #'call-process `("git" nil ,buffer t "clone"
                                                  ,@(when-let* ((depth (plist-get order :depth)))
                                                      (list (format "--depth=%d" depth) "--no-single-branch"))
                                                  ,(plist-get order :repo) ,repo))))
                  ((zerop (call-process "git" nil buffer t "checkout"
                                        (or (plist-get order :ref) "--"))))
                  (emacs (concat invocation-directory invocation-name))
                  ((zerop (call-process emacs nil buffer nil "-Q" "-L" "." "--batch"
                                        "--eval" "(byte-recompile-directory \".\" 0 'force)")))
                  ((require 'elpaca))
                  ((elpaca-generate-autoloads "elpaca" repo)))
            (progn (message "%s" (buffer-string)) (kill-buffer buffer))
          (error "%s" (with-current-buffer buffer (buffer-string))))
      ((error) (warn "%s" err) (delete-directory repo 'recursive))))
  (unless (require 'elpaca-autoloads nil t)
    (require 'elpaca)
    (elpaca-generate-autoloads "elpaca" repo)
    (let ((load-source-file-function nil)) ("./elpaca-autoloads"))))
(add-hook 'after-init-hook #'elpaca-process-queues)
(elpaca `(,@elpaca-order))
#+end_src

some elpaca customization after the boilerplate
#+begin_src emacs-lisp
(when *work_remote*
  (setq elpaca-queue-limit 12)
  (elpaca-no-symlink-mode))

(elpaca elpaca-use-package
  (elpaca-use-package-mode))

(setq elpaca-ui-row-limit nil)
(elpaca-wait)
#+end_src
stolen from
https://github.com/progfolio/.emacs.d/blob/cff07d4454d327a4df1915a2cdf8ac6bc5dfde23/init.org?plain=1#L276

usage e.g. here. Not sure though what the benefits are
https://github.com/justinbarclay/.emacs.d
#+begin_src emacs-lisp
(defmacro use-feature (name &rest args)
  "Like `use-package' but accounting for asynchronous installation.
NAME and ARGS are in `use-package'."
  (declare (indent defun))
  `(use-package ,name
     :ensure nil
     ,@args))
#+end_src
* use-package keywords general / diminish
Needs to be loaded before any other package which uses the :general keyword

#+BEGIN_SRC emacs-lisp
(use-package general
  :ensure t
  :demand t)

(use-package diminish
  :ensure t
  :demand t)

;;wait for elpaca any time a use-package keyword is added
(elpaca-wait)
#+END_SRC

* sane defaults
#+begin_src emacs-lisp
(setq-default create-lockfiles nil) ;; disable lock files, can cause trouble in e.g. lsp-mode
(defalias 'yes-or-no-p 'y-or-n-p) ;; answer with y and n
(setq custom-safe-themes t) ;; don't ask me if I want to load a theme
(setq sentence-end-double-space nil) ;; don't coun two spaces after a period as the end of a sentence.
(delete-selection-mode t)  ;; delete selected region when typing
(use-package saveplace
  :ensure nil
  :config
  (save-place-mode 1)  ;; saves position in file when it's closed
  :custom
  (save-place-file (concat MY--PATH_USER_LOCAL "places")))
(setq save-place-forget-unreadable-files nil)  ;; checks if file is readable before saving position
(global-set-key (kbd "RET") 'newline-and-indent)  ;; indent after newline
(setq save-interprogram-paste-before-kill t)  ;; put replaced text into killring

;; https://emacs.stackexchange.com/questions/3673/how-to-make-vc-and-magit-treat-a-symbolic-link-to-a-real-file-in-git-repo-just
(setq find-file-visit-truename t)  ;; some programs like lsp have trouble following symlinks, maybe vc-follow-symlinks would be enough
#+END_SRC

* Performance Optimization
** Garbage Collection
Make startup faster by reducing the frequency of garbage collection.
Set gc-cons-threshold (default is 800kb) to maximum value available, to prevent any garbage collection from happening during load time.

#+BEGIN_SRC emacs-lisp :tangle early-init.el
(setq gc-cons-threshold most-positive-fixnum)
#+END_SRC

Restore it to reasonable value after init. Also stop garbage collection during minibuffer interaction (helm etc.)

#+begin_src emacs-lisp
(defconst 1mb 1048576)
(defconst 20mb 20971520)
(defconst 30mb 31457280)
(defconst 50mb 52428800)

(defun my--defer-garbage-collection ()
  (setq gc-cons-threshold most-positive-fixnum))

(defun my--restore-garbage-collection ()
  (run-at-time 1 nil (lambda () (setq gc-cons-threshold 30mb))))

(add-hook 'emacs-startup-hook 'my--restore-garbage-collection 100)
(add-hook 'minibuffer-setup-hook 'my--defer-garbage-collection)
(add-hook 'minibuffer-exit-hook 'my--restore-garbage-collection)

(setq read-process-output-max 1mb)  ;; lsp-mode's performance suggest
#+end_src

** File Handler
#+begin_src emacs-lisp :tangle early-init.el
(defvar default-file-name-handler-alist file-name-handler-alist)
(setq file-name-handler-alist nil)

(add-hook 'emacs-startup-hook
          (lambda ()
            (setq file-name-handler-alist default-file-name-handler-alist)) 100)
#+end_src

** Others
#+begin_src emacs-lisp :tangle early-init.el
;; Resizing the emacs frame can be a terriblu expensive part of changing the font.
;; By inhibiting this, we easily hale startup times with fonts that are larger
;; than the system default.
(setq package-enable-at-startup nil)
(setq frame-inhibit-implied-resize t)
#+end_src

* Appearance
** Defaults
#+begin_src emacs-lisp
(set-charset-priority 'unicode)
(setq-default locale-coding-system 'utf-8
              default-process-coding-system '(utf-8-unix . utf-8-unix))
(set-terminal-coding-system 'utf-8)
(set-keyboard-coding-system 'utf-8)
(set-selection-coding-system 'utf-8)
(if *sys/windows*
    (progn
      (prefer-coding-system 'utf-8-dos)
      (set-clipboard-coding-system 'utf-16-le)
      (set-selection-coding-system 'utf-16-le))
  (prefer-coding-system 'utf-8))

(setq-default bidi-paragraph-direction 'left-to-right
              bidi-inhibit-bpa t                      ;; both settings reduce line rescans
              uniquify-buffer-name-style 'forward
              indent-tabs-mode nil                    ;; avoid tabs in place of multiple spaces (they look bad in tex)
              indicate-empty-lines t                  ;; show empty lines
              scroll-margin 5                         ;; smooth scrolling
              scroll-conservatively 10000
              scroll-preserve-screen-position 1
              scroll-step 1
              ring-bell-function 'ignore              ;; disable pc speaker bell
              visible-bell t)
(global-hl-line-mode t)                               ;; highlight current line
(blink-cursor-mode -1)                                ;; turn off blinking cursor
(column-number-mode t)
#+end_src

** Remove redundant UI
#+begin_src emacs-lisp :tangle early-init.el
(menu-bar-mode -1)    ;; disable menu bar
(tool-bar-mode -1)    ;; disable tool bar
(scroll-bar-mode -1)  ;; disable scroll bar
#+end_src

** Font
#+BEGIN_SRC emacs-lisp
(when *sys/linux*
  (set-face-font 'default "Hack-10"))
(when *work_remote*
  (set-face-font 'default "Lucida Sans Typewriter-11"))
#+END_SRC

** Themes
#+BEGIN_SRC emacs-lisp
(defun my/toggle-theme ()
  (interactive)
  (when (or *sys/windows* *sys/linux*)
    (if (eq (car custom-enabled-themes) 'plastic)
        (progn (disable-theme 'plastic)
               (load-theme 'leuven))
      (progn
        (disable-theme 'leuven)
        (load-theme 'plastic)))))

(bind-key "C-c t" 'my/toggle-theme)
#+END_SRC

Windows Theme:
#+BEGIN_SRC emacs-lisp
(when *sys/windows*
  (mapcar #'disable-theme custom-enabled-themes)
  (load-theme 'tango))
(when *sys/linux*
  (mapcar #'disable-theme custom-enabled-themes)
  (load-theme 'leuven))
#+END_SRC

** line wrappings
#+BEGIN_SRC emacs-lisp
(global-visual-line-mode)
;(diminish 'visual-line-mode)
(use-package adaptive-wrap
  :ensure t
  :hook
  (visual-line-mode . adaptive-wrap-prefix-mode))
  ;  :init
  ;  (when (fboundp 'adaptive-wrap-prefix-mode)
  ;    (defun me/activate-adaptive-wrap-prefix-mode ()
  ;      "Toggle `visual-line-mode' and `adaptive-wrap-prefix-mode' simultaneously."
  ;      (adaptive-wrap-prefix-mode (if visual-line-mode 1 -1)))
  ;    (add-hook 'visual-line-mode-hook 'me/activate-adaptive-wrap-prefix-mode)))
#+END_SRC

** line numbers
#+BEGIN_SRC emacs-lisp
(use-package display-line-numbers
  :ensure nil
  :init
  :hook
  ((prog-mode
    org-src-mode) . display-line-numbers-mode)
  :config
  (setq-default display-line-numbers-type 'visual
                display-line-numbers-current-absolute t
                display-line-numbers-with 4
                display-line-numbers-widen t))
#+END_SRC

** misc
Delight can replace mode names with custom names ,
e.g. python-mode with just "π ".
#+BEGIN_SRC emacs-lisp
(use-package rainbow-mode
  :ensure t
  :diminish
  :hook
  ((org-mode
    emacs-lisp-mode) . rainbow-mode))

(use-package delight
  :if *sys/linux*
  :ensure t)

(show-paren-mode t) ;; show other part of brackets
(setq blink-matching-paren nil)  ;; not necessary with show-paren-mode, bugs out on C-s counsel-line
(use-package rainbow-delimiters
  :ensure t
  :hook
  (prog-mode . rainbow-delimiters-mode))
#+END_SRC


* dired
#+begin_src emacs-lisp
(use-package dired
  :ensure nil
  :custom
  (dired-kill-when-opening-new-dired-buffer t))
#+end_src

* Bookmarks
Usage:
- C-x r m (bookmark-set): add bookmark
- C-x r l (list-bookmark): list bookmarks
- C-x r b (bookmark-jump): open bookmark

Edit bookmarks (while in bookmark file):
- d: mark current item
- x: delete marked items
- r: rename current item
- s: save changes

#+begin_src emacs-lisp
(use-package bookmark
  :ensure nil
  :custom
  (bookmark-default-file (concat MY--PATH_USER_LOCAL "bookmarks")))

;;do I really want this?
(use-package bookmark+
  :ensure (:host github :repo "emacsmirror/bookmark-plus"))
#+end_src

Some windows specific stuff
#+BEGIN_SRC emacs-lisp
(when *sys/windows*
  (remove-hook 'find-file-hook 'vc-refresh-state)
;  (progn
;    (setq gc-cons-threshold (* 511 1024 1024)
;          gc-cons-percentage 0.5
;          garbage-collection-messages t

;    (run-with-idle-timer 5 t #'garbage-collect))
  (when (boundp 'w32-pipe-read-delay)
    (setq w32-pipe-read-delay 0))
  (when (boundp 'w32-get-true-file-attributes)
    (setq w32-get-true-file-attributes nil))
  (setq text-mode-ispell-word-completion nil)  ;;got errors since emacs30.1 (2025-04-14) in corfu/ispell, no local dictionary? 
  )
#+END_SRC

* burly
[[https://github.com/alphapapa/burly.el][Github]]
Store window configuration and save them as a bookmark
burly-bookmark-windows: bookmarks the current window layout

#+begin_src emacs-lisp
(use-package burly
  :ensure t
  :config
  (burly-tabs-mode) ;;open a burly window bookbark in a new tab
  )
#+end_src

* recentf
Exclude some dirs from spamming recentf
#+begin_src emacs-lisp
(use-package recentf
  :ensure nil
;  :defer 1
  :config
  (recentf-mode)
  :custom
  (recentf-exclude '(".*-autoloads\\.el\\'"
                      "[/\\]\\elpa/"
                      "COMMIT_EDITMSG\\'"))
  (recentf-save-file (concat MY--PATH_USER_LOCAL "recentf"))
  (recentf-max-menu-items 600)
  (recentf-max-saved-items 600))
#+end_src

* savehist
#+begin_src emacs-lisp
(use-package savehist
  :ensure nil
  :config
  (savehist-mode)
  :custom
  (savehist-file (concat MY--PATH_USER_LOCAL "history")))
#+end_src


* undo
#+BEGIN_SRC emacs-lisp
(use-package undo-tree
  :ensure t
  :diminish undo-tree-mode
  :init
  (global-undo-tree-mode 1)
  :custom
  (undo-tree-auto-save-history nil))
#+END_SRC

* COMMENT ace-window (now avy)
#+begin_src emacs-lisp
(use-package ace-window
  :ensure t
  :bind
  (:map global-map
        ("C-x o" . ace-window)))
#+end_src

* which-key
#+BEGIN_SRC emacs-lisp
(use-package which-key
  :ensure t
  :diminish which-key-mode
  :custom
  (which-key-idle-delay 0.5)
  (which-key-sort-order 'which-key-description-order)
  :config
  (which-key-mode)
  (which-key-setup-side-window-bottom))
#+END_SRC

* abbrev
#+begin_src emacs-lisp
(use-package abbrev
  :ensure nil
  :diminish abbrev-mode
  :hook
  ((text-mode org-mode) . abbrev-mode)
  :init
  (setq abbrev-file-name (concat MY--PATH_USER_GLOBAL "abbrev_tables.el"))
  :config
  (if (file-exists-p abbrev-file-name)
      (quietly-read-abbrev-file))
  (setq save-abbrevs 'silently))  ;; don't bother me with asking for abbrev saving
#+end_src

* imenu-list
   A minor mode to show imenu in a sidebar.
   Call imenu-list-smart-toggle.
   [[https://github.com/bmag/imenu-list][Source]]

#+BEGIN_SRC emacs-lisp
(use-package imenu-list
  :ensure t
  :demand t  ; otherwise mode loads too late and won't work on first file it's being activated on
  :config
  (setq imenu-list-focus-after-activation t
        imenu-list-auto-resize t
        imenu-list-position 'right)
  :general
  ([f9] 'imenu-list-smart-toggle)
  (:states '(normal insert)
   :keymaps 'imenu-list-major-mode-map
   "RET" '(imenu-list-goto-entry    :which-key "goto")
   "TAB" '(hs-toggle-hiding         :which-key "collapse")
   "v"   '(imenu-list-display-entry :which-key "show")  ; also prevents visual mode
   "q"   '(imenu-list-quit-window   :which-key "quit"))
  :custom
  (org-imenu-depth 4))
#+END_SRC

* COMMENT Evil
See also
https://github.com/noctuid/evil-guide

Use C-z (evil-toggle-key) to switch between evil and emacs keybindings,
in case evil is messing something up.

#+BEGIN_SRC emacs-lisp
(use-package evil
  :ensure t
  :defer .1
  :custom
  (evil-want-C-i-jump nil)  ;; prevent evil from blocking TAB in org tree expanding
  (evil-want-integration t)
  (evil-want-keybinding nil)
  :config
  ;; example for using emacs default key map in a certain mode
  ;; (evil-set-initial-state 'dired-mode 'emacs)
  (evil-mode 1))
#+END_SRC
* Eldoc
use builtin version
#+begin_src emacs-lisp
    (use-package eldoc
      :ensure nil
      :diminish eldoc-mode
      :defer t)
#+end_src
* COMMENT Eldoc Box
Currently corfu-popupinfo displays eldoc in highlighted completion candidate. Maybe that's good enough.
#+begin_src emacs-lisp
(use-package eldoc-box
  :ensure t)
#+end_src

* Meow
#+begin_src emacs-lisp
(use-package meow
  :ensure t
  :config
  (setq meow-cheatsheet-layout meow-cheatsheet-layout-qwerty)
  (meow-motion-overwrite-define-key
   '("j"          . meow-next)
   '("k"          . meow-prev)
   '("<escape>" . ignore))
  (meow-leader-define-key
   ;; SPC j/k will run the original command in MOTION state.
   '("j"        . "H-j")
   '("k"        . "H-k")
   ;; Use SPC (0-9) for digit arguments.
   '("1"        . meow-digit-argument)
   '("2"        . meow-digit-argument)
   '("3"        . meow-digit-argument)
   '("4"        . meow-digit-argument)
   '("5"        . meow-digit-argument)
   '("6"        . meow-digit-argument)
   '("7"        . meow-digit-argument)
   '("8"        . meow-digit-argument)
   '("9"        . meow-digit-argument)
   '("0"        . meow-digit-argument)
   '("/"        . meow-keypad-describe-key)
   '("?"        . meow-cheatsheet))
  (meow-normal-define-key
   '("0"        . meow-expand-0)
   '("9"        . meow-expand-9)
   '("8"        . meow-expand-8)
   '("7"        . meow-expand-7)
   '("6"        . meow-expand-6)
   '("5"        . meow-expand-5)
   '("4"        . meow-expand-4)
   '("3"        . meow-expand-3)
   '("2"        . meow-expand-2)
   '("1"        . meow-expand-1)
   '("-"        . negative-argument)
   '(";" . meow-reverse)
   '(","        . meow-inner-of-thing)
   '("."        . meow-bounds-of-thing)
   '("["        . meow-beginning-of-thing)
   '("]"        . meow-end-of-thing)
   '("a"        . meow-append)
   '("A"        . meow-open-below)
   '("b"        . meow-back-word)
   '("B"        . meow-back-symbol)
   '("c"        . meow-change)
   '("d"        . meow-delete)
   '("D"        . meow-backward-delete)
   '("e"        . meow-next-word)
   '("E"        . meow-next-symbol)
   '("f"        . meow-find)
   '("g"        . meow-cancel-selection)
   '("G"        . meow-grab)
   '("h"        . meow-left)
   '("H"        . meow-left-expand)
   '("i"        . meow-insert)
   '("I"        . meow-open-above)
   '("j"        . meow-next)
   '("J"        . meow-next-expand)
   '("k"        . meow-prev)
   '("K"        . meow-prev-expand)
   '("l"        . meow-right)
   '("L"        . meow-right-expand)
   '("m"        . meow-join)
   '("n"        . meow-search)
   '("o"        . meow-block)
   '("O"        . meow-to-block)
   '("p"        . meow-yank)
   '("q"        . meow-quit)
   '("Q"        . meow-goto-line)
   '("r"        . meow-replace)
   '("R"        . meow-swap-grab)
   '("s"        . meow-kill)
   '("t"        . meow-till)
   '("u"        . meow-undo)
   '("U"        . meow-undo-in-selection)
   '("v"        . meow-visit)
   '("w"        . meow-mark-word)
   '("W"        . meow-mark-symbol)
   '("x"        . meow-line)
   '("X"        . meow-goto-line)
   '("y"        . meow-save)
   '("Y"        . meow-sync-grab)
   '("z"        . meow-pop-selection)
   '("'"        . repeat)
   '("<escape>" . ignore))
;  :config
  (meow-global-mode t))
#+end_src

* avy
Search, move, copy, delete text within all visible buffers.
Also replaces ace-window for buffer switching.
[[https://github.com/abo-abo/avy]]

#+BEGIN_SRC emacs-lisp
(use-package avy
  :ensure t
  :general
  (:prefix "M-s"
   ""    '(:ignore t           :which-key "avy")
   "w"   '(avy-goto-char-2     :which-key "avy-jump")
   "s"   '(avy-goto-char-timer :which-key "avy-timer")
   "c"   '(:ignore t           :which-key "avy copy")
   "c l" '(avy-copy-line       :which-key "avy copy line")
   "c r" '(avy-copy-region     :which-key "avy copy region")
   "m"   '(:ignore t           :which-key "avy move")
   "m l" '(avy-move-line       :which-key "avy move line")
   "m r" '(avy-move-region     :which-key "avy move region")))
#+END_SRC

* Vertico
Vertico is a completion ui for the minibuffer.
[[https://github.com/minad/vertico][Vertico Github]]

#+begin_src emacs-lisp
;; completion ui
(use-package vertico
  :ensure t
  :init
  (vertico-mode))
#+end_src

* Corfu
Completion ui for the buffer.
[[https://github.com/minad/corfu][Corfu Github]]

#+begin_src emacs-lisp
(use-package corfu
  :ensure t
  :after savehist
  :custom
  (corfu-popupinfo-delay t)
  (corfu-auto t)
  (corfu-cycle t)
  (corfu-auto-delay 0.3)
  (corfu-preselect-first nil)
  (corfu-popupinfo-delay '(1.0 . 0.0)) ;1s for first popup, instant for subsequent popups
  (corfu-popupinfo-max-width 70)
  (corfu-popupinfo-max-height 20)
  :init
  (global-corfu-mode)
;  (corfu-popupinfo-mode)  ; causes corfu window to stay
  (corfu-history-mode)
  ;; belongs to emacs
  (add-to-list 'savehist-additional-variables 'corfu-history)
  :hook
  (corfu-mode . corfu-popupinfo-mode))
;  :bind
;  (:map corfu-map
;        ("TAB" . corfu-next)
;        ("<C-return>" . corfu-insert)
;        ("C-TAB" . corfu-popupinfo-toggle)))

;; (general-define-key
;;  :states 'insert
;;  :definer 'minor-mode
;;  :keymaps 'completion-in-region-mode
;;  :predicate 'corfu-mode
;;  "C-d" 'corfu-info-documentation)

(use-package emacs
  :ensure nil
  :init
  ;; hide commands in M-x which do not apply to current mode
  (setq read-extended-command-predicate #'command-completion-default-include-p)
  ;; enable indentation + completion using TAB
  (setq tab-always-indent 'complete))
#+end_src

* Cape
[[https://github.com/minad/cape][Cape Github]]
Backend completions for the buffer (not minibuffer).
Available functions:
dabbrev, file, history, keyword, tex, sgml, rfc1345, abbrev, ispell, dict, symbol, line

#+begin_src emacs-lisp
(use-package cape
  :ensure t
  :bind
  (("C-c p p" . completion-at-point) ;; capf
   ("C-c p t" . complete-tag)   ;; etags
   ("C-c p d" . cape-dabbrev)
   ("C-c p h" . cape-history)
   ("C-c p f" . cape-file))
  :init
  (advice-add #'lsp-completion-at-point :around #'cape-wrap-noninterruptible)  ;; for performance issues with lsp
  (add-to-list 'completion-at-point-functions #'cape-dabbrev)
  (add-to-list 'completion-at-point-functions #'cape-file)
  (add-to-list 'completion-at-point-functions #'cape-history))
#+end_src

* kind-icon
Make corfu pretty
[[https://github.com/jdtsmith/kind-icon][kind-icon Github]]

#+begin_src emacs-lisp
(use-package kind-icon
  :ensure t
  :after corfu
  :custom
  (kind-icon-default-face 'corfu-default)  ;; to compute blended backgrounds correctly
  :config
  (add-to-list 'corfu-margin-formatters #'kind-icon-margin-formatter))
#+end_src

* Orderless
[[https://github.com/oantolin/orderless][Orderless Github]]
Orderless enables customizing the way how input is completed, orders the suggestions by recency. Works for vertico and corfu.

#+begin_src emacs-lisp
(use-package orderless
  :ensure t
  :init
  (setq completion-styles '(orderless partial-completion basic)
        completion-category-defaults nil
        completion-category-overrides nil))
;        completion-category-overrides '((file (styles partial-completion)))))
#+end_src

* Consult
[[https://github.com/minad/consult][Github]]
Backend completion functions for the minibuffer.
Default preview key: M-.

#+begin_src emacs-lisp
(use-package consult
  :ensure t
  :bind
  (("C-x C-r" . consult-recent-file)
   ("C-x b" . consult-buffer)
   ("C-s" . consult-line)
   ("C-x r b" . consult-bookmark))  ;replace bookmark-jump
  :config
  ;; disable preview for some commands and buffers
  ;; and enable it by M-.
  ;; see https://github.com/minad/consult#use-package-example
  (consult-customize
   consult-theme :preview-key '(debounce 0.2 any)
   consult-ripgrep consult-git-grep consult-grep
   consult-bookmark consult-recent-file consult-xref
   consult--source-bookmark consult--source-file-register
   consult--source-recent-file consult--source-project-recent-file
   :preview-key '(:debounce 0.2 any)))
#+end_src

* Marginalia
[[https://github.com/minad/marginalia/][Github]]
Adds additional information/annotations to the minibuffer
#+begin_src emacs-lisp
(use-package marginalia
  :ensure t
  :init
  (marginalia-mode)
  :bind
  (:map minibuffer-local-map
        ("M-A" . marginalia-cycle))
  :custom
  ;; switch by 'marginalia-cycle
  (marginalia-annotators '(marginalia-annotators-heavy
                           marginalia-annotators-light
                           nil)))
#+end_src
* Embark
Actions on the completion buffer.
#+begin_src emacs-lisp
(use-package embark
  :ensure t
  :bind
  (("C-S-a" . embark-act)
   ("C-h B" . embark-bindings))
  :init
  (setq prefix-help-command #'embark-prefix-help-command)
  :config
  ;; hide modeline of the embark live/completions buffers
  (add-to-list 'display-buffer-alist
               '("\\`\\*Embark Collect \\(Live\\|Completions\\)\\*"
                 nil
                 (window-parameters (mode-line-format . none)))))

(use-package embark-consult
  :ensure t
  :after (embark consult)
  :demand t
  :hook
  (embark-collect-mode . embark-consult-preview-minor-mode))
#+end_src
* Tree-sitter
#+begin_src emacs-lisp
  (when *sys/linux*
    (use-package tree-sitter
      :ensure t
      :init
      (global-tree-sitter-mode t)
      :hook
      (tree-sitter-after-on . tree-sitter-hl-mode))

    (use-package tree-sitter-langs
      :ensure t
      :after tree-sitter)
  )
#+end_src

* COMMENT Xeft (needs xapian, not really windows compatible)
Fast full text search for stuff org-ql cannot cover
#+begin_src emacs-lisp
(use-package xeft
  :ensure t
  :custom
  (xeft-recursive 'follow-symlinks))
#+end_src
* COMMENT Helm
As an alternative if I'm not happy with selectrum & co
#+begin_src emacs-lisp
(use-package helm
  :ensure t
  :hook
  (helm-mode . helm-autoresize-mode)
  ;; :bind
  ;; (("M-x" . helm-M-x)
  ;;  ("C-s" . helm-occur)
  ;;  ("C-x C-f" . helm-find-files)
  ;;  ("C-x C-b" . helm-buffers-list)
  ;;  ("C-x b" . helm-buffers-list)
  ;;  ("C-x C-r" . helm-recentf)
  ;;  ("C-x C-i" . helm-imenu))
  :config
  (helm-mode)
  :custom
  (helm-split-window-inside-p t)  ;; open helm buffer inside current window
  (helm-move-to-line-cycle-in-source t)
  (helm-echo-input-in-header-line t)
  (helm-autoresize-max-height 20)
  (helm-autoresize-min-height 5)
  )
#+end_src
* Emails
Requires on system
- isync to sync emails between host and local
- mu / mu4e4e (apt nstall mu4e/bookworm-backports)

After installing mu4e it needs to be initialized
#+begin_src shell :tangle no
mu init --maildir=/path/to/mail/folder --my-address=mu@adress.com --my-address=another@adress.com...
mu index
#+end_src

for credentials see
https://www.emacswiki.org/emacs/SmtpAuth

#+begin_src emacs-lisp
;(use-package notmuch
;  :ensure t)
(use-feature mu4e
  :if *sys/linux*
;  :ensure nil
  :after (org)
  :init
  (require 'mu4e)
  :custom
  ;; this is set to 't' to avoid mail syncing issues when using mbsync
  (mu4e-change-filenames-when-moving t)
  (smtpmail-servers-requiring-authorization ".*")
  (mu4e-update-interval (* 10 60))
  (mu4e-get-mail-command "mbsync -a")
  (mu4e-maildir "~/archiv/Dokumente/email")
  (mu4e-attachment-dir "/mnt/backup/downloads")
  (mu4e-org-support t)
  (mu4e-completion-read-function 'completion-read)
  (mu4e-use-fancy-chars t)
  (mu4e-view-show-addresses t)
  (mu4e-view-show-images t)
  (mu4e-sent-messages-behaviour 'sent)  ;works better with mbsync
  (mu4e-headers-fields
        '((:human-date . 12)
          (:flags . 6)
          (:from . 22)
          (:to . 25)
          (:subject)))
  :config
  (setq mu4e-contexts
        `( ,(make-mu4e-context
             :name "mail.de"
             :enter-func (lambda () (mu4e-message "switch to mail.de context"))
             :match-func (lambda (msg)
                           (when msg
                              (string-match-p "^/mailde" (mu4e-message-field msg :maildir))))
             :vars '((user-mail-address  . "marc.pohling@mail.de")
                     (user-full-name     . "Marc Pohling")
                     (message-send-mail-function . smtpmail-send-it)
                     (smtpmail-smtp-user . "marc.pohling@mail.de")
                     (smtpmail-smtp-server . "smtp.mail.de")
                     (smtpmail-smtp-service . 587)
                     (smtpmail-stream-type . starttls)
                     (mu4e-drafts-folder . "/mailde/drafts")
                     (mu4e-sent-folder   . "/mailde/sent")
                     (mu4e-refile-folder . "/mailde/archive")
                     (mu4e-trash-folder  . "/mailde/trash")))
           ,(make-mu4e-context
             :name "web.de"
             :enter-func (lambda () (mu4e-message "switch to web.de context"))
             :match-func (lambda (msg)
                           (when msg
                              (string-match-p "^/mailde" (mu4e-message-field msg :maildir))))
             :vars '((user-mail-address  . "marc.pohling@web.de")
                     (user-full-name     . "Marc Pohling")
                     (mu4e-drafts-folder . "/webde/drafts")
                     (mu4e-sent-folder   . "/webde/sent")
                     (mu4e-refile-folder . "/webde/archive")
                     (mu4e-trash-folder  . "/webde/trash")))
           ,(make-mu4e-context
             :name "gmail loui"
             :enter-func (lambda () (mu4e-message "switch to gmail loui context"))
             :match-func (lambda (msg)
                           (when msg
                              (string-match-p "^/gmailloui" (mu4e-message-field msg :maildir))))
             :vars '((user-mail-address  . "louithelou1@gmail.com")
                     (user-full-name     . "Loui")
                     (message-send-mail-function . smtpmail-send-it)
                     (smtpmail-smtp-user . "louithelou1@gmail.com")
                     (smtpmail-smtp-server . "smtp.gmail.com")
                     (smtpmail-smtp-service . 587)
                     (smtpmail-stream-type . starttls)
                     (mu4e-drafts-folder . "/gmailloui/drafts")
                     (mu4e-sent-folder   . "/gmailloui/sent")
                     (mu4e-refile-folder . "/gmailloui/archive")
                     (mu4e-trash-folder  . "/gmailloui/trash")))))
  )
#+end_src

mu4e-dashboard
https://github.com/rougier/mu4e-dashboard

#+begin_src emacs-lisp
(push 'mu4e elpaca-ignored-dependencies)

(use-package mu4e-dashboard
  :if *sys/linux*
  :ensure (:host github :repo "rougier/mu4e-dashboard")
  :after mu4e
  :hook
  (mu4e-dashboard-mode . (lambda () (display-line-numbers-mode -1)))
  :custom
  (mu4e-dashboard-file (concat MY--PATH_USER_GLOBAL "mu4e-dashboard.org"))
  :config
;  (require 'mu4e)
  (defun mu4e-dashboard-edit ()
    (interactive)
    (let ((edit-buffer "*edit-mu4e-dashboard*"))
      (when (get-buffer edit-buffer)
        (kill-buffer (get-buffer edit-buffer)))
      (make-indirect-buffer (current-buffer) edit-buffer)
      (switch-to-buffer-other-window (get-buffer edit-buffer))
      (org-mode 1)))
  (display-line-numbers-mode -1)
  (flyspell-mode -1))
#+end_src
* outlook
In outlook a macro is necessary, also a reference to FM20.DLL
(Microsoft Forms 2.0 Object Library, in c:\windows\syswow64\fm20.dll)
The macro copies the GUID of the email to the clipboard
Attention: the GUID changes when the email is moved to another folder!

The macro:
#+BEGIN_SRC shell :tangle no
Sub AddLinkToMessageInClipboard()
'Adds a link to the currently selected message to the clipboard
   Dim objMail As Outlook.MailItem
   Dim doClipboard As New DataObject

   'One and ONLY one message muse be selected
   If Application.ActiveExplorer.Selection.Count <> 1 Then
       MsgBox ("Select one and ONLY one message.")
       Exit Sub
   End If

   Set objMail = Application.ActiveExplorer.Selection.Item(1)
   doClipboard.SetText "[[outlook:" + objMail.EntryID + "][MESSAGE: " + objMail.Subject + " (" + objMail.SenderName + ")]]"
   doClipboard.PutInClipboard
End Sub
#+END_SRC

#+BEGIN_SRC emacs-lisp
;(org-add-link-type "outlook" 'my--org-outlook-open)
;(org-add-link-type "outlooknewmail" 'my--org-outlook-new-mail)

(defun my--org-outlook-open (id)
  (w32-shell-execute "open" "outlook" (concat " /select outlook:" id)))

(defun my--org-outlook-new-mail (receipients)
  ; separate RECEIPIENTS for main, cc and bcc by |
  (setq recp (split-string receipients "|"))
  (setq mailstring (concat " /c ipm.note /m " (nth 0 recp)))
  (if (nth 1 recp)
      ; this check has tp be separated because (and..) would stumble over a nil
      (if (not (equal "" (nth 1 recp)))
          (setq mailstring (concat mailstring "&cc=" (nth 1 recp)))))
  (if (nth 2 recp)
      (setq mailstring (concat mailstring "&bcc=" (nth 2 recp))))
  (w32-shell-execute "open" "outlook" mailstring))

(defun my/org-outlook-open-test ()
  (interactive)
  (w32-shell-execute "open" "outlook" " /select outlook:000000008A209C397CEF2C4FBA9E54AEB5B1F97F0700846D043B407C5B43A0C05AFC46DC5C630587BE5E020900006E48FF8F6027694BA6593777F542C19E0002A6434D000000"))'
#+END_SRC

* misc
#+begin_src emacs-lisp
(use-package autorevert
  :diminish auto-revert-mode)
#+end_src

* orgmode
** some notes
*** copy file path within emacs
Enter dired-other-window
place cursor on the file
M-0 w (copy absolute path)
C-u w (copy relative path)
*** Archiving
C-c C-x C-a

To keep the subheading structure when archiving, set the properties of the superheading.
#+begin_src org :tangle no
,* FOO
:PROPERTIES:
:ARCHIVE: %s_archive::* FOO
,** DONE BAR
,** TODO BAZ
#+end_src
When moving BAR to archive, it will go to FILENAME.org_archive below the heading FOO.
[[http://doc.endlessparentheses.com/Var/org-archive-location.html][Other examples]]

** org

This seems necessary to prevent 'org is already installed' error
https://github.com/jwiegley/use-package/issues/319

#+begin_src emacs-lisp
;(assq-delete-all 'org package--builtins)'
;(assq-delete-all 'org package--builtin-versions)
#+end_src

#+BEGIN_SRC emacs-lisp

(use-package org
  :ensure t
;  :pin gnu
  :mode (("\.org$" . org-mode))
  :diminish org-indent-mode
  :defer 1
  :hook
  (org-mode . org-indent-mode)
  (org-source-mode . smartparens-mode)
  :bind (("C-c l" . org-store-link)
         ("C-c c" . org-capture)
         ("C-c a" . org-agenda)
         :map org-mode-map ("S-<right>" . org-shiftright)
       ("S-<left>" . org-shiftleft))
  :init
;;   (defun my--org-agenda-files-set ()
;;     "Sets default agenda files.
;; Necessary when updating roam agenda todos."
;;     (setq org-agenda-files (list (concat MY--PATH_ORG_FILES "notes.org")
;;    (concat MY--PATH_ORG_FILES "projects.org")
;;    (concat MY--PATH_ORG_FILES "tasks.org")))
;;     (when *sys/linux*
;;       (nconc org-agenda-files
;;    (directory-files-recursively MY--PATH_ORG_FILES_MOBILE "\\.org$"))))
;;  (my--org-agenda-files-set)
  (setq org-agenda-files
        (cl-remove-if (lambda (file)
                        ;; exclude some subdirs
                        (let ((excluded-dirs '("notes" "dummy")))
                          (cl-some (lambda (dir) (string-match-p (concat "/" dir "/") file))
                                   excluded-dirs)))
;;                        (string-match-p "/notes/" file))
                      (directory-files-recursively MY--PATH_ORG_FILES "\\.org$")))

  (defun my--org-skip-subtree-if-priority (priority)
      "Skip an agenda subtree if it has a  priority of PRIORITY.

       PRIORITY may be one of the characters ?A, ?B, or ?C."
      (let ((subtree-end (save-excursion (org-end-of-subtree t)))
  (pri-value (* 1000 (- org-lowest-priority priority)))
  (pri-current (org-get-priority (thing-at-point 'line t))))
        (if (= pri-value pri-current)
  subtree-end
nil)))
  :config
  (when *work_remote*
    (org-add-link-type "outlook" 'my--org-outlook-open)
    (org-add-link-type "outlooknewmail" 'my--org-outlook-new-mail)
    (setq org-todo-keywords
     '((sequence "WAIT" "OPEN" "NEXT" "TODO" "|" "DONE" "DROP")))
    (setq org-capture-templates
              '(("t" "telephone call"
             entry (file+olp+datetree "p:/Eigene Dateien/Notizen/phone_calls.org")
             "* call [%<%Y-%m-%d %H:%M>] %?"
             :empty-lines 0
             :prepare-finalize (org-id-get-create)
             :jump-to-captured t)
                ("o" "OP-Verwaltung neuer Eintrag"
                 entry (file+olp+datetree "p:/Eigene Dateien/Notizen/op-verwaltung.org")
                 "* OP [%<%Y-%m-%d %H:%M>] %?"
                 :prepare-finalize (org-id-get-create)
                 :jump-to-captured t)
                )))
  (when *sys/linux*
    (setq org-capture-templates
          ;; TODO
          ;; entry for a journal?
          ;; entry for a project incl. layout
          ;; entry for a todo in a todo file
          ;; add an entry to a new or existing node
          ;; which is not in the org-node exclusion list
          '(("i" "capture into ID node"
             entry (function org-node-capture-target) nil
             :jump-to-captured t
             :empty-lines-after 1)
            ;; jump to an existing org-node
            ("j" "Jumo to ID node"
             plain (function org-node-capture-target) nil
             :jump-to-captured t
             :immediate-finish t)
            ("p" "phone call"
             entry (file+olp+datetree "~/archiv/notes/phonecalls.org")
             "* call [%<%Y-%m-%d %H:%M>] %?"
             :empty-lines 0
             :prepare-finalize (org-id-get-create)
             :jump-to-captured t)
            ))
    (setq org-pretty-entities t))
  :custom
  (org-startup-truncated t)
  (org-startup-align-all-tables t)
  (org-src-fontify-natively t) ;; use syntax highlighting in code blocks
  (org-src-preserve-indentation t) ;; no extra indentation
  (org-src-window-setup 'current-window) ;; C-c ' opens in current window
  (org-modules (quote (org-id
   org-habit
   org-tempo)))  ;; easy templates
  (org-default-notes-file (concat MY--PATH_ORG_FILES "notes.org"))
  (org-id-locations-file (concat MY--PATH_USER_LOCAL ".org-id-locations"))
  (org-log-into-drawer "LOGBOOK")
  (org-log-done 'time)  ;; create timestamp when task is done
  (org-blank-before-new-entry '((heading) (plain-list-item)))  ;; prevent new line before new item
  (org-src-tab-acts-natively t)
  ;;Sort agenda by deadline and priority
      (org-agenda-sorting-strategy
       (quote
        ((agenda deadline-up priority-down)
         (todo priority-down category-keep)
         (tags priority-down category-keep)
         (search category-keep))))
  (org-agenda-custom-commands
   '(("c" "Simple agenda view"
      ((tags "PRIORITY=\"A\""
             ((org-agenda-skip-function '(org-agenda-skip-entry-if 'todo 'done))
              (org-agenda-overriding-header "Hohe Priorität:")))
       (agenda ""
               ((org-agenda-skip-function '(org-agenda-skip-entry-if 'todo 'done))
                (org-agenda-span 7)
                (org-agenda-start-on-weekday nil)
                (org-agenda-overriding-header "Nächste 7 Tage:")))
       (alltodo ""
                ((org-agenda-skip-function '(or (my--org-skip-subtree-if-priority ?A)
                                                (org-agenda-skip-if nil '(scheduled deadline))))
                 (org-agenda-overriding-header "Sonstige Aufgaben:"))))))))
#+END_SRC

** COMMENT languages
Set some languages and disable confirmation for evaluating code blocks C-c C-c

Elpaca cant find it, though it's built in org
#+begin_src emacs-lisp

  (use-package ob-python
  ;  :ensure nil
    :defer t
    :after org
  ;  :ensure org-contrib
    :commands
    (org-babel-execute:python))

#+end_src

** COMMENT habits
#+BEGIN_SRC emacs-lisp
(require 'org-habit)  ;;TODO Lösung ohne require finden, scheint mir nicht ideal zu sein, nur um ein org-modul zu aktivieren
;;  (add-to-list 'org-modules "org-habit")
(setq org-habit-graph-column 80
     org-habit-preceding-days 30
      org-habit-following-days 7
      org-habit-show-habits-only-for-today nil)
#+END_SRC

** contacts
#+BEGIN_SRC emacs-lisp
(use-package org-contacts
  :ensure (:host nil :repo "https://repo.or.cz/org-contacts.git")
  :after org
  :custom
  (org-contacts-files (list (concat MY--PATH_ORG_ROAM "contacts.org"))))
#+END_SRC
** *TODO*
[[https://github.com/nobiot/org-transclusion][org-transclusion]]?

** COMMENT journal
[[https://github.com/bastibe/org-journal][Source]]

Ggf. durch org-roam-journal ersetzen
#+BEGIN_SRC emacs-lisp
(use-package org-journal
  :if *sys/linux*
  :ensure t
  :defer t
  :config
  ;; feels hacky, but this way compiler error "assignment to free variable" disappears
  (when (and (boundp 'org-journal-dir)
             (boundp 'org-journal-enable-agenda-integration))
    (setq org-journal-dir MY--PATH_ORG_JOURNAl
          org-journal-enable-agenda-integration t)))
#+END_SRC

** COMMENT org-roam
[[https://github.com/org-roam/org-roam][Github]]
Um Headings innerhalb einer Datei zu verlinken:
- org-id-get-create im Heading, 
- org-roam-node-insert in der verweisenden Datei

Bei Problemen wie unique constraint
org-roam-db-clear-all
org-roam-db-sync
#+BEGIN_SRC emacs-lisp
;(use-package emacsql-sqlite-builtin
;  :ensure t)

(defun my--buffer-prop-set (name value)
  "Set a file property called NAME to VALUE in buffer file.
If the property is already set, replace its value."
  (setq name (downcase name))
  (org-with-point-at 1
    (let ((case-fold-search t))
      (if (re-search-forward (concat "^#\\+" name ":\\(.*\\)")
                             (point-max) t)
          (replace-match (concat "#+" name ": " value) 'fixedcase)
        (while (and (not (eobp))
                    (looking-at "^[#:]"))
          (if (save-excursion (end-of-line) (eobp))
                        (progn
                          (end-of-line)
                          (insert "\n"))
                      (forward-line)
                      (beginning-of-line)))
                  (insert "#+" name ": " value "\n")))))

          (defun my--buffer-prop-remove (name)
            "Remove a buffer property called NAME."
            (org-with-point-at 1
              (when (re-search-forward (concat "\\(^#\\+" name ":.*\n?\\)")
                                       (point-max) t)
                (replace-match ""))))

(use-package org-roam
;    :requires emacsql-sqlite-builtin
    :ensure t
    :defer 2
    :after org
    :init
    (setq org-roam-v2-ack t)

    (defun my--roamtodo-p ()
      "Return non-nil if current buffer has any todo entry.

TODO entries marked as done are ignored, meaning this function
returns nil if current buffer contains only completed tasks."
      (seq-find
       (lambda (type)
         (eq type 'todo))
       (org-element-map
           (org-element-parse-buffer 'headline)
           'headline
         (lambda (h)
           (org-element-property :todo-type h)))))

    (defun my--roamtodo-update-tag ()
      "Update ROAMTODO tag in the current buffer."
      (when (and (not (active-minibuffer-window))
                 (my--buffer-roam-note-p))
        (save-excursion
          (goto-char (point-min))
          (let* ((tags (my--buffer-tags-get))
                 (original-tags tags))
            (if (my--roamtodo-p)
                (setq tags (cons "roamtodo" tags))
              (setq tags (remove "roamtodo" tags)))

            ;;cleanup duplicates
            (when (or (seq-difference tags original-tags)
                      (seq-difference original-tags tags))
              (apply #'my--buffer-tags-set tags))))))

    (defun my--buffer-tags-get ()
      "Return filetags value in current buffer."
      (my--buffer-prop-get-list "filetags" "[ :]"))

    (defun my--buffer-tags-set (&rest tags)
      "Set TAGS in current buffer.
If filetags value is already set, replace it."
      (if tags
          (my--buffer-prop-set
           "filetags" (concat ":" (string-join tags ":") ":"))
        (my--buffer-prop-remove "filetags")))
    
    (defun my--buffer-tags-add (tag)
      "Add a TAG to filetags in current buffer."
      (let* ((tags (my--buffer-tags-get))
             (tags (append tags (list tag))))
        (apply #'my--buffer-tags-set tags)))
    
    (defun my--buffer-tags-remove (tag)
      "Remove a TAG from filetags in current buffer."
      (let* ((tags (my--buffer-tags-get))
             (tags (delete tag tags)))
        (apply #'my--buffer-tags-set tags)))
    
    (defun my--buffer-prop-set (name value)
      "Set a file property called NAME to VALUE in buffer file.
If the property is already set, replace its value."
      (setq name (downcase name))
      (org-with-point-at 1
        (let ((case-fold-search t))
          (if (re-search-forward (concat "^#\\+" name ":\\(.*\\)")
                                 (point-max) t)
              (replace-match (concat "#+" name ": " value) 'fixedcase)
            (while (and (not (eobp))
                        (looking-at "^[#:]"))
              (if (save-excursion (end-of-line) (eobp))
                  (progn
                    (end-of-line)
                    (insert "\n"))
                (forward-line)
                (beginning-of-line)))
            (insert "#+" name ": " value "\n")))))

    (defun my--buffer-prop-set-list (name values &optional separators)
      "Set a file property called NAME to VALUES in current buffer.
VALUES are quoted and combined into single string using
`combine-and-quote-strings'.
If SEPARATORS is non-nil, it should be a regular expression
matching text that separates, but is not part of, the substrings.
If nil it defaults to `split-string-and-unquote', normally
\"[ \f\t\n\r\v]+\", and OMIT-NULLS is forced to t.
If the property is already set, replace its value."
      (my--buffer-prop-set
       name (combine-and-quote-strings values separators)))
    
    (defun my--buffer-prop-get (name)
      "Get a buffer property called NAME as a string."
      (org-with-point-at 1
        (when (re-search-forward (concat "^#\\+" name ": \\(.*\\)")
                                 (point-max) t)
          (buffer-substring-no-properties
           (match-beginning 1)
           (match-end 1)))))
    
    (defun my--buffer-prop-get-list (name &optional separators)
      "Get a buffer property NAME as a list using SEPARATORS.
If SEPARATORS is non-nil, it should be a regular expression
matching text that separates, but is not part of, the substrings.
If nil it defaults to `split-string-default-separators', normally
\"[ \f\t\n\r\v]+\", and OMIT-NULLS is forced to t."
      (let ((value (my--buffer-prop-get name)))
        (when (and value (not (string-empty-p value)))
          (split-string-and-unquote value separators))))
    
    (defun my--buffer-prop-remove (name)
      "Remove a buffer property called NAME."
      (org-with-point-at 1
        (when (re-search-forward (concat "\\(^#\\+" name ":.*\n?\\)")
                                 (point-max) t)
          (replace-match ""))))

    (defun my--buffer-roam-note-p ()
      "Return non-nil if the currently visited buffer is a note."
      (and buffer-file-name
           (string-prefix-p
            (expand-file-name (file-name-as-directory MY--PATH_ORG_ROAM)) 
            (file-name-directory buffer-file-name))))

    (defun my--org-roam-filter-by-tag (tag-name)
      (lambda (node)
        (member tag-name (org-roam-node-tags node))))

    (defun my--org-roam-list-notes-by-tag (tag-name)
      (mapcar #'org-roam-node-file
              (seq-filter
               (my--org-roam-filter-by-tag tag-name)
               (org-roam-node-list))))

    ;; (defun my/org-roam-refresh-agenda-list ()
    ;;   "Add all org roam files with #+filetags: roamtodo"
    ;;   (interactive)
    ;;   (my--org-agenda-files-set)
    ;;   (nconc org-agenda-files
    ;;          (my--org-roam-list-notes-by-tag "roamtodo"))
    ;;   (setq org-agenda-files (delete-dups org-agenda-files)))
    
    (add-hook 'find-file-hook #'my--roamtodo-update-tag)
    (add-hook 'before-save-hook #'my--roamtodo-update-tag)

;;    (advice-add 'org-agenda :before #'my/org-roam-refresh-agenda-list)
;;    (advice-add 'org-todo-list :before #'my/org-roam-refresh-agenda-list)

    (add-to-list 'org-tags-exclude-from-inheritance "roamtodo")
    :config
    (require 'org-roam-dailies) ;; ensure the keymap is available
    (org-roam-db-autosync-mode)
    ;; build the agenda list the first ime for the session
;;    (my/org-roam-refresh-agenda-list)

    (when *work_remote*
      (setq org-roam-capture-templates
       '(("n" "note" plain
          "%?"
          :if-new (file+head "notes/%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n")
          :unnarrowed t)
         ("i" "idea" plain
          "%?"
          :if-new (file+head "ideas/%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n")
          :unnarrowed t)
        ("p" "project" plain
         "%?"
         :target (file+head "projects/${slug}.org" "#+title: ${title}\n#+filetags: :project:\n")
         :unnarrowed t)
        ("s" "Sicherheitenmeldung" plain
         "*** TODO [#A] Sicherheitenmeldung ${title}\n :PROPERTIES:\n :ID: %(org-id-uuid)\n:END:\n%u\n"
         :target (file+olp "tasks.org" ("Todos" "Sicherheitenmeldungen")))
        ("m" "Monatsbericht" plain
         "*** TODO [#A] Monatsbericht ${title}\n :PROPERTIES:\n :ID: %(org-id-uuid)\n:END:\n%u\n"
         :target (file+olp "tasks.org" ("Todos" "Monatsberichte"))))))
    :custom
    (org-roam-database-connector 'sqlite-builtin)
    (org-roam-directory MY--PATH_ORG_ROAM)
    (org-roam-completion-everywhere t)
    (org-roam-capture-templates
     '(("n" "note" plain
        "%?"
        :if-new (file+head "notes/%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n")
        :unnarrowed t)
       ("i" "idea" plain
        "%?"
        :if-new (file+head "ideas/%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n")
        :unnarrowed t)
        ))
    :bind (("C-c n l" . org-roam-buffer-toggle)
           ("C-c n f" . org-roam-node-find)
           ("C-c n i" . org-roam-node-insert)
           :map org-mode-map
           ("C-M-i" . completion-at-point)
           :map org-roam-dailies-map
           ("Y" . org-roam-dailies-capture-yesterday)
           ("T" . org-roam-dailies-capture-tomorrow))
    :bind-keymap
    ("C-c n d" . org-roam-dailies-map))

#+END_SRC

** org-node (alternative to org-roam)
https://github.com/meedstrom/org-node
no sqlite, just plain text and linking with ids
not sure if actually better
#+begin_src emacs-lisp
(use-package org-node
  :ensure t
  :after org
  :config
  (org-node-cache-mode)
  (org-node-backlink-mode)
  (org-node-context-follow-mode)
  (setq org-node-context-collapse-more-than 10000)
  ;; ignore TOOD nodes, nodes with tag 'calls' and ROAM_EXCLUDE
  (setq org-node-filter-fn
        (lambda (node)
          (not
           (or (org-node-get-todo node)
               (member "calls" (org-node-get-tags node))
               (member "op" (org-node-get-tags node))
               (assoc "ROAM_EXCLUDE" (org-node-get-properties node))))))
  )
#+end_src

** org-ql
[[https://github.com/alphapapa/org-ql][org-ql]]
Run queries on org files
#+begin_src emacs-lisp
(use-package org-ql
  :ensure t
  :bind (:map org-agenda-keymap
              ("<tab>" . org-agenda-show-and-scroll-up)
              ("TAB" . org-agenda-show-and-scroll-up))
  :custom
  (org-directory MY--PATH_ORG_FILES)
  (org-ql-search-directories-files-recursive t)  ;;might hit performance but will search recursively in org-directory
  )
#+end_src

** org-sidebar
https://github.com/alphapapa/org-sidebar
#+begin_src emacs-lisp
(use-package org-sidebar
  :ensure t
  :bind (:map org-sidebar-map
              ("<tab>" . org-agenda-show-and-scroll-up)
              ("TAB" . org-agenda-show-and-scroll-up)))
#+end_src

** TODO Verzeichnis außerhalb roam zum Archivieren (u.a. für erledigte Monatsmeldungen etc.)
* Programming

** Magit / Git
Little crash course in magit:
- magit-init to init a git project
- magit-status (C-x g) to call the status window
In status buffer:
- s   stage files
- u   unstage files
- U   unstage all files
- a   apply changes to staging
- c c commit (type commit message, then C-c C-c to commit)
- b b switch to another branch
- P u git push
- F u git pull

#+BEGIN_SRC emacs-lisp
;; updated version needed for magit, at least on windows
(use-package transient
  :ensure t)

(use-package magit
  :ensure t
 ; :pin melpa-stable
  :defer t
  :init
                                        ; set git-path in work environment
  (if (string-equal user-login-name "POH")
      (setq magit-git-executable "P:/Tools/Git/bin/git.exe")
    )
  :bind (("C-x g" . magit-status)))
#+END_SRC
** COMMENT Eglot (can't do dap-mode, maybe dape?)
for python pyls (in env: pip install python-language-server) seems to work better than pyright (npm install -g pyright),
at least pandas couldnt be resolved in pyright
#+begin_src emacs-lisp
(use-package eglot
  :ensure t
  :init
  (setq completion-category-overrides '((eglot (styles orderless))))
  :config
  (add-to-list 'eglot-server-programs '(python-mode . ("pyright-langserver" "--stdio")))
  (with-eval-after-load 'eglot
    (load-library "project"))
  :hook
  (python-mode . eglot-ensure)
  :custom
  (eglot-ignored-server-capabilities '(:documentHighlightProvider))
  (eglot-autoshutdown t)
  (eglot-events-buffer-size 0)
  )

;; performance stuff if necessary
;(fset #'jsonrpc--log-event #'ignore)

#+end_src
** LSP-Mode
#+begin_src emacs-lisp
(defun corfu-lsp-setup ()
  (setf (alist-get 'styles (alist-get 'lsp-capf completion-category-defaults))
        '(orderless)))

(use-package lsp-mode
  :ensure t
;  :hook
;  ((python-mode . lsp))
  :custom
  (lsp-completion-provider :none)
  (lsp-enable-suggest-server-download nil)
  :hook
  (lsp-completion-mode #'corfu-lsp-setup))

;(use-package lsp-ui
;  :ensure t
;  :commands lsp-ui-mode)

(use-package lsp-pyright
  :ensure t
  :after (python lsp-mode)
  :custom
  (lsp-pyright-multi-root nil)
  :hook
  (python-mode-hook . (lambda ()
                        (require 'lsp-pyright) (lsp))))
#+end_src
** flymake
python in venv: pip install pyflake (or ruff?)
TODO: if ruff active, sideline stops working
#+begin_src emacs-lisp
(setq python-flymake-command '("ruff" "--quiet" "--stdin-filename=stdin" "-"))
#+end_src
** sideline
show flymake errors on the right of code window
#+begin_src emacs-lisp
(use-package sideline
  :ensure t)

(use-package sideline-flymake
  :ensure t
  :requires sideline
  :hook
  (flymake-mode . sideline-mode)
  :init
  (setq sideline-flymake-display-mode 'line ; 'point or 'line
;        sideline-backends-left '(sideline-lsp)
        sideline-backends-right '(sideline-flymake)))
#+end_src

** yasnippet
For useful snippet either install yasnippet-snippets or get them from here
[[https://github.com/AndreaCrotti/yasnippet-snippets][Github]]

#+begin_src emacs-lisp
(use-package yasnippet
  :ensure t
  :defer t
  :diminish yas-minor-mode
  :config
  (setq yas-snippet-dirs (list (concat MY--PATH_USER_GLOBAL "snippets")))
  (yas-global-mode t)
  (yas-reload-all)
  (unbind-key "TAB" yas-minor-mode-map)
  (unbind-key "<tab>" yas-minor-mode-map))
#+end_src

** hippie expand
With hippie expand I am able to use yasnippet and emmet at the same time with the same key.
#+begin_src emacs-lisp
(use-package hippie-exp
  :ensure nil
  :defer t
  :bind
  ("C-<return>" . hippie-expand)
  :config
  (setq hippie-expand-try-functions-list
        '(yas-hippie-try-expand emmet-expand-line)))
#+end_src

** COMMENT flycheck (now flymake)
#+BEGIN_SRC emacs-lisp
(use-package flycheck
  :ensure t
  :hook
  ((css-mode . flycheck-mode)
   (emacs-lisp-mode . flycheck-mode)
   (python-mode . flycheck-mode))
  :defer 1.0
  :init
  (setq flycheck-emacs-lisp-load-path 'inherit)
  :config
  (setq-default
   flycheck-check-synta-automatically '(save mode-enabled)
   flycheck-disable-checkers '(emacs-lisp-checkdoc)
   eldoc-idle-delay .1                ;; let eldoc echo faster than flycheck
   flycheck-display-errors-delay .3)) ;; this way any errors will override eldoc messages
#+END_SRC

** smartparens
#+BEGIN_SRC emacs-lisp
(use-package smartparens
  :ensure t
  :diminish smartparens-mode
  :bind
  (:map smartparens-mode-map
        ("C-M-f"           . sp-forward-sexp)
        ("C-M-b"           . sp-backward-sexp)
        ("C-M-a"           . sp-backward-down-sexp)
        ("C-M-e"           . sp-up-sexp)
        ("C-M-w"           . sp-copy-sexp)
        ("M-k"             . sp-kill-sexp)
        ("C-M-<backspace>" . sp-slice-sexp-killing-backward)
        ("C-S-<backspace>" . sp-slice-sexp-killing-around)
        ("C-]"             . sp-select-next-thing-exchange))
  :config
  (setq sp-show-pair-from-inside nil
        sp-escape-quotes-after-insert nil)
  (require 'smartparens-config))
  
#+END_SRC

** lisp
#+BEGIN_SRC emacs-lisp
(use-package elisp-mode
  :ensure nil
  :defer t)
#+END_SRC

** web
apt install npm
sudo npm install -g vscode-html-languageserver-bin
evtl alternativ typescript-language-server?

Unter Windows:
Hier runterladen: https://nodejs.org/dist/latest/
und in ein Verzeichnis entpacken.
Optional: PATH erweitern unter Windows (so kann exec-path-from-shell den Pfad ermitteln):
PATH=P:\path\to\node;%path%
*** web-mode
 #+BEGIN_SRC emacs-lisp
(use-package web-mode
  :ensure t
  :defer t
  :mode
  ("\\.phtml\\'"
   "\\.tpl\\.php\\'"
   "\\.djhtml\\'"
   "\\.[t]?html?\\'")
  :hook
  (web-mode . smartparens-mode)
  :init
  (if *work_remote*
      (setq exec-path (append exec-path '("P:/Tools/node"))))
  :config
  (setq web-mode-enable-auto-closing t
        web-mode-enable-auto-pairing t))
 #+END_SRC

Emmet offers snippets, similar to yasnippet.
Default completion is C-j
[[https://github.com/smihica/emmet-mode#usage][Github]]

#+begin_src emacs-lisp
(use-package emmet-mode
  :ensure t
  :defer t
  :hook
  ((web-mode . emmet-mode)
   (css-mode . emmet-mode))
  :config
  (unbind-key "C-<return>" emmet-mode-keymap))
#+end_src

*** JavaScript

npm install -g typescript-language-server typescript
maybe only typescript?
npm install -g prettier

#+begin_src emacs-lisp
(use-package rjsx-mode
  :ensure t
  :mode ("\\.js\\'"
         "\\.jsx'"))
;  :config
;  (setq js2-mode-show-parse-errors nil
;        js2-mode-show-strict-warnings nil
;        js2-basic-offset 2
;        js-indent-level 2)
;  (setq-local flycheck-disabled-checkers (cl-union flycheck-disable-checkers
;                                                   '(javascript-jshint))))  ; jshint doesn"t work for JSX

(use-package tide
  :ensure t
  :after (rjsx-mode company flycheck)
;  :hook (rjsx-mode . setup-tide-mode)
  :config
  (defun setup-tide-mode ()
    "Setup function for tide."
    (interactive)
    (tide-setup)
    (flycheck-mode t)
    (setq flycheck-check-synta-automatically '(save mode-enabled))
    (tide-hl-identifier-mode t)))

;; needs npm install -g prettier
(use-package prettier-js
  :ensure t
  :after (rjsx-mode)
  :defer t
  :diminish prettier-js-mode
  :hook ((js2-mode rsjx-mode) . prettier-js-mode))
#+end_src

** YAML
#+begin_src emacs-lisp
(use-package yaml-mode
  :if *sys/linux*
  :ensure t
  :defer t
  :mode ("\\.yml$" . yaml-mode))
#+end_src

** R
#+BEGIN_SRC emacs-lisp
(use-package ess
  :ensure t
  :defer t
  :init
  (if *work_remote*
      (setq exec-path (append exec-path '("P:/Tools/R/bin/x64"))
            org-babel-R-command "P:/Tools/R/bin/x64/R --slave --no-save")))
#+END_SRC

** project.el
#+begin_src emacs-lisp
(use-package project
  :custom
  (project-vc-extra-root-markers '(".project.el" ".project" )))

#+end_src
** Python
Preparations:
- create env:
  m-x shell
  python3 -m venv ./.env
- Install language server in *each* projects venv
  source ./.env/bin/activate
  pip install pyright
- in project root:
  touch .project.el
  echo "((nil . (pyvenv-activate . "/path/to/project/.env")))" >> .dir-locals.el

 für andere language servers
 https://github.com/emacs-lsp/lsp-mode#install-language-server

 TODO if in a project, set venv automatically
 (when-let ((project (project-current))) (project-root project))
 returns project path from project.el
 to recognize a project, either have git or
 place a .project.el file in project root and
 (setq project-vc-extra-root-markers '(".project.el" "..." ))
 #+begin_src emacs-lisp
(use-package python
  :if *sys/linux*
  :delight "π "
  :defer t
  :bind (("M-[" . python-nav-backward-block)
         ("M-]" . python-nav-forward-block))
  :mode
  (("\\.py\\'" . python-mode)))

(use-package pyvenv
;  :if *sys/linux*
  :ensure t
  :defer t
  :after python
  :hook
  (python-mode . pyvenv-mode)
  :custom
  (pyvenv-default-virtual-env-name ".env")
  (pyvenv-mode-line-indicator '(pyvenv-virtual-env-name ("[venv:" pyvenv-virtual-env-name "]"))))


;; formatting to pep8
;; requires pip install black
;(use-package blacken
;  :ensure t)

 #+end_src


 TODO  python mode hook:
 - activate venv
 - activate eglot with proper ls
 - activate tree-sitter?
 - have some fallback if activations fail
   
* beancount
** Installation
#+BEGIN_SRC shell :tangle no
sudo su
cd /opt
python3 -m venv beancount
source ./beancount/bin/activate
pip3 install wheel
pip3 install beancount
sleep 100
echo "shell running!"
deactivate
#+END_SRC

#+begin_src emacs-lisp
(use-package beancount
  :ensure nil
  :if *sys/linux*
  :load-path "user-global/elisp/"
;  :ensure t
  :defer t
  :mode
  ("\\.beancount$" . beancount-mode)
  :hook
  (beancount-mode . my/beancount-company)
  :config
  (defun my/beancount-company ()
    (setq-local completion-at-point-functions #'beancount-completion-at-point))
  (setq beancount-filename-main "/home/marc/archiv/Finanzen/Transaktionen/transactions.beancount"))

#+end_src
+BEGIN_SRC emacs-lisp
(use-package beancount
  :if *sys/linux*
  :load-path "user-global/elisp"
; :ensure t
  :defer t
  :mode
  ("\\.beancount$" . beancount-mode)
;  :hook
;  (beancount-mode . my/beancount-company)
 ; :init
 ; (add-hook 'beancount-mode-hook 'company/beancount-mode-hook)
  :config
  (defun my/beancount-company ()
    (setq-local completion-at-point-functions #'beancount-complete-at-point nil t))
;                (mapcar #'cape-company-to-capf
 ;                       (list #'company-beancount #'company-dabbrev))))
  (defun my--beancount-companyALT ()
    (set (make-local-variable 'company-backends)
         '(company-beancount)))
  (setq beancount-filename-main "/home/marc/Archiv/Finanzen/Transaktionen/transactions.beancount"))
+END_SRC

To support org-babel, check if it can find the symlink to ob-beancount.el

#+BEGIN_SRC shell :tangle no
orgpath=`find /home/marc/.emacs.d/elpa/ -type d -name "org-plus*" -print`
beansym="$orgpath/ob-beancount.el
  bean="/home/marc/Archiv/Programmierprojekte/Lisp/beancount-mode/ob-beancount.el"

  if [ -h "$beansym" ]
  then
    echo "$beansym found"
  elif [ -e "$bean" ]
  then
    echo "creating symlink"
    ln -s "$bean" "$beansym"
  else
    echo "$bean not found, symlink creation aborted"
  fi
#+END_SRC

Fava is strongly recommended.

#+BEGIN_SRC shell  :tangle no
cd /opt
python3 -m venv fava
source ./fava/bin/activate
pip3 install wheel
pip3 install fava
deactivate
#+END_SRC

Start fava with fava my_file.beancount

It is accessable on this URL: [[http://127.0.0.1:5000][Fava]]
Beancount-mode can start fava and open the URL right away. 
* typst
todo
tinymist lsp
tree-sitter
#+begin_src emacs-lisp
(use-package typst-ts-mode
:ensure (:type git :host codeberg :repo "meow_king/typst-ts-mode" :branch "develop")
  :custom
  (typst-ts-watch-options "--open")
  (typst-ts-mode-grammar-location (expand-file-name "tree-sitter/libtree-sitter-typst.so" user-emacs-directory))
  (typst-ts-mode-enable-raw-blocks-highlight t)
  :config
  (keymap-set typst-ts-mode-map "C-c C-c" #'typst-ts-tmenu))

;;workaround until I can use typst tree-sitter
;; https://github.com/Ziqi-Yang/typst-mode.el
(use-package typst-mode
  :ensure (:type git :host github :repo "Ziqi-Yang/typst-mode.el")
  :mode ("\\.typst\\'" . typst-mode)
  :config
  (setq lsp-clients-typst-server-command '("typst" "serve")))

(use-package websocket
  :ensure t)
(use-package typst-preview
  :ensure (:type git :host github :repo "havarddj/typst-preview.el")
  :config
  (setq typst-preview-browser "default"))
  
#+end_src
* Stuff after everything else
Set garbage collector to a smaller value to let it kick in faster.
Maybe a problem on Windows?
#+begin_src emacs-lisp
;(setq gc-cons-threshold (* 2 1000 1000))
#+end_src

Rest of early-init.el
#+begin_src emacs-lisp :tangle early-init.el
(defconst config-org (expand-file-name "config.org" user-emacs-directory))
(defconst init-el (expand-file-name "init.el" user-emacs-directory))

(unless (file-exists-p init-el)
  (require 'org)
  (org-babel-tangle-file config-org init-el))
#+end_src