syscfg/emacs/init.el

829 lines
27 KiB
EmacsLisp

;;; 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)" "|"
"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)
(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 "<f12>") '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" . #'denote)
("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
(defun my/search-denote-notes ()
"Use ripgrep to search denote notes."
(interactive)
(let ((default-directory "/ssh:lkn@alioth.uberspace.de:/home/lkn/notes/"))
(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))
(setq denote-directory (expand-file-name "/ssh:lkn@alioth.uberspace.de:/home/lkn/notes/")
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
;; Override projectile functions with consult-projectile functions
(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-<escape>" . #'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)
(provide 'init)
;;; init.el ends here