;;; init.el -- emacs configuration file ;;; Commentary: ;;; none ;;; Code: (require 'package) (with-eval-after-load 'package (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") '("nongnu" . "https://elpa.nongnu.org/nongnu/"))) (package-initialize) (setq package-enable-at-startup t) (require 'use-package) (setq auth-sources '("~/.authinfo.gpg")) (defvar bootstrap-version) (let ((bootstrap-file (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory)) (bootstrap-version 5)) (unless (file-exists-p bootstrap-file) (with-current-buffer (url-retrieve-synchronously "https://raw.githubusercontent.com/raxod502/straight.el/develop/install.el" 'silent 'inhibit-cookies) (goto-char (point-max)) (eval-print-last-sexp))) (load bootstrap-file nil 'nomessage)) ;; helper functions ;; ===== (defun my/format-elisp-buffer () "Format the whole elisp buffer." (interactive) (save-excursion (indent-region (point-min) (point-max)))) (defun my/setup-elisp-formatting-key () "Set up the formatting key for Emacs Lisp buffers." (local-set-key (kbd "C-c f") 'my/format-elisp-buffer)) (add-hook 'emacs-lisp-mode-hook #'my/setup-elisp-formatting-key) (defun my/visit-todo () "Visit TODO.org in home." (interactive) (find-file "/ssh:lkn@alioth.uberspace.de:/home/lkn/TODO.org")) (defun my/reload-init-file () "Reload the Emacs init file." (interactive) (load-file user-init-file)) (defun my/reload-org-config () "Reload the org configuration." (interactive) (my/org-config) (message "Org configuration reloaded")) (defun my-org-agenda-format-item (item) "Format an agenda or to-do ITEM without the filename, but keep the keyword." (let* ((marker (or (get-text-property 0 'org-marker item) (get-text-property 0 'org-hd-marker item))) (line (when marker (org-with-point-at marker (buffer-substring (line-beginning-position) (line-end-position))))) (todo-keyword (when marker (org-with-point-at marker (org-get-todo-state)))) (truncated-line (when line (replace-regexp-in-string "^\\[#[A-Z]\\] " "" line)))) (if truncated-line (let ((clean-item (replace-regexp-in-string "^\\(\\*+ \\)" "" truncated-line))) (if todo-keyword (concat todo-keyword " " (replace-regexp-in-string (concat "^" todo-keyword " ?") "" clean-item)) clean-item)) item))) (defun my/kill-all-org-agenda-buffers () "Close all org-agenda-related buffers." (interactive) (dolist (buffer (buffer-list)) (with-current-buffer buffer (when (or (derived-mode-p 'org-agenda-mode) (and (buffer-file-name) (member (buffer-file-name) (org-agenda-files)))) (kill-buffer buffer)))) (message "Closed all org-agenda-related buffers.")) (defun my/org-config () "Setup personal 'org-mode' configuration." (setq org-agenda-inhibit-startup t org-adapt-indentation nil org-todo-keywords '((sequence "HOLD(h)" "TODO(t)" "WAIT(w)" "|" "DONE(d)" "CNCL(x)")) org-todo-keyword-faces '(("HOLD" . (:foreground "orange" :weight bold)) ("TODO" . (:foreground "red" :weight bold)) ("DONE" . (:foreground "green" :weight bold)) ("CNCL" . (:foreground "gray" :weight bold))) org-agenda-prefix-format '((agenda . "%i %?-12t% s ") (todo . " %i ") (tags . " %i ") (search . " %i ")) org-agenda-hide-tags-regexp "." org-agenda-todo-keyword-format "%s" org-archive-location "%s_archive::datetree/" org-log-done 'note org-agenda-hide-tags-regexp nil org-agenda-tags-column -80 org-log-into-drawer t) (setq org-stuck-projects '("+prj/-maybe-someday-done" ("TODO" "WAIT" "prj") nil "")) (setq org-tags-exclude-from-inheritance '("prj")) (advice-add 'org-agenda-format-item-line :filter-return #'my-org-agenda-format-item) (advice-add 'org-agenda-format-item :filter-return #'my-org-agenda-format-item)) (defun my/org-refile-config () "Setup org-refile targets." (defvar org-refile-targets) (defvar org-refile-use-outline-path) (defvar org-outline-path-complete-in-steps) (defvar org-refile-allow-creating-parent-nodes) (setq org-refile-targets '((nil :maxlevel . 9) ("/ssh:lkn@alioth.uberspace.de:/home/lkn/TODO.org" :maxlevel . 9)) org-refile-use-outline-path 'file org-outline-path-complete-in-steps nil org-refile-allow-creating-parent-nodes 'confirm)) (defun my/org-capture-config () "Setup org-capture." (setq org-capture-templates '(("t" "TODO" entry (file "/ssh:lkn@alioth.uberspace.de:/home/lkn/TODO.org") "* TODO %?\n %i" :prepend t))) (with-eval-after-load 'denote (defvar denote-org-capture-specifiers) (setq denote-org-capture-specifiers "%l\n%i\n%?") (add-to-list 'org-capture-templates '("n" "New note (with denote.el)" plain (file denote-last-path) #'denote-org-capture :no-save t :immediate-finish nil :kill-buffer t :jump-to-captured t))) (advice-add 'org-agenda-format-item-line :filter-return #'my-org-agenda-format-item) (advice-add 'org-agenda-format-item :filter-return #'my-org-agenda-format-item)) (use-package emacs :init (tool-bar-mode -1) (require 'server) (unless (server-running-p) (server-start)) (setq tramp-persistency-file-name "~/.config/emacs/tramp") (setq tramp-inline-compress-start-size 1000) (setq backup-by-copying t completion-cycle-threshold 3 create-lockfiles nil custom-file (concat user-emacs-directory "custom.el") delete-old-versions t enable-recursive-minibuffers t focus-follows-mouse t indent-tabs-mode nil inhibit-startup-screen t kept-new-versions 20 kept-old-versions 2 minibuffer-prompt-properties '(read-only t cursor-intangible t face minibuffer-prompt) mouse-autoselect-window t read-extended-command-predicate #'command-completion-default-include-p read-process-output-max (* 1024 1024) tab-always-indent 'complete tab-width 4 text-mode-ispell-word-completion nil use-short-answers t version-control t warning-minimum-level :emergency backup-directory-alist `((".*" . ,(concat user-emacs-directory "backup"))) read-extended-command-predicate #'command-completion-default-include-p text-mode-ispell-word-completion nil gc-cons-threshold 100000000) (defun crm-indicator (args) (cons (format "[CRM%s] %s" (replace-regexp-in-string "\\`\\[.*?]\\*\\|\\[.*?]\\*\\'" "" crm-separator) (car args)) (cdr args))) (advice-add #'completing-read-multiple :filter-args #'crm-indicator) ;; Do not allow the cursor in the minibuffer prompt (setq minibuffer-prompt-properties '(read-only t cursor-intangible t face minibuffer-prompt)) (add-hook 'minibuffer-setup-hook #'cursor-intangible-mode) (load-theme 'misterioso) (put 'erase-buffer 'disabled nil) (put 'narrow-to-region 'disabled nil) (put 'narrow-to-defun 'disabled nil) (put 'narrow-to-page 'disabled nil) (put 'widen 'disabled nil) (add-hook 'minibuffer-setup-hook #'cursor-intangible-mode) (with-eval-after-load 'ibuffer (define-key ibuffer-mode-map (kbd "TAB") nil)) (global-unset-key (kbd "C-x n")) (global-set-key (kbd "") 'dired-sidebar-toggle-sidebar) (global-set-key (kbd "M-/") 'dabbrev-expand) (global-set-key (kbd "C-=") 'multi-vterm-dedicated-toggle) (global-set-key (kbd "M-s r") 'replace-regexp) (global-set-key (kbd "M-s M-r") 'replace-string) (global-set-key (kbd "C-x c O") 'my/reload-org-config) (global-set-key (kbd "C-x c r") #'my/reload-init-file) (global-set-key (kbd "C-x E n p") #'narrow-to-page) (global-set-key (kbd "C-x E n f") #'narrow-to-defun) (global-set-key (kbd "C-x E n r") #'narrow-to-region) (global-set-key (kbd "C-x E w") #'widen) (when (file-exists-p custom-file) (load custom-file))) (use-package async :ensure t) (use-package async-status :ensure t) (use-package dash :ensure t :defer t) (use-package multiple-cursors :ensure t :defer t :init (multiple-cursors-mode 1) :bind (("C-x C-<" . 'mc/edit-lines) ("C->" . 'mc/mark-next-like-this) ("C-<" . 'mc/mark-previous-like-this) ("C-x C->" . 'mc/mark-all-like-this))) ;; minibuffer ;; === (use-package which-key :ensure t :defer t :init (which-key-mode)) ;; eye candy ;; === (use-package doom-modeline :ensure t :init (doom-modeline-mode 1)) (use-package nerd-icons :ensure t :defer t) (use-package nerd-icons-dired :ensure t :defer t :hook (dired-mode . nerd-icons-dired-mode)) (use-package nerd-icons-completion :ensure t :defer t) (use-package nerd-icons-ibuffer :ensure t :defer t) (use-package unicode-fonts :ensure t :defer t) ;; org-mode ;; === (use-package org :ensure t :bind (("C-x O a" . org-agenda) ("C-x O c" . org-capture) ("C-x O t" . my/visit-todo) ("C-x O k" . my/kill-all-org-agenda-buffers)) :mode ("\\.org\\'" . org-mode) :config (my/org-config) (my/org-refile-config) (my/org-capture-config) (defun my/add-remote-todo-file () "Add remote TODO.org file to org-agenda-files." (interactive) (unless org-agenda-files (setq org-agenda-files (list "/ssh:lkn@alioth.uberspace.de:/home/lkn/TODO.org")))) (advice-add 'org-agenda :before #'my/add-remote-todo-file) (org-babel-do-load-languages 'org-babel-load-languages '((shell . t)))) ;; knowledge management ;; === (use-package denote :ensure t :commands (my/search-denote-notes) :bind (("C-x n f" . #'denote-open-or-create) ("C-x n n" . #'my/denote-create-note) ("C-x n d s" . #'my/denote-switch-directory) ("C-x n d d" . #'my/denote-echo-current-directory) ("C-x n c" . #'denote-region) ("C-x n !" . #'denote-date) ("C-x n z" . #'denote-signature) ("C-x n s" . #'denote-subdirectory) ("C-x n S-n" . #'denote-type) ("C-x n t" . #'denote-template) ("C-x n l l" . #'denote-link) ("C-x n l a" . #'denote-add-links) ("C-x n l f" . #'denote-find-link) ("C-x n b l" . #'denote-backlinks) ("C-x n b f" . #'denote-find-backlink) ("C-x n /" . #'my/search-denote-notes) ("C-x n r r" . #'denote-rename-file) ("C-x n r f" . #'denote-rename-file-using-front-matter)) :config (defvar my/denote-local-notes-directory "~/notes/") (defvar my/denote-remote-notes-directory "/ssh:lkn@alioth.uberspace.de:/home/lkn/notes/") (setq denote-directory my/denote-local-notes-directory) (defun my/denote-echo-current-directory () "Echo the current value of denote-directory." (interactive) (message "Current Denote directory: %s" denote-directory)) (defun my/denote-directory-segment () "Return the current Denote directory for the mode line." (when (bound-and-true-p denote-directory) (format " Denote:%s " (file-name-nondirectory (directory-file-name denote-directory))))) (defun my/denote-switch-directory () "Switch current denote working directory." (interactive) (let ((new-dir (if (string= denote-directory my/denote-local-notes-directory) my/denote-remote-notes-directory my/denote-local-notes-directory))) (setq denote-directory new-dir) (message "Denote directory switched to %s" new-dir))) (defun my/denote-use-both-directories (orig-fun &rest args) "Temporarily use both directories for a given function." (let ((original-dir denote-directory) (result nil)) (setq denote-directory my/denote-local-notes-directory) (setq result (apply orig-fun args)) (setq denote-directory my/denote-remote-notes-directory) (setq result (append result (apply orig-fun args))) (setq denote-directory original-dir) result)) (defun my/denote-create-note () "Create a note in directory of choice." (interactive) (let* ((dirs (list my/denote-local-notes-directory my/denote-remote-notes-directory)) (chosen-dir (completing-read "Choose directory: " dirs))) (let ((denote-directory chosen-dir)) (call-interactively #'denote)))) (advice-add 'denote-directory-files :around #'my/denote-use-both-directories) (advice-add 'denote-directory-subdirectories :around #'my/denote-use-both-directories) (defun my/search-denote-notes () "Use ripgrep to search denote notes." (interactive) (let ((default-directory denote-directory)) (consult-ripgrep))) (let ((map dired-mode-map)) (define-key map (kbd "C-x n d") #'denote-dired-mode) (define-key map (kbd "C-x n i") #'denote-link-dired-marked-notes) (define-key map (kbd "C-x n k") #'denote-dired-rename-marked-files-with-keywords) (define-key map (kbd "C-x n r r") #'denote-dired-rename-files) (define-key map (kbd "C-x n r f") #'denote-dired-rename-marked-files-using-front-matter)) ;; show current denote directory in modeline (with-eval-after-load 'doom-modeline (doom-modeline-def-segment denote-directory (my-denote-directory-segment)) (setq doom-modeline-format-left (append doom-modeline-format-left '(denote-directory)))) (setq denote-save-buffers nil denote-known-keywords '("emacs" "golang" "dev") denote-infer-keywords t denote-sort-keywords t denote-file-type nil denote-history-completion-in-prompts nil denote-prompts '(title keywords) denote-excluded-directories-regexp nil denote-excluded-keywords-regexp nil denote-rename-confirmations '(rewrite-front-matter modify-file-name) denote-date-prompt-use-org-read-date t denote-date-format nil denote-backlinks-show-context t) (denote-rename-buffer-mode 1)) ;; version control ;; === (use-package magit :ensure t :defer t :commands (magit-status-here magit-get-current-branch magit-get-upstream-branch magit-read-branch magit-git-string magit-reset-to-merge-base magit-reset-to-upstream) :custom (magit-display-buffer-function #'magit-display-buffer-same-window-except-diff-v1) :bind (("C-x g g" . #'magit-status) ("C-x g r b" . #'magit-reset-to-merge-base) ("C-x g r u" . #'magit-reset-to-upstream) ("C-x g l l" . #'magit-log) ("C-x g l a" . #'magit-log-all) ("C-x g l f" . #'magit-log-buffer-file) ("C-x g R s" . #'magit-reset-soft) ("C-x g R h" . #'magit-reset-hard) ("C-x g !" . #'magit-run) ("C-x g b b" . #'magit-branch) ("C-x g b c" . #'magit-branch-create) ("C-x g b s" . #'magit-branch-checkout) ("C-x g m m" . #'magit-merge) ("C-x g d d" . #'magit-diff)) :config (defun magit-reset-to-merge-base () "Reset to merge-base of two branches using Magit's branch selection." (interactive) (require 'magit) (let* ((branch1 (magit-read-branch "Select first branch: ")) (branch2 (magit-read-branch "Select second branch: ")) (merge-base (magit-git-string "merge-base" branch1 branch2))) (if merge-base (progn (magit-reset-internal "--soft" merge-base) (message "Reset to merge-base of %s and %s" branch1 branch2)) (user-error "No merge-base found between %s and %s" branch1 branch2)))) (defun magit-reset-to-upstream () "Reset to upstream of the current branch using Magit." (interactive) (require 'magit) (let ((current-branch (magit-get-current-branch))) (if current-branch (let ((upstream (magit-get-upstream-branch current-branch))) (if upstream (progn (magit-reset-internal "--soft" "@{u}") (message "Reset to upstream of %s (%s)" current-branch upstream)) (user-error "No upstream branch set for %s" current-branch))) (user-error "Not on a branch"))))) ;; diagramming ;; === (use-package graphviz-dot-mode :ensure t :defer t :config (with-eval-after-load 'org (org-babel-do-load-languages 'org-babel-load-languages '((dot . t))))) (use-package plantuml-mode :ensure t :custom (plantuml-default-exec-mode 'jar) (plantuml-jar-path "/usr/share/plantuml/plantuml.jar") (org-plantuml-jar-path (expand-file-name "/usr/share/plantuml/plantuml.jar")) :config (with-eval-after-load 'org (org-babel-do-load-languages 'org-babel-load-languages '((plantuml . t))))) ;; coding ;; === (use-package go-mode :ensure t :defer t :mode (("\\.go$" . go-mode))) (use-package yaml-mode :ensure t :defer t :mode (("\\.yml$" . yaml-mode))) (use-package json-mode :ensure t :defer t :mode (("\\.json$" . json-mode))) (use-package php-mode :ensure t :defer t :mode (("\\.php$" . php-mode))) (use-package python-mode :ensure t :defer t :mode (("BUILD\\'" . python-mode) ;; please BUILD config ("\\.py$" . python-mode))) (use-package mmm-mode :ensure t :defer t) (use-package multi-web-mode :ensure t :defer t) (use-package protobuf-mode :ensure t :defer t) (use-package rego-mode :ensure t :defer t) (use-package kotlin-mode :ensure t :defer t) (use-package fic-mode :ensure t :defer t) (use-package flycheck :ensure t :defer t :init (global-flycheck-mode)) ;; navigation ;; === (use-package avy :ensure t :bind (("C-x j j" . avy-goto-char) ("C-x j c" . avy-goto-char-2) ("C-x j l" . avy-goto-line) ("C-x j w" . avy-goto-word-1) ("C-x j ." . avy-resume)) :config (avy-setup-default) (setq avy-background t)) ;; api testing ;; === (use-package plz :ensure t) ;; force recompiling init.el ;; === (defun my-compile-init-file () "Compile the init file." (let ((init-file (file-truename user-init-file))) (when (string= (buffer-file-name) init-file) (byte-compile-file init-file)))) (add-hook 'after-save-hook #'my-compile-init-file) (use-package orderless :ensure t :custom (completion-styles '(orderless basic)) (completion-category-overrides '((file (styles . (partial-completion))))) (orderless-matching-styles '(orderless-flex orderless-prefixes orderless-initialism)) :config (setq orderless-component-separator #'orderless-escapable-split-on-space)) ;; better minibuffer (use-package vertico :ensure t :init (vertico-mode) :custom (vertico-cycle t) (vertico-count 13) :config (setq read-file-name-completion-ignore-case t read-buffer-completion-ignore-case t completion-ignore-case t completion-styles '(orderless basic) completion-category-defaults nil completion-category-overrides '((file (styles partial-completion))))) ;; it's like ace-jump in a vertico minibuffer (use-package vertico-quick :after vertico :bind (:map vertico-map ("C-q" . vertico-quick-insert) ("M-q" . vertico-quick-exit))) ;; save history (use-package savehist :ensure t :init (savehist-mode)) ;; inline completion (use-package corfu :ensure t :demand t :bind ("C-." . completion-at-point) :init (global-corfu-mode) :hook (eglot-managed-mode . corfu-mode) :config ;; Enable Orderless for Corfu (setq completion-styles '(orderless basic) completion-category-defaults nil completion-at-point-functions nil completion-category-overrides '((file (styles . (partial-completion))))) (setq corfu-auto t) ;; Enable auto completion (setq corfu-separator ?\s) ;; Orderless field separator (setq corfu-quit-at-boundary nil) ;; Never quit at completion boundary (setq corfu-quit-no-match nil) ;; Never quit, even if there is no match (setq corfu-preview-current nil) ;; Disable current candidate preview (setq corfu-on-exact-match nil) ;; Configure handling of exact matches (setq corfu-echo-documentation nil) ;; Disable documentation in the echo area (setq corfu-scroll-margin 5) ;; Use scroll margin ;; Set up dabbrev as the primary completion source ;; (setq-local completion-at-point-functions '(dabbrev-completion)) ;; Enable kind-icon (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)) ;; Add Cape for additional completion sources (use-package cape :ensure t :init ;; Add `completion-at-point-functions', used by `completion-at-point'. (add-to-list 'completion-at-point-functions #'cape-file) (add-to-list 'completion-at-point-functions #'cape-elisp-block) (add-to-list 'completion-at-point-functions #'cape-dabbrev) ;;(add-to-list 'completion-at-point-functions #'cape-history) ;;(add-to-list 'completion-at-point-functions #'cape-keyword) ;;(add-to-list 'completion-at-point-functions #'cape-tex) ;;(add-to-list 'completion-at-point-functions #'cape-sgml) ;;(add-to-list 'completion-at-point-functions #'cape-rfc1345) ;;(add-to-list 'completion-at-point-functions #'cape-abbrev) ;;(add-to-list 'completion-at-point-functions #'cape-dict) ;;(add-to-list 'completion-at-point-functions #'cape-symbol) ;;(add-to-list 'completion-at-point-functions #'cape-line) )) ;; better search (use-package consult :ensure t :demand t :bind (("C-s" . consult-line) ("C-r" . consult-line-multi) ("C-c h" . consult-history) ("C-c m" . consult-mode-command) ("C-c k" . consult-kmacro) ;; Add keybindings for ripgrep-related commands ("C-c s" . consult-ripgrep) ("C-c C-s" . consult-ripgrep-multi)) :custom (consult-preview-key nil) ; Disable preview :config ;; Configure ripgrep for consult (setq consult-ripgrep-command "rg --null --line-buffered --color=ansi --max-columns=1000 --no-heading --line-number . -e ARG OPTS") ;; Use ripgrep for `consult-grep` as well (setq consult-grep-command "rg --null --line-buffered --color=ansi --max-columns=1000 --no-heading --line-number . -e ARG OPTS") ;; Configure mode-specific histories (setq consult-mode-histories '((comint-mode . comint-input-ring) (eshell-mode . eshell-history-ring) (geiser-repl-mode . geiser-repl--history) (inferior-python-mode . comint-input-ring) (minibuffer-local-map . consult-history))) (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-recent-file consult--source-project-recent-file :preview-key '(:debounce 0.4 any)) ;; Configure async search for better performance (setq consult-async-min-input 2) (setq consult-async-refresh-delay 0.15) (setq consult-async-input-throttle 0.2) (setq consult-async-input-debounce 0.1) ;; Use Consult to select xref locations with preview (setq xref-show-xrefs-function #'consult-xref xref-show-definitions-function #'consult-xref)) (use-package projectile :ensure t :config (projectile-mode +1) :bind-keymap ("C-x p" . projectile-command-map) :bind (:map projectile-command-map ("f" . consult-projectile-find-file) ("d" . consult-projectile-find-dir) ("b" . consult-projectile-switch-to-buffer) ("s" . consult-projectile-switch-project) ("p" . consult-projectile) ("/" . consult-ripgrep))) (use-package consult-projectile :ensure t :after (consult projectile) :config (advice-add 'projectile-find-file :override #'consult-projectile-find-file) (advice-add 'projectile-find-dir :override #'consult-projectile-find-dir) (advice-add 'projectile-switch-to-buffer :override #'consult-projectile-switch-to-buffer) (advice-add 'projectile-switch-project :override #'consult-projectile-switch-project) ;; Custom function to always use consult-projectile-find-file (defun my-consult-projectile-find-file () "Use consult-projectile-find-file, falling back to consult-projectile if not in a project." (interactive) (if (projectile-project-p) (consult-projectile-find-file) (consult-projectile))) ;; Replace the keybinding in projectile-command-map (define-key projectile-command-map (kbd "f") #'my-consult-projectile-find-file)) ;; util ;; ===== (use-package elfeed :ensure t :defer t) (use-package eshell :ensure t :defer t :bind (("C-" . #'eshell-toggle)) :config (use-package eshell-autojump :ensure t :defer t) (use-package eshell-bookmark :ensure t :defer t) (use-package eshell-git-prompt :ensure t :defer t) (use-package eshell-toggle :ensure t :defer t)) (use-package dired-sidebar :ensure t :defer t) (use-package surround :ensure t :defer t :bind-keymap ("M-'" . surround-keymap)) (use-package wrap-region :ensure t :defer t) (use-package eat :ensure t :defer t) (use-package scratch :ensure t :defer t :bind (("C-;" . scratch))) (defun my-set-scratch-mode () (funcall 'text-mode)) (add-hook 'scratch-create-buffer-hook 'my-set-scratch-mode) (use-package go-playground :ensure t :defer t) (use-package persistent-scratch :ensure t :init (setq persistent-scratch-directory "~/.local/state/emacs/") (unless (file-exists-p persistent-scratch-directory) (make-directory persistent-scratch-directory t)) :config (persistent-scratch-setup-default) :custom (persistent-scratch-autosave-interval 30) (persistent-scratch-save-file (expand-file-name "scratch" persistent-scratch-directory)) (persistent-scratch-what-to-save '(((:current . "*scratch*") . (major-mode point narrowing)))) :hook (after-init . persistent-scratch-autosave-mode) :bind (("C-c s" . persistent-scratch-save) ("C-c r" . persistent-scratch-restore))) (defun my/dired-duplicate-file (arg) "Duplicate the current file in Dired." (interactive "p") (let ((filename (dired-get-filename))) (setq target (concat (file-name-sans-extension filename) "-old" (if (> arg 1) (number-to-string arg)) (file-name-extension filename t))) (if (file-directory-p filename) (copy-file filename target)) ) ) (define-key dired-mode-map (kbd "C-c d") 'my/dired-duplicate-file) (add-to-list 'auto-mode-alist '("BUILD\\'" . python-mode)) (use-package org-journal :ensure t :defer t :config (setq org-journal-dir "~/daily/") (setq org-journal-date-format "%A, %d %B %Y") (setq org-journal-file-format "%Y%m%d.org") (setq org-journal-enable-agenda-integration t)) (global-set-key (kbd "C-x O j") 'org-journal-new-entry) (defun copy-file-path-to-clipboard () "Copy the current buffer's file path to the clipboard." (interactive) (let ((filepath (buffer-file-name))) (when filepath (kill-new filepath) (message "Copied file path: %s" filepath)))) (global-set-key (kbd "C-x y b p") 'copy-file-path-to-clipboard) (add-hook 'before-save-hook #'gofmt-before-save) (use-package ellama :ensure t :defer t :init (setopt ellama-keymap-prefix "C-c e") (setopt ellama-language "English") (require 'llm-ollama) (setopt ellama-provider (make-llm-ollama :chat-model "codellama:latest" :embedding-model "nomic-embed-text" :default-chat-non-standard-params '(("num_ctx" . 8192))))) (use-package lmtf :ensure t :defer t :straight (lmtf :type git :host github :repo "lneely/lmtf") :bind (:map emacs-lisp-mode-map ("C-c t !" . lmtf-run-all-tests) ("C-c t r" . lmtf-show-results))) (defun compile-init-on-save () "Automatically compile init.el when saving it." (when (equal buffer-file-name (expand-file-name "~/.emacs.d/init.el")) (byte-compile-file buffer-file-name))) (add-hook 'after-save-hook #'compile-init-on-save) (use-package claude-shell :straight (claude-shell :type git :host github :repo "arminfriedl/claude-shell") :config (setq claude-shell-api-token (getenv "CLAUDE_API_KEY"))) (use-package websocket :ensure t) (use-package org-ai :load-path (lambda () "~/.config/emacs/elisp/org-ai") :commands (org-ai-mode org-ai-global-mode org-ai-install-yasnippets) :init (add-hook 'org-mode-hook #'org-ai-mode) (org-ai-global-mode) (org-ai-install-yasnippets) :config (setq org-ai-service 'anthropic) (setq org-ai-use-auth-source t) (setq org-ai-default-chat-model "claude-3-5-sonnet-20240620")) (use-package yasnippet :ensure t :config (setq yas-snippet-dirs '("~/.config/emacs/snippets/")) (yas-global-mode 1) :bind ("C-x y i" . yas-insert-snippet) ("C-x y n" . yas-new-snippet) ("C-x y v" . yas-visit-snippet-file)) (use-package yasnippet-snippets :ensure t :after yasnippet) (use-package wgrep :ensure t) ;; web browsing ;; === (require 'eww) (setq shr-max-image-proportion 0.7) (setq shr-image-animate nil) (setq browse-url-browser-function 'eww-browse-url) ;; for pages that require JS for some stupid reason... (defun browse-url-xdg-open (url &optional _new-window) "Open URL using the default browser via `xdg-open'." (interactive (browse-url-interactive-arg "URL: ")) (call-process "xdg-open" nil 0 nil url)) (defun eww-open-in-default-browser () "Open the current EWW page in the default system browser." (interactive) (browse-url-xdg-open (eww-current-url))) (define-key eww-mode-map (kbd "C-c o") 'eww-open-in-default-browser) (add-hook 'prog-mode-hook #'yas-minor-mode) (add-hook 'org-mode-hook #'yas-minor-mode) (use-package emmet-mode :ensure t :defer t) (use-package embark :ensure t :bind (("C-." . embark-act) ;; pick some comfortable binding ("C-;" . embark-dwim) ;; good alternative: M-. ("C-h B" . embark-bindings)) ;; alternative for `describe-bindings' :init ;; Optionally replace the key help with a completing-read interface (setq prefix-help-command #'embark-prefix-help-command) :config ;; Hide the mode line of the Embark live/completions buffers (add-to-list 'display-buffer-alist '("\\`\\*Embark Collect \\(Live\\|Completions\\)\\*" nil (window-parameters (mode-line-format . none))))) (provide 'init) ;; Local Variables: ;; flycheck-disabled-checkers: (emacs-lisp-checkdoc emacs-lisp) ;; End: ;;; init.el ends here