diff --git a/emacs/custom.el b/emacs/custom.el index b8518da..cb1e786 100644 --- a/emacs/custom.el +++ b/emacs/custom.el @@ -34,13 +34,9 @@ ;; If you edit it by hand, you could mess it up, so be careful. ;; Your init file should contain only one such instance. ;; If there is more than one, they won't work right. - '(custom-safe-themes - '("9c22d1654fbfed0285a519541b5ea76462d9b03d778d1dc34779fd38613bf7c8" "9fb561389e5ac5b9ead13a24fb4c2a3544910f67f12cfcfe77b75f36248017d0" "88f7ee5594021c60a4a6a1c275614103de8c1435d6d08cc58882f920e0cec65e" "aec7b55f2a13307a55517fdf08438863d694550565dee23181d2ebd973ebd6b8" "014cb63097fc7dbda3edf53eb09802237961cbb4c9e9abd705f23b86511b0a69" "81f53ee9ddd3f8559f94c127c9327d578e264c574cda7c6d9daddaec226f87bb" "3770d0ae70172461ee0a02edcff71b7d480dc54066e8960d8de9367d12171efb" "f64189544da6f16bab285747d04a92bd57c7e7813d8c24c30f382f087d460a33" "f079ef5189f9738cf5a2b4507bcaf83138ad22d9c9e32a537d61c9aae25502ef" "835d934a930142d408a50b27ed371ba3a9c5a30286297743b0d488e94b225c5f" default)) - '(elfeed-feeds '("https://hnrss.org/newest")) '(mc/always-run-for-all t) - '(org-agenda-files nil) - '(package-selected-packages - '(org-journal plan9-theme parchment-theme acme-theme wrap-region surround go-playground dired-sidebar consult persistent-scratch scratch cape kind-icon corfu eat eshell-toggle eshell-git-prompt eshell-bookmark eshell-autojump eshell-atuin elfeed vertico orderless plz ace-mc ace-jump-mode flycheck fic-mode kotlin-mode rego-mode protobuf-mode multi-web-mode mmm-mode python-mode php-mode json-mode yaml-mode go-mode plantuml-mode graphviz-dot-mode magit denote unicode-fonts nerd-icons-ibuffer nerd-icons-completion nerd-icons-dired doom-modeline which-key multiple-cursors dash async-status async consult-projectile)) + '(safe-local-variable-values + '((flycheck-disabled-checkers emacs-lisp-checkdoc emacs-lisp))) '(tab-width 2)) (custom-set-faces ;; custom-set-faces was added by Custom. diff --git a/emacs/feature/feature-alioth.el b/emacs/feature/feature-alioth.el new file mode 100644 index 0000000..fe2d779 --- /dev/null +++ b/emacs/feature/feature-alioth.el @@ -0,0 +1,32 @@ +(defun ensure-alioth-mounted () + "Check if ~/alioth is mounted, and if not, mount it." + (unless (file-exists-p "~/alioth/.exists") + (mount-alioth-sshfs))) + +(defun umount-alioth-sshfs () + "Unmount Alioth Uberspace using SSHFS." + (interactive) + (let ((default-directory "~")) + (shell-command "umount ~/alioth"))) + +(defun mount-alioth-sshfs () + "Mount Alioth Uberspace using SSHFS." + (interactive) + (let ((default-directory "~")) + (shell-command "sshfs lkn@alioth.uberspace.de:/home/lkn/ ~/alioth"))) + +(add-hook 'find-file-hook + (lambda () + (when (string-prefix-p (expand-file-name "~/alioth/") + (expand-file-name (buffer-file-name))) + (ensure-alioth-mounted)))) + +(add-hook 'dired-mode-hook + (lambda () + (when (string-prefix-p (expand-file-name "~/alioth/") + (expand-file-name default-directory)) + (ensure-alioth-mounted)))) + + + +(provide 'feature-alioth) diff --git a/emacs/feature/feature-apitest.el b/emacs/feature/feature-apitest.el new file mode 100644 index 0000000..9ab0d8a --- /dev/null +++ b/emacs/feature/feature-apitest.el @@ -0,0 +1,5 @@ +(require 'feature-pkg) + +(pkg/require 'plz) + +(provide 'feature-apitest) diff --git a/emacs/feature/feature-async.el b/emacs/feature/feature-async.el new file mode 100644 index 0000000..56431be --- /dev/null +++ b/emacs/feature/feature-async.el @@ -0,0 +1,4 @@ +(pkg/require 'async) +(pkg/require 'async-status) + +(provide 'feature-async) diff --git a/emacs/feature/feature-automount.el b/emacs/feature/feature-automount.el new file mode 100644 index 0000000..b26e00f --- /dev/null +++ b/emacs/feature/feature-automount.el @@ -0,0 +1,18 @@ +(defun ensure-alioth-mounted () + "Check if ~/alioth is mounted, and if not, mount it." + (unless (file-exists-p "~/alioth/.exists") + (mount-alioth-sshfs))) + +(defun umount-alioth-sshfs () + "Unmount Alioth Uberspace using SSHFS." + (interactive) + (let ((default-directory "~")) + (shell-command "umount ~/alioth"))) + +(defun mount-alioth-sshfs () + "Mount Alioth Uberspace using SSHFS." + (interactive) + (let ((default-directory "~")) + (shell-command "sshfs lkn@alioth.uberspace.de:/home/lkn/ ~/alioth"))) + +(provide 'feature-alioth) diff --git a/emacs/feature/feature-avy.el b/emacs/feature/feature-avy.el new file mode 100644 index 0000000..b99483f --- /dev/null +++ b/emacs/feature/feature-avy.el @@ -0,0 +1,14 @@ +(require 'feature-pkg) + +(pkg/require 'avy) + +(avy-setup-default) +(setq avy-background t) + +(global-set-key (kbd "C-x j j") #'avy-goto-char) +(global-set-key (kbd "C-x j c") #'avy-goto-char-2) +(global-set-key (kbd "C-x j l") #'avy-goto-line) +(global-set-key (kbd "C-x j w") #'avy-goto-word-1) +(global-set-key (kbd "C-x j .") #'avy-resume) + +(provide 'feature-avy) diff --git a/emacs/feature/feature-base.el b/emacs/feature/feature-base.el new file mode 100644 index 0000000..db41adf --- /dev/null +++ b/emacs/feature/feature-base.el @@ -0,0 +1,91 @@ +;;; cfg-emacs.el -- configure emacs behavior and user interface +;;; +;;; Commentary: +;;; Exactly what it looks like. +;;; +;;; Code: + +(message "Loading base features...") + + +(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)))) + +(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)))) + +(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/reload-init-file () + "Reload the Emacs init file." + (interactive) + (load-file user-init-file)) + +(defun my/format-elisp-buffer () + "Format the whole elisp buffer." + (interactive) + (save-excursion + (indent-region (point-min) (point-max)))) + +(tool-bar-mode -1) +(setq backup-by-copying t + completion-cycle-threshold 3 + create-lockfiles nil + 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 + use-short-answers t + version-control t + backup-directory-alist `((".*" . ,(concat user-emacs-directory "backup"))) + read-extended-command-predicate #'command-completion-default-include-p + gc-cons-threshold 100000000 + auth-sources '("~/.authinfo.gpg") + warning-minimum-level :emergency + text-mode-ispell-word-completion nil + tramp-persistency-file-name "~/.config/emacs/tramp" + tramp-inline-compress-start-size 1000) + +;; load custom file +(setq custom-file (expand-file-name "custom.el" user-emacs-directory)) +(when (file-exists-p custom-file) + (load custom-file)) + +;; compile init.el on save +(add-hook 'after-save-hook #'my-compile-init-file) + +;; setup base key bindings +(global-set-key (kbd "M-/") 'dabbrev-expand) +(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 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) +(global-set-key (kbd "C-x c r") #'my/reload-init-file) +(global-set-key (kbd "C-x y b p") 'copy-file-path-to-clipboard) + +(provide 'feature-base) + +;;; feature-base.el ends here. diff --git a/emacs/feature/feature-browser.el b/emacs/feature/feature-browser.el new file mode 100644 index 0000000..a4174d0 --- /dev/null +++ b/emacs/feature/feature-browser.el @@ -0,0 +1,20 @@ +(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) + +(provide 'feature-browser) + diff --git a/emacs/feature/feature-consult-projectile.el b/emacs/feature/feature-consult-projectile.el new file mode 100644 index 0000000..1bf93e5 --- /dev/null +++ b/emacs/feature/feature-consult-projectile.el @@ -0,0 +1,37 @@ +;;; feature-consult-projectile.el --- consult/projectile integration +;;; Commentary: +;;; Integrate consult and projectile. +;;; +;;; Code: + +(require 'feature-pkg) +(require 'feature-consult) +(require 'feature-projectile) + +(defun my-consult-projectile-find-file () + "Use consult-projectile-find-file, consult-find-file outside of project." + (interactive) + (if (projectile-project-p) + (consult-projectile-find-file) + (consult-projectile))) + +(pkg/with-eval-after-load + (consult projectile) + + (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)) + +(global-set-key (kbd "C-x p") projectile-command-map) +(define-key projectile-command-map (kbd "f") #'consult-projectile-find-file) +(define-key projectile-command-map (kbd "d") #'consult-projectile-find-dir) +(define-key projectile-command-map (kbd "b") #'consult-projectile-switch-to-buffer) +(define-key projectile-command-map (kbd "s") #'consult-projectile-switch-project) +(define-key projectile-command-map (kbd "p") #'consult-projectile) +(define-key projectile-command-map (kbd "/") #'consult-ripgrep) +(define-key projectile-command-map (kbd "f") #'my-consult-projectile-find-file) + +(provide 'feature-consult-projectile) + +;;; feature-consult-projectile.el ends here. diff --git a/emacs/feature/feature-consult.el b/emacs/feature/feature-consult.el new file mode 100644 index 0000000..1a3b5ba --- /dev/null +++ b/emacs/feature/feature-consult.el @@ -0,0 +1,53 @@ +;;; feature-consult.el --- setup consult for emacs +;;; Commentary: +;;; Setup consult for Emacs. +;;; +;;; Code: + +(require 'feature-pkg) +(pkg/require 'consult) + +(setq consult-preview-key nil) + +;; use ripgrep +(setq consult-ripgrep-command "rg --null --line-buffered --color=ansi --max-columns=1000 --no-heading --line-number . -e ARG OPTS") +(setq consult-grep-command "rg --null --line-buffered --color=ansi --max-columns=1000 --no-heading --line-number . -e ARG OPTS") + +;; 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)) + +;; async search +(setq consult-async-min-input 2 + consult-async-refresh-delay 0.15 + consult-async-input-throttle 0.2 + consult-async-input-debounce 0.1) + +;; select xref locations with preview +(setq xref-show-xrefs-function #'consult-xref + xref-show-definitions-function #'consult-xref) + +(global-set-key (kbd "C-s") #'consult-line) +(global-set-key (kbd "C-r") #'consult-line-multi) +(global-set-key (kbd "C-c h") #'consult-history) +(global-set-key (kbd "C-c m") #'consult-mode-command) +(global-set-key (kbd "C-c k") #'consult-kmacro) +(global-set-key (kbd "C-c s") #'consult-ripgrep) + + +(provide 'feature-consult) + +;;; feature-consult.el ends here. diff --git a/emacs/feature/feature-ctags.el b/emacs/feature/feature-ctags.el new file mode 100644 index 0000000..5a18db7 --- /dev/null +++ b/emacs/feature/feature-ctags.el @@ -0,0 +1,11 @@ +(defun generate-tags-for-directory (directory) + "Generate CTAGS for the specified DIRECTORY." + (interactive + (list (read-directory-name "Generate tags for directory: " default-directory nil t))) + (let ((default-directory directory)) + (shell-command "ctags -e -R .") + (message "Tags generated for %s" directory))) + +(global-set-key (kbd "C-x E t") 'generate-tags-for-directory) + +(provide 'feature-ctags) diff --git a/emacs/feature/feature-denote-index.el b/emacs/feature/feature-denote-index.el new file mode 100644 index 0000000..6f3a291 --- /dev/null +++ b/emacs/feature/feature-denote-index.el @@ -0,0 +1,92 @@ +;;; feature-denote-index.el -- index and metadata for denote notes +;;; +;;; Commentary: Provides a mechanism for maintaining an index of +;;; denote notes with useful metadata to facilitate search. +;;; +;;; Code: + +(require 'feature-denote) + +(defvar my/denote-metadata-index-file (concat denote-directory "../denote-metadata.org") + "Path to the metadata index file.") + +(defun my/extract-date-from-filename (file) + "Extract date from FILE name. Assumes first 8 characters are YYYYMMDD." + (substring (file-name-nondirectory file) 0 8)) + +(defun my/format-date (date-string) + "Format DATE-STRING (YYYYMMDD) as YYYY-MM-DD." + (format "%s-%s-%s" + (substring date-string 0 4) + (substring date-string 4 6) + (substring date-string 6 8))) + +(defun my/extract-summary (file) + "Extract summary from FILE." + (with-temp-buffer + (insert-file-contents file) + (goto-char (point-min)) + (when (re-search-forward "^\\* Summary$" nil t) + (forward-line) + (let ((start (point))) + (if (re-search-forward "^\\*" nil t) + (string-trim (buffer-substring-no-properties start (line-beginning-position))) + (string-trim (buffer-substring-no-properties start (point-max)))))))) + +(defun my/update-denote-metadata-index () + "Update the metadata index for Denote notes in Org format." + (interactive) + (with-temp-file my/denote-metadata-index-file + (insert "#+TITLE: Denote Metadata Index\n") + (insert "#+AUTHOR: Your Name\n") + (insert "#+DATE: " (format-time-string "%Y-%m-%d") "\n\n") + (dolist (file (directory-files-recursively denote-directory "\\.org$")) + (let ((file-contents (with-temp-buffer + (insert-file-contents file) + (buffer-string)))) + (with-temp-buffer + (insert file-contents) + (goto-char (point-min)) + (when (re-search-forward "^#\\+title: \\(.+\\)$" nil t) + (let* ((title (string-trim (match-string 1))) + (keywords (denote-extract-keywords-from-path file)) + (date-string (my/extract-date-from-filename file)) + (formatted-date (my/format-date date-string)) + (is-journal (and (listp keywords) (member "journal" keywords))) + (summary (if is-journal + (format "Journal entry for [%s]" formatted-date) + (my/extract-summary file)))) + (with-current-buffer (find-file-noselect my/denote-metadata-index-file) + (goto-char (point-max)) + (insert (format "* [[file:%s][%s]]\n" file title)) + (insert " :PROPERTIES:\n") + (insert (format " :KEYWORDS: %s\n" keywords)) + (insert (format " :DATE: [%s]\n" formatted-date)) + (insert " :END:\n\n") + (insert (if summary + summary + "No summary provided.")) + (unless (string-suffix-p "\n\n" (or summary "")) + (insert "\n\n")))))))))) + + +(defun my/search-denote-notes () + "Use consult-ripgrep to search only the Denote metadata index file." + (interactive) + (let* ((index-file my/denote-metadata-index-file) + (default-directory (file-name-directory index-file)) + (file-name (file-name-nondirectory index-file)) + (consult-ripgrep-args + (concat "rg --null --ignore-case --type org --line-buffered --color=never " + "--max-columns=500 --no-heading --line-number " + "--max-depth 0 " + (shell-quote-argument file-name)))) + (if (file-exists-p index-file) + (consult-ripgrep default-directory) + (message "Metadata index file not found: %s" index-file)))) + +(global-set-key (kbd "C-x n /") #'my/search-denote-notes) + +(provide 'feature-denote-index) + +;;; feature-denote-index.el ends here diff --git a/emacs/feature/feature-denote-multidir.el b/emacs/feature/feature-denote-multidir.el new file mode 100644 index 0000000..9126229 --- /dev/null +++ b/emacs/feature/feature-denote-multidir.el @@ -0,0 +1,77 @@ +;;; feature-denote-multidir.el --- denote with multi-dir support +;;; Commentary: +;;; Extend denote with support for multiple notes directories. +;;; +;;; Code: + +(require 'feature-denote) +(require 'feature-denote-index) +(require 'feature-alioth) ;; depends on alioth for some functions + +(defvar my/denote-local-notes-directory "~/notes/") +(defvar my/denote-remote-notes-directory "~/alioth/notes/") + +(setq denote-directory my/denote-local-notes-directory) + +(defun my/denote-open-or-create () + "Ensure alioth is mounted before creating or opening a note." + (interactive) + (ensure-alioth-mounted) + (let ((target (denote-file-prompt nil nil :no-require-match))) + (if (and target (file-exists-p target)) + (find-file target) + (denote--command-with-features (denote-command-prompt) :use-file-prompt-as-def-title nil nil nil)))) + +(defun my/denote-echo-current-directory () + "Echo the current value of denote-directory." + (declare (interactive-only t)) + (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) + (setq my/denote-metadata-index-file (concat denote-directory "../denote-metadata.org")) + (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 ORIG-FUN and ARGS." + (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) + +(global-set-key (kbd "C-x n f") #'my/denote-open-or-create) +(global-set-key (kbd "C-x n n") #'my/denote-create-note) +(global-set-key (kbd "C-x n d s") #'my/denote-switch-directory) +(global-set-key (kbd "C-x n d d") #'my/denote-echo-current-directory) + +(provide 'feature-denote-multidir) + +;;; feature-denote-multidir.el ends here. diff --git a/emacs/feature/feature-denote.el b/emacs/feature/feature-denote.el new file mode 100644 index 0000000..441990b --- /dev/null +++ b/emacs/feature/feature-denote.el @@ -0,0 +1,52 @@ +;;; feature-denote.el --- note taking +;;; Commentary: +;;; Configure denote for note taking purposes. +;;; +;;; Code: + +(require 'feature-pkg) +(require 'feature-dired) + +(pkg/require 'denote) + +(denote-rename-buffer-mode 1) + +(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) + +(global-unset-key (kbd "C-x n")) +(global-set-key (kbd "C-x n c") #'denote-region) +(global-set-key (kbd "C-x n !") #'denote-date) +(global-set-key (kbd "C-x n z") #'denote-signature) +(global-set-key (kbd "C-x n s") #'denote-subdirectory) +(global-set-key (kbd "C-x n S-n") #'denote-type) +(global-set-key (kbd "C-x n t") #'denote-template) +(global-set-key (kbd "C-x n l l") #'denote-link) +(global-set-key (kbd "C-x n l a") #'denote-add-links) +(global-set-key (kbd "C-x n l f") #'denote-find-link) +(global-set-key (kbd "C-x n b l") #'denote-backlinks) +(global-set-key (kbd "C-x n b f") #'denote-find-backlink) +(global-set-key (kbd "C-x n r r") #'denote-rename-file) +(global-set-key (kbd "C-x n r f") #'denote-rename-file-using-front-matter) +(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)) + + +(provide 'feature-denote) + +;;; feature-denote.el ends here. diff --git a/emacs/feature/feature-detached.el b/emacs/feature/feature-detached.el new file mode 100644 index 0000000..6cc9b17 --- /dev/null +++ b/emacs/feature/feature-detached.el @@ -0,0 +1,23 @@ +;;; feature-detached.el --- dtach for Emacs +;;; Commentary: +;;; Setup dtach and Emacs integration. +;;; +;;; Code: + +(require 'feature-pkg) + +(pkg/require 'detached) + +(setq detached-terminal-data-command system-type) + +(detached-init) + +(global-set-key [remap async-shell-command] 'detached-shell-command) +(global-set-key [remap compile] 'detached-compile) +(global-set-key [remap recompile] 'detached-compile-recompile) +(global-set-key [remap detached-open-session] 'detached-consult-session) + +(provide 'feature-detached) + +;;; feature-detached.el ends here. + diff --git a/emacs/feature/feature-dired.el b/emacs/feature/feature-dired.el new file mode 100644 index 0000000..5dd1db4 --- /dev/null +++ b/emacs/feature/feature-dired.el @@ -0,0 +1,29 @@ +;;; feature-dired.el --- dired stuff +;;; Commentary: +;;; Configure Dired stuff. +;;; +;;; Code: + +(require 'feature-pkg) +(require 'dired) + +(pkg/require 'dired-sidebar) + +(defun my/dired-duplicate-file (file) + "Duplicate the current FILE in Dired." + (interactive "p") + (let ((filename (dired-get-filename)) + (target '())) + (setq target (concat (file-name-sans-extension filename) + "-old" + (if (> file 1) (number-to-string file)) + (file-name-extension filename t))) + (if (file-directory-p filename) + (copy-file filename target)))) + +(global-set-key (kbd "") 'dired-sidebar-toggle-sidebar) +(define-key dired-mode-map (kbd "C-c d") 'my/dired-duplicate-file) + +(provide 'feature-dired) + +;;; feature-dired.el ends here. diff --git a/emacs/feature/feature-embark.el b/emacs/feature/feature-embark.el new file mode 100644 index 0000000..2c24f80 --- /dev/null +++ b/emacs/feature/feature-embark.el @@ -0,0 +1,15 @@ +(require 'feature-pkg) +(pkg/require 'embark) + +(setq prefix-help-command #'embark-prefix-help-command) + +(add-to-list 'display-buffer-alist + '("\\`\\*Embark Collect \\(Live\\|Completions\\)\\*" + nil + (window-parameters (mode-line-format . none)))) + +(global-set-key (kbd "C-.") #'embark-act) +(global-set-key (kbd "C-;") #'embark-dwim) +(global-set-key (kbd "C-h B") #'embark-bindings) + +(provide 'feature-embark) diff --git a/emacs/feature/feature-eshell.el b/emacs/feature/feature-eshell.el new file mode 100644 index 0000000..1be1d87 --- /dev/null +++ b/emacs/feature/feature-eshell.el @@ -0,0 +1,19 @@ +;;; feature-eshell.el --- eshell enhancements. +;;; Commentary: +;;; Configure eshell enhancements. +;;; +;;; Code: + +(require 'feature-pkg) + +(pkg/require 'eshell) +(pkg/require 'eshell-autojump) +(pkg/require 'eshell-bookmark) +(pkg/require 'eshell-git-prompt) +(pkg/require 'eshell-toggle) + +(global-set-key (kbd "C-") #'eshell-toggle) + +(provide 'feature-eshell) + +;;; feature-eshell.el ends here. diff --git a/emacs/feature/feature-eyecandy.el b/emacs/feature/feature-eyecandy.el new file mode 100644 index 0000000..01f5e96 --- /dev/null +++ b/emacs/feature/feature-eyecandy.el @@ -0,0 +1,20 @@ +;; themes +(pkg/require 'acme-theme) + +;; modeline +(pkg/require 'doom-modeline) +(doom-modeline-mode 1) + +;; icons +(pkg/require 'nerd-icons) +(pkg/require 'nerd-icons-dired) +(pkg/require 'nerd-icons-completion) +(pkg/require 'nerd-icons-ibuffer) +(pkg/require 'kind-icon) + +(add-hook 'dired-mode-hook #'nerd-icons-dired-mode) + +;; fonts +(pkg/require 'unicode-fonts) + +(provide 'feature-eyecandy) diff --git a/emacs/feature/feature-golang-playground.el b/emacs/feature/feature-golang-playground.el new file mode 100644 index 0000000..7165ec0 --- /dev/null +++ b/emacs/feature/feature-golang-playground.el @@ -0,0 +1,3 @@ +(pkg/require 'go-playground) + +(provide 'feature-golang-playground) diff --git a/emacs/feature/feature-golang.el b/emacs/feature/feature-golang.el new file mode 100644 index 0000000..56b541e --- /dev/null +++ b/emacs/feature/feature-golang.el @@ -0,0 +1,6 @@ +(pkg/require 'go-mode) + +(add-to-list 'auto-mode-alist '("\\.go$" . go-mode)) +(add-hook 'before-save-hook #'gofmt-before-save) + +(provide 'feature-golang) diff --git a/emacs/feature/feature-graphviz.el b/emacs/feature/feature-graphviz.el new file mode 100644 index 0000000..bd2d19f --- /dev/null +++ b/emacs/feature/feature-graphviz.el @@ -0,0 +1,7 @@ +(pkg/require 'graphviz-dot-mode) + +(with-eval-after-load 'org + (org-babel-do-load-languages + 'org-babel-load-languages '((dot . t)))) + +(provide 'feature-graphviz) diff --git a/emacs/feature/feature-json.el b/emacs/feature/feature-json.el new file mode 100644 index 0000000..89bee23 --- /dev/null +++ b/emacs/feature/feature-json.el @@ -0,0 +1,5 @@ +(pkg/require 'json-mode) + +(add-to-list 'auto-mode-alist '("\\.json$" . json-mode)) + +(provide 'feature-json) diff --git a/emacs/feature/feature-keybindings.el b/emacs/feature/feature-keybindings.el new file mode 100644 index 0000000..e69de29 diff --git a/emacs/feature/feature-kotlin.el b/emacs/feature/feature-kotlin.el new file mode 100644 index 0000000..2526234 --- /dev/null +++ b/emacs/feature/feature-kotlin.el @@ -0,0 +1,5 @@ +(pkg/require 'kotlin-mode) + +;;(add-to-list 'auto-mode-alist ("\\.kt$" . kotlin-mode)) + +(provide 'feature-kotlin) diff --git a/emacs/feature/feature-llm.el b/emacs/feature/feature-llm.el new file mode 100644 index 0000000..11ea32b --- /dev/null +++ b/emacs/feature/feature-llm.el @@ -0,0 +1,24 @@ +(require 'feature-pkg) + +(pkg/require 'ellama) +(require 'llm-ollama) + +(setq ellama-language "English") +(setq ellama-provider 'ollama) +(setq ellama-ollama-binary "/usr/local/bin/ollama") + +(setopt ellama-provider + (make-llm-ollama + :chat-model "codellama:latest" + :embedding-model "nomic-embed-text" + :default-chat-non-standard-params '(("num_ctx" . 8192)))) + +(setq ellama-keymap-prefix "C-x A e") +(with-eval-after-load 'ellama + (ellama-setup-keymap)) + + +(pkg/require 'org-ai) +(add-hook 'org-mode-hook #'org-ai-mode) + +(provide 'feature-llm) diff --git a/emacs/feature/feature-lmtf.el b/emacs/feature/feature-lmtf.el new file mode 100644 index 0000000..35c10ab --- /dev/null +++ b/emacs/feature/feature-lmtf.el @@ -0,0 +1,10 @@ +(require 'feature-pkg) + +(straight-use-package + '(lmtf :type git :host github :repo "lneely/lmtf")) + +(with-eval-after-load 'lmtf + (define-key emacs-lisp-mode-map (kbd "C-c t !") #'lmtf-run-all-tests) + (define-key emacs-lisp-mode-map (kbd "C-c t r") #'lmtf-show-results)) + +(provide 'feature-lmtf) diff --git a/emacs/feature/feature-magit.el b/emacs/feature/feature-magit.el new file mode 100644 index 0000000..6a58909 --- /dev/null +++ b/emacs/feature/feature-magit.el @@ -0,0 +1,62 @@ +;;; feature-magit.el --- git integration +;;; Commentary: +;;; Setup git integration with Emacs. +;;; +;;; Code: + +(require 'feature-pkg) + +(pkg/require 'magit) + +(setq magit-display-buffer-function + #'magit-display-buffer-same-window-except-diff-v1) + +;; git reset --soft $(git merge-base branch1 branch2) +(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)))) + +;; git reset --soft @{u} +(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")))) + +(defvar my/magit-prefix-map (make-sparse-keymap)) +(define-key ctl-x-map "g" my/magit-prefix-map) +(define-key my/magit-prefix-map "g" #'magit-status) +(define-key my/magit-prefix-map "rb" #'magit-reset-to-merge-base) +(define-key my/magit-prefix-map "ru" #'magit-reset-to-upstream) +(define-key my/magit-prefix-map "ll" #'magit-log) +(define-key my/magit-prefix-map "la" #'magit-log-all) +(define-key my/magit-prefix-map "lf" #'magit-log-buffer-file) +(define-key my/magit-prefix-map "Rs" #'magit-reset-soft) +(define-key my/magit-prefix-map "Rh" #'magit-reset-hard) +(define-key my/magit-prefix-map "!" #'magit-run) +(define-key my/magit-prefix-map "bb" #'magit-branch) +(define-key my/magit-prefix-map "bc" #'magit-branch-create) +(define-key my/magit-prefix-map "bs" #'magit-branch-checkout) +(define-key my/magit-prefix-map "mm" #'magit-merge) +(define-key my/magit-prefix-map "dd" #'magit-diff) + +(provide 'feature-magit) + +;;; feature-magit.el ends here. diff --git a/emacs/feature/feature-multi-cursor.el b/emacs/feature/feature-multi-cursor.el new file mode 100644 index 0000000..2027a96 --- /dev/null +++ b/emacs/feature/feature-multi-cursor.el @@ -0,0 +1,19 @@ +;;; feature-multi-cursor.el --- multiple cursors +;;; Commentary: +;;; Multiple cursor support for Emacs. +;;; +;;; Code: + +(require 'feature-pkg) +(pkg/require 'multiple-cursors) + +(multiple-cursors-mode 1) + +(global-set-key (kbd "C-x C-<") 'mc/edit-lines) +(global-set-key (kbd "C->") 'mc/mark-next-like-this) +(global-set-key (kbd "C-<") 'mc/mark-previous-like-this) +(global-set-key (kbd "C-x C->") 'mc/mark-all-like-this) + +(provide 'feature-multi-cursor) + +;;; feature-multi-cursor.el ends here. diff --git a/emacs/feature/feature-orderless.el b/emacs/feature/feature-orderless.el new file mode 100644 index 0000000..d1aea6e --- /dev/null +++ b/emacs/feature/feature-orderless.el @@ -0,0 +1,16 @@ +;;; feature-orderless.el --- fuzzy search +;;; Commentary: +;;; Setup fuzzy search for Emacs. +;;; +;;; Code: + +(require 'feature-pkg) +(pkg/require 'orderless) + +(setq completion-styles '(orderless basic) + completion-category-overrides '((file (styles . (partial-completion)))) + orderless-matching-styles '(orderless-flex orderless-prefixes orderless-initialism) + orderless-component-separator #'orderless-escapable-split-on-space) + +(provide 'feature-orderless) +;;; feature-orderless.el ends here. diff --git a/emacs/feature/feature-org.el b/emacs/feature/feature-org.el new file mode 100644 index 0000000..dff7325 --- /dev/null +++ b/emacs/feature/feature-org.el @@ -0,0 +1,133 @@ +;;; feature-org.el --- org-mode +;;; Commentary: +;;; Setup org-mode for Emacs. +;;; +;;; Code: + +(require 'feature-pkg) +(pkg/require 'org) + +(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/add-remote-todo-file () + "Add remote TODO.org file to org-agenda-files." + (interactive) + (unless org-agenda-files + (setq org-agenda-files + (list "~/alioth/TODO.org")))) + +(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" ("TODO" "WAIT" "CNCL" "prj") nil "")) + (setq org-tags-exclude-from-inheritance '("prj"))) + +(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) + ("~/alioth/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 "~/alioth/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)))) + +(defun my/reload-org-config () + "Reload the org configuration." + (interactive) + (my/org-config) + (message "Org configuration reloaded")) + +(defun my/visit-todo () + "Visit TODO.org in home." + (interactive) + (find-file "~/alioth/TODO.org")) + +(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.")) + +(add-to-list 'auto-mode-alist '("\\.org\\'" . org-mode)) +(advice-add 'org-agenda :before #'my/add-remote-todo-file) +(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) +(org-babel-do-load-languages 'org-babel-load-languages + '((shell . t))) + + +;; key bindings +(global-set-key (kbd "C-x c O") 'my/reload-org-config) +(global-set-key (kbd "C-x O a") 'org-agenda) +(global-set-key (kbd "C-x O c") 'org-capture) +(global-set-key (kbd "C-x O t") 'my/visit-todo) +(global-set-key (kbd "C-x O k") 'my/kill-all-org-agenda-buffers) + +(provide 'feature-org) + +;;; feature-org.el ends here. diff --git a/emacs/feature/feature-pkg.el b/emacs/feature/feature-pkg.el new file mode 100644 index 0000000..9e90a48 --- /dev/null +++ b/emacs/feature/feature-pkg.el @@ -0,0 +1,74 @@ +(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) + +;; install straight.el +(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)) + +(defun pkg/ensure-installed (package) + "Ensure that PACKAGE is installed." + (unless (package-installed-p package) + (package-refresh-contents) + (package-install package))) + +(defmacro pkg/require (feature &optional filename noerror) + "Ensure FEATURE is installed, then require it. +FILENAME and NOERROR are passed to `require'." + `(progn + (pkg/ensure-installed ,feature) + (require ,feature ,filename ,noerror))) + +(defmacro pkg/require-defer (feature &optional filename noerror) + "Defer loading of FEATURE until it's actually needed. +Ensure FEATURE is installed first. +FILENAME, if non-nil, is the file to load to provide FEATURE. +If NOERROR is non-nil, don't signal an error if the file is not found." + `(progn + (pkg/ensure-installed ,feature) + (unless (featurep ',feature) + (autoload #',(intern (format "%s-autoloaded" feature)) + ,(or filename (symbol-name feature)) + nil t) + (defalias ',feature #',(intern (format "%s-autoloaded" feature)))) + ',feature)) + +(defun pkg/require-defer-load (feature) + "Check if a deferred FEATURE is already loaded." + (unless (featurep feature) + (unload-feature feature t) + (require feature) + (fmakunbound (intern (format "%s-autoloaded" feature)))) + feature) + +(defmacro pkg/require-defer-timer (feature seconds) + "Ensure FEATURE is installed, then defer its loading by SECONDS idle time." + `(progn + (pkg/ensure-installed ,feature) + (run-with-idle-timer ,seconds nil #'require ',feature))) + +(defun pkg/packages-loaded-p (&rest packages) + "Return t if all PACKAGES are loaded." + (cl-every #'featurep packages)) + +(defmacro pkg/with-eval-after-load (packages &rest body) + "Run BODY after all PACKAGES are loaded." + `(if (apply #'pkg/packages-loaded-p ',packages) + (progn ,@body) + (eval-after-load (car (last ',packages)) + '(pkg/run-after-packages-load ',packages ,@body)))) + +(provide 'feature-pkg) diff --git a/emacs/feature/feature-plantuml.el b/emacs/feature/feature-plantuml.el new file mode 100644 index 0000000..097cbca --- /dev/null +++ b/emacs/feature/feature-plantuml.el @@ -0,0 +1,12 @@ +(pkg/require 'plantuml-mode) + +(setq 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")) + +(with-eval-after-load 'org + (org-babel-do-load-languages + 'org-babel-load-languages '((plantuml . t)))) + +(provide 'feature-plantuml) diff --git a/emacs/feature/feature-projectile.el b/emacs/feature/feature-projectile.el new file mode 100644 index 0000000..4460e5c --- /dev/null +++ b/emacs/feature/feature-projectile.el @@ -0,0 +1,15 @@ +;;; feature-projectile.el --- project management +;;; Commentary: +;;; Project management for Emacs nerds. +;;; +;;; Code: + +(require 'feature-pkg) +(pkg/require 'projectile) + +(projectile-mode +1) + +(provide 'feature-projectile) + +;;; feature-projectile.el ends here. + diff --git a/emacs/feature/feature-protobuf.el b/emacs/feature/feature-protobuf.el new file mode 100644 index 0000000..7693dbc --- /dev/null +++ b/emacs/feature/feature-protobuf.el @@ -0,0 +1,6 @@ +(pkg/require 'protobuf-mode) + +(add-to-list 'auto-mode-alist '("\\.proto$" . protobuf-mode)) + +(provide 'feature-protobuf) + diff --git a/emacs/feature/feature-python.el b/emacs/feature/feature-python.el new file mode 100644 index 0000000..e36ff42 --- /dev/null +++ b/emacs/feature/feature-python.el @@ -0,0 +1,7 @@ +(pkg/require 'python-mode) +(pkg/require 'python-environment) + +(add-to-list 'auto-mode-alist '("\\.py$" . python-mode)) +(add-to-list 'auto-mode-alist '("BUILD\\'" . python-mode)) + +(provide 'feature-python) diff --git a/emacs/feature/feature-rego.el b/emacs/feature/feature-rego.el new file mode 100644 index 0000000..7a9beee --- /dev/null +++ b/emacs/feature/feature-rego.el @@ -0,0 +1,3 @@ +(pkg/require 'rego-mode) + +(provide 'feature-rego) diff --git a/emacs/feature/feature-rss.el b/emacs/feature/feature-rss.el new file mode 100644 index 0000000..c958a80 --- /dev/null +++ b/emacs/feature/feature-rss.el @@ -0,0 +1,12 @@ +;;; feature-orderless.el --- rss feeds in emacs +;;; Commentary: +;;; Read RSS feeds in Emacs, stop browsing the web. +;;; +;;; Code: +(require 'feature-pkg) + +(pkg/require 'elfeed) + +(provide 'feature-rss) + +;;; feature-rss.el ends here. diff --git a/emacs/feature/feature-scratch.el b/emacs/feature/feature-scratch.el new file mode 100644 index 0000000..8100459 --- /dev/null +++ b/emacs/feature/feature-scratch.el @@ -0,0 +1,34 @@ +;;; feature-scratch.el --- scratch buffer enhancements +;;; Commentary: +;;; Scratch buffer enhancements for Emacs. +;;; +;;; Code: + +(require 'feature-pkg) +(pkg/require 'scratch) +(pkg/require 'persistent-scratch) + +(defun my-set-scratch-mode () + "Set default scratch mode." + (funcall 'text-mode)) + +(persistent-scratch-setup-default) +(setq persistent-scratch-directory "~/.local/state/emacs/" + persistent-scratch-autosave-interval 30 + persistent-scratch-what-to-save '(((:current . "*scratch*") . + (major-mode point narrowing))) + persistent-scratch-save-file (expand-file-name + "scratch" persistent-scratch-directory)) + +(unless (file-exists-p persistent-scratch-directory) + (make-directory persistent-scratch-directory t)) + +(add-hook 'scratch-create-buffer-hook 'my-set-scratch-mode) +(add-hook 'after-init-hook #'persistent-scratch-autosave-mode) + +(global-set-key (kbd "C-c s") #'persistent-scratch-save) +(global-set-key (kbd "C-c r") #'persistent-scratch-restore) + +(provide 'feature-scratch) + +;;; feature-scratch.el ends here. diff --git a/emacs/feature/feature-sidebar.el b/emacs/feature/feature-sidebar.el new file mode 100644 index 0000000..e69de29 diff --git a/emacs/feature/feature-snippets.el b/emacs/feature/feature-snippets.el new file mode 100644 index 0000000..3dc9047 --- /dev/null +++ b/emacs/feature/feature-snippets.el @@ -0,0 +1,16 @@ +(require 'feature-pkg) + +(pkg/require 'yasnippet) +(pkg/require 'yasnippet-snippets) + +(setq yas-snippet-dirs '("~/.config/emacs/snippets/")) +(yas-global-mode 1) + +(global-set-key (kbd "C-x y i") #'yas-insert-snippet) +(global-set-key (kbd "C-x y n") #'yas-new-snippet) +(global-set-key (kbd "C-x y v") #'yas-visit-snippet-file) + +(add-hook 'prog-mode-hook #'yas-minor-mode) +(add-hook 'org-mode-hook #'yas-minor-mode) + +(provide 'feature-snippets) diff --git a/emacs/feature/feature-syntax.el b/emacs/feature/feature-syntax.el new file mode 100644 index 0000000..4046cda --- /dev/null +++ b/emacs/feature/feature-syntax.el @@ -0,0 +1,17 @@ +;;; feature-syntax.el --- syntax related stuff +;;; Commentary: +;;; Configure syntax-related settings. +;;; +;;; Code: + +(require 'feature-pkg) + +(pkg/require 'fic-mode) +(pkg/require 'flycheck) + +(global-flycheck-mode) +(setq flycheck-emacs-lisp-load-path 'inherit) + +(provide 'feature-syntax) + +;;; feature-syntax.el ends here. diff --git a/emacs/feature/feature-vertico.el b/emacs/feature/feature-vertico.el new file mode 100644 index 0000000..02449a2 --- /dev/null +++ b/emacs/feature/feature-vertico.el @@ -0,0 +1,29 @@ +;;; feature-vertico.el --- better minibuffer +;;; Commentary: +;;; Better minibuffer for emacs. +;;; +;;; Code: + +(require 'feature-pkg) +(pkg/require 'vertico) + + +(vertico-mode) +(setq vertico-cycle t + vertico-count 13 + 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)))) + +(pkg/with-eval-after-load + (vertico) + + (require 'vertico-quick) + (define-key vertico-map (kbd "C-q") #'vertico-quick-insert) + (define-key vertico-map (kbd "M-q") #'vertico-quick-exit)) + +(provide 'feature-vertico) +;;; feature-vertico.el ends here. diff --git a/emacs/feature/feature-web.el b/emacs/feature/feature-web.el new file mode 100644 index 0000000..1f2fc68 --- /dev/null +++ b/emacs/feature/feature-web.el @@ -0,0 +1,9 @@ +(pkg/require 'mmm-mode) +(pkg/require 'multi-web-mode) +(pkg/require 'php-mode) +(pkg/require 'emmet-mode) + +(add-to-list 'auto-mode-alist '("\\.php$" . php-mode)) + +(provide 'feature-web) + diff --git a/emacs/feature/feature-which-key.el b/emacs/feature/feature-which-key.el new file mode 100644 index 0000000..dec8c00 --- /dev/null +++ b/emacs/feature/feature-which-key.el @@ -0,0 +1,5 @@ +(pkg/require 'which-key) + +(which-key-mode) + +(provide 'feature-which-key) diff --git a/emacs/feature/feature-yaml.el b/emacs/feature/feature-yaml.el new file mode 100644 index 0000000..d82273e --- /dev/null +++ b/emacs/feature/feature-yaml.el @@ -0,0 +1,5 @@ +(pkg/require 'yaml-mode) + +(add-to-list 'auto-mode-alist '("\\.yml$" . yaml-mode)) + +(provide 'feature-yaml) diff --git a/emacs/init.el b/emacs/init.el index 9a40193..6b097e8 100644 --- a/emacs/init.el +++ b/emacs/init.el @@ -1,1039 +1,69 @@ -;;; init.el -- emacs configuration file - +;;; init.el --- Emacs configuration entry point ;;; Commentary: -;;; none - +;;; Configure Emacs. +;;; ;;; 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 "~/alioth/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" ("TODO" "WAIT" "CNCL" "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) - ("~/alioth/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 "~/alioth/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 "~/alioth/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 "~/alioth/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) - (setq my/denote-metadata-index-file (concat denote-directory "../denote-metadata.org")) - - (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 consult-ripgrep to search only the Denote metadata index file, without recursion." - (interactive) - (let* ((index-file my/denote-metadata-index-file) - (default-directory (file-name-directory index-file)) - (file-name (file-name-nondirectory index-file)) - (consult-ripgrep-args - (concat "rg --null --ignore-case --type org --line-buffered --color=never " - "--max-columns=500 --no-heading --line-number " - "--max-depth 0 " - (shell-quote-argument file-name)))) - (if (file-exists-p index-file) - (consult-ripgrep default-directory) - (message "Metadata index file not found: %s" index-file)))) - - - ;; (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-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 python-environment :ensure t) -(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 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 ellama - :ensure t - :demand t - :config - (setq ellama-language "English") - (setq ellama-provider 'ollama) - (setq ellama-ollama-binary "/usr/local/bin/ollama") - (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)))) - :config - (setq ellama-keymap-prefix "C-x A e")) - -(with-eval-after-load 'ellama - (ellama-setup-keymap)) - ;; :bind - ;; (("C-x A c" . ellama-chat) - ;; ("C-x A a" . ellama-ask-about) - ;; ("C-x A s" . ellama-summarize) - ;; ("C-x A t" . ellama-translate) - ;; ("C-x A r" . ellama-code-review))) - -(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))))) - -(use-package ement - :ensure t - :defer t - :config - (setq ement-save-sessions t) - (setq ement-room-list-auto-update t)) - -(require 'denote) - -;; automated denote metadata management -(defvar my/denote-metadata-index-file (concat denote-directory "../denote-metadata.org") - "Path to the metadata index file.") - -(defun my/extract-date-from-filename (file) - "Extract date from FILE name. Assumes first 8 characters are YYYYMMDD." - (substring (file-name-nondirectory file) 0 8)) - -(defun my/format-date (date-string) - "Format DATE-STRING (YYYYMMDD) as YYYY-MM-DD." - (format "%s-%s-%s" - (substring date-string 0 4) - (substring date-string 4 6) - (substring date-string 6 8))) - -(defun my/extract-summary (file) - "Extract summary from FILE." - (with-temp-buffer - (insert-file-contents file) - (goto-char (point-min)) - (when (re-search-forward "^\\* Summary$" nil t) - (forward-line) - (let ((start (point))) - (if (re-search-forward "^\\*" nil t) - (string-trim (buffer-substring-no-properties start (line-beginning-position))) - (string-trim (buffer-substring-no-properties start (point-max)))))))) - - -(defun my/update-denote-metadata-index () - "Update the metadata index for Denote notes in Org format." - (interactive) - (with-temp-file my/denote-metadata-index-file - (insert "#+TITLE: Denote Metadata Index\n") - (insert "#+AUTHOR: Your Name\n") - (insert "#+DATE: " (format-time-string "%Y-%m-%d") "\n\n") - (dolist (file (directory-files-recursively denote-directory "\\.org$")) - (let ((file-contents (with-temp-buffer - (insert-file-contents file) - (buffer-string)))) - (with-temp-buffer - (insert file-contents) - (goto-char (point-min)) - (when (re-search-forward "^#\\+title: \\(.+\\)$" nil t) - (let* ((title (string-trim (match-string 1))) - (keywords (denote-extract-keywords-from-path file)) - (date-string (my/extract-date-from-filename file)) - (formatted-date (my/format-date date-string)) - (is-journal (and (listp keywords) (member "journal" keywords))) - (summary (if is-journal - (format "Journal entry for [%s]" formatted-date) - (my/extract-summary file)))) - (with-current-buffer (find-file-noselect my/denote-metadata-index-file) - (goto-char (point-max)) - (insert (format "* [[file:%s][%s]]\n" file title)) - (insert " :PROPERTIES:\n") - (insert (format " :KEYWORDS: %s\n" keywords)) - (insert (format " :DATE: [%s]\n" formatted-date)) - (insert " :END:\n\n") - (insert (if summary - summary - "No summary provided.")) - (unless (string-suffix-p "\n\n" (or summary "")) - (insert "\n\n")))))))))) - -(use-package detached - :ensure t - :init - (detached-init) - :bind (([remap async-shell-command] . detached-shell-command) - ([remap compile] . detached-compile) - ([remap recompile] . detached-compile-recompile) - ([remap detached-open-session] . detached-consult-session)) - :custom - ((detached-show-output-on-attach t) - (detached-terminal-data-command system-type))) - - -(defun generate-tags-for-directory (directory) - "Generate CTAGS for the specified DIRECTORY." - (interactive - (list (read-directory-name "Generate tags for directory: " default-directory nil t))) - (let ((default-directory directory)) - (shell-command "ctags -e -R .") - (message "Tags generated for %s" directory))) - -(global-set-key (kbd "C-x E t") 'generate-tags-for-directory) - -(defun mount-alioth-sshfs () - "Mount Alioth Uberspace using SSHFS." - (interactive) - (let ((default-directory "~")) - (shell-command "sshfs lkn@alioth.uberspace.de:/home/lkn/ ~/alioth"))) - -(defun umount-alioth-sshfs () - "Unmount Alioth Uberspace using SSHFS." - (interactive) - (let ((default-directory "~")) - (shell-command "umount ~/alioth"))) - - -(provide 'init) - -;; Local Variables: -;; flycheck-disabled-checkers: (emacs-lisp-checkdoc emacs-lisp) -;; End: +;; enable disabled-by-default functions +(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) + +;; setup load paths +(add-to-list 'load-path (expand-file-name "cfg" user-emacs-directory)) +(add-to-list 'load-path (expand-file-name "lisp" user-emacs-directory)) +(let* ((feature-directory (file-truename (expand-file-name "feature" user-emacs-directory))) + (default-directory feature-directory)) + (add-to-list 'load-path feature-directory) + (normal-top-level-add-subdirs-to-load-path)) + +;; load base configuration and additional features +(require 'feature-base) ; prep the beast +(require 'feature-pkg) ; this comes right after feature-base +(require 'feature-dired) ; dired stuff +(require 'feature-async) ; asynchronous functions in emacs +(require 'feature-alioth) ; when one computer isn't enough +(require 'feature-which-key) ; memorization is for the birds +(require 'feature-multi-cursor) ; do repeat yourself +(require 'feature-org) ; when markdown isn't good enough +(require 'feature-ctags) ; a fork for your spaghetti +(require 'feature-detached) ; attachment causes suffering +(require 'feature-denote) ; a second brain +(require 'feature-denote-multidir) ; a third, fourth, ... brain +(require 'feature-denote-index) ; manage second-brain dementia +(require 'feature-eyecandy) ; eye candy (themes, modeline...) +(require 'feature-magit) ; git stuff +(require 'feature-graphviz) ; diagrams for old people +(require 'feature-plantuml) ; diagrams for less-old people +(require 'feature-golang) ; gophers unite +(require 'feature-golang-playground) ; for playing with go code +(require 'feature-python) ; code that eats itself +(require 'feature-web) ; a veritable fucking mess +(require 'feature-protobuf) ; some google messaging shit +(require 'feature-kotlin) ; java for the only mildly insane +(require 'feature-yaml) ; because we all have to deal with it +(require 'feature-json) ; data for argonauts +(require 'feature-rego) ; some policy language we use at work +(require 'feature-syntax) ; track wip, compile into submission +(require 'feature-eshell) ; elisp shell +(require 'feature-scratch) ; extend scratch buffers +(require 'feature-orderless) ; fuzzy search +(require 'feature-vertico) ; better minibuffer +(require 'feature-embark) ; woof! +(require 'feature-consult) ; consult +(require 'feature-projectile) ; project management for nerds +(require 'feature-consult-projectile) ; consult-projectile +(require 'feature-rss) ; web browsing sucks +(require 'feature-llm) ; generate some bullshit +(require 'feature-snippets) ; lazy code generator +(require 'feature-browser) ; when we really have to browse... +(require 'feature-lmtf) ; lispy mctestface +(require 'feature-apitest) ; api testing capabilities +(require 'feature-avy) ; avy for quick jumping + +;; theme +(load-theme 'misterioso) ;;; init.el ends here - diff --git a/emacs/lisp/org-ai b/emacs/lisp/org-ai new file mode 160000 index 0000000..02a4325 --- /dev/null +++ b/emacs/lisp/org-ai @@ -0,0 +1 @@ +Subproject commit 02a43259e357846372672b58ba9416093493b3fb diff --git a/emacs/snippets/org-mode/.yas-compiled-snippets.el b/emacs/snippets/org-mode/.yas-compiled-snippets.el index 323e9e7..09422c2 100644 --- a/emacs/snippets/org-mode/.yas-compiled-snippets.el +++ b/emacs/snippets/org-mode/.yas-compiled-snippets.el @@ -7,4 +7,4 @@ ("aicld" "* ${1:What}\n\n#+begin_ai :service anthropic :model claude-3-5-sonnet-20240620\n[SYS]: ${2:You are a helpful assistant.}\n\n[ME]: ${0}\n#+end_ai\n" "aicld" nil nil nil "/home/lkn/.config/emacs/snippets/org-mode/aicld" nil nil))) -;;; Do not edit! File generated at Tue Sep 10 15:15:33 2024 +;;; Do not edit! File generated at Sat Sep 14 15:30:36 2024