;;; init.el --- Emacs configuration -*- lexical-binding: t; -*- ;; Copyright (C) 2023-2069 Thanos Apollo ;; Author: Thanos Apollo ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; ╭━━━━┳╮╱╱╱╱╱╱╱╱╱╱╱╱╱╱╭━━━╮╱╱╱╱╱╭╮╭╮╱╱╱╱╱╱╱╱╭━━━╮ ;; ┃╭╮╭╮┃┃╱╱╱╱╱╱╱╱╱╱╱╱╱╱┃╭━╮┃╱╱╱╱╱┃┃┃┃╱╱╱╱╱╱╱╱┃╭━━╯ ;; ╰╯┃┃╰┫╰━┳━━┳━╮╭━━┳━━╮┃┃╱┃┣━━┳━━┫┃┃┃╭━━╮╱╱╱╱┃╰━━┳╮╭┳━━┳━━┳━━╮ ;; ╱╱┃┃╱┃╭╮┃╭╮┃╭╮┫╭╮┃━━┫┃╰━╯┃╭╮┃╭╮┃┃┃┃┃╭╮┃╭━━╮┃╭━━┫╰╯┃╭╮┃╭━┫━━┫ ;; ╱╱┃┃╱┃┃┃┃╭╮┃┃┃┃╰╯┣━━┃┃╭━╮┃╰╯┃╰╯┃╰┫╰┫╰╯┃╰━━╯┃╰━━┫┃┃┃╭╮┃╰━╋━━┃ ;; ╱╱╰╯╱╰╯╰┻╯╰┻╯╰┻━━┻━━╯╰╯╱╰┫╭━┻━━┻━┻━┻━━╯╱╱╱╱╰━━━┻┻┻┻╯╰┻━━┻━━╯ ;; ╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱┃┃ ;; ╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╰╯ ;;; Code: (package-initialize) (setf user-full-name "Thanos Apollo" user-mail-address "public@thanosapollo.org") (setf copyright-names-regexp (format "%s <%s>" user-full-name user-mail-address)) (defvar is-zeus (string= (system-name) "zeus")) (defvar is-hermes (string= (system-name) "hermes")) (defvar is-phone (string= (system-name) "localhost")) (defvar is-uranus (string= (system-name) "uranus")) ;; Font (custom-set-faces (if is-hermes '(default ((t (:inherit nil :height 130 :family "Fira Mono")))) '(default ((t (:inherit nil :height 150 :family "Fira Mono"))))) '(org-modern-symbol ((t (:inherit t :family "Iosevka Aile")))) '(variable-pitch ((t (:inherit t :family "Fira Mono" :height 130))))) ;; Autoinsert (auto-insert-mode 1) (setf auto-insert-alist '((python-mode . "python.template")) auto-insert-directory (locate-user-emacs-file "insert")) ;;;; Completions (add-to-list 'completion-styles 'initials t) (setf tab-always-indent 'icomplete-force-complete) (require 'completion-preview) (define-key completion-in-region-mode-map (kbd "M-n") 'minibuffer-next-completion) (define-key completion-in-region-mode-map (kbd "M-p") 'minibuffer-previous-completion) (setf completion-styles '(flex) completion-auto-select t completion-auto-help 'always completion-cycle-threshold 1 completions-format 'vertical completions-sort 'historical completions-max-height 20) ;; Fido (require 'icomplete) (fido-vertical-mode) (define-key icomplete-fido-mode-map (kbd "TAB") #'icomplete-fido-ret) (when (> emacs-major-version 29) (global-completion-preview-mode)) ;; Search (setf search-default-mode #'char-fold-to-regexp) ;; make it easier to search Greek chars ;; Set and load custom.el (setf custom-file (locate-user-emacs-file "custom.el")) (load custom-file 'noerror) ;; Imenu ;; Enable use-package support for imenu (setf use-package-enable-imenu-support t) (require 'imenu) (setf imenu-auto-rescan t imenu-flatten 'prefix) (global-set-key (kbd "C-c m") 'imenu) ;; Registers & Misc nav (setf register-use-preview t) (global-set-key (kbd "C-x r d") 'bookmark-delete) (global-set-key (kbd "C-x C-b") 'switch-to-prev-buffer) (global-set-key (kbd "C-x M-b") 'ibuffer) (defvar-keymap thanos/search-map "g" #'grep-find "l" #'lgrep "f" #'find-name-dired "s" #'occur) (global-set-key (kbd "C-c s") thanos/search-map) (setf browse-url-browser-function 'browse-url-generic browse-url-generic-program "icecat" backup-directory-alist '((".*" . "~/.Trash")) sentence-end-double-space t default-input-method "bulgarian-phonetic" gc-cons-threshold 5000000 disabled-command-function nil ;; Enable all commands url-privacy-level 'high) (setf calendar-date-style 'european) (defun thanos/add-custom-keywords () "Add custom warning keywords." (font-lock-add-keywords nil '(("\\<\\(FIXME\\):" 1 font-lock-warning-face t) ("\\<\\(TODO\\):" 1 font-lock-warning-face t) ("\\<\\(NOTE\\):" 1 font-lock-warning-face t)))) (use-package org :ensure t :config (setf org-directory "~/org/" org-agenda-files '("~/org/seminars.org" "~/org/lectures.org") org-default-notes-file (expand-file-name "notes.org" org-directory) org-ellipsis " ▼" org-log-done 'time org-hide-emphasis-markers nil ;;change to t to hide emphasis markers org-table-convert-region-max-lines 20000 org-log-done 'time org-todo-keywords '((sequence "TODO(t)" "SEMINAR(s)" "LECTURE(l)" "DONE(d)"))) (setf org-imenu-depth 3) (setf org-structure-template-alist '(("e" . "src emacs-lisp") ("p" . "src python") ("l" . "src lisp") ("b" . "src bash") ("q" . "QUOTE"))) (require 'ox-latex) (add-to-list 'org-latex-classes '("article" "\\documentclass[11pt,a4paper]{article} \\usepackage[utf8]{inputenc} \\usepackage[T1]{fontenc} \\usepackage{fixltx2e} \\usepackage{graphicx} \\usepackage{longtable} \\usepackage{float} \\usepackage{wrapfig} \\usepackage{rotating} \\usepackage[normalem]{ulem} \\usepackage{amsmath} \\usepackage{textcomp} \\usepackage{marvosym} \\usepackage{wasysym} \\usepackage{amssymb} \\usepackage{hyperref} \\usepackage{mathpazo} \\usepackage{color} \\usepackage{enumerate} \\definecolor{bg}{rgb}{0.95,0.95,0.95} \\tolerance=1000 [NO-DEFAULT-PACKAGES] [PACKAGES] [EXTRA] \\linespread{1.1} \\hypersetup{pdfborder=0 0 0}" ("\\section{%s}" . "\\section*{%s}") ("\\subsection{%s}" . "\\subsection*{%s}") ("\\subsubsection{%s}" . "\\subsubsection*{%s}") ("\\paragraph{%s}" . "\\paragraph*{%s}"))) (push 'org-self-insert-command completion-preview-commands) :hook ((org-mode . (lambda () (display-line-numbers-mode -1) (flyspell-mode)))) :bind (:map org-mode-map (("C-c l" . org-store-link) ("C-c M-t" . org-todo) ("C-c RET" . org-table-hline-and-move) ("C-M-i" . completion-at-point)))) ;; (completion-styles-alist ) ;; Export (defun thanos/org-html-remove-curly-braces (text backend info) "Remove curly braces for superscripts and subscripts in HTML export." (when (org-export-derived-backend-p backend 'html 'latex) (replace-regexp-in-string "{\\([^}]*?\\)}" "\\1" text))) (use-package emacs :ensure nil :config (if is-zeus (display-battery-mode 0) (display-battery-mode 1)) ;; use ssh for tramp, faster for small files (setf tramp-default-method "ssh") (defun thanos/dired-gnosis-rename (&optional file) (interactive) (let ((file (or file (dired-get-filename))) (name (read-string "File name: "))) (dired-rename-file file (expand-file-name name gnosis-images-dir) nil))) (savehist-mode) (save-place-mode 1) (recentf-mode 1) (electric-pair-mode 1) :bind (("M-" . 'backward-kill-sexp) ("C-c L" . 'display-line-numbers-mode) ("C-z" . nil) ("C-c r" . 'rename-visited-file) ("C-x 7" . 'window-swap-states) ("C-c t" . 'create-text-scratch)) :hook ((emacs-lisp-mode . prettify-symbols-mode) (emacs-lisp-mode . thanos/add-custom-keywords) (lisp-mode . prettify-symbols-mode) (lisp-mode . thanos/add-custom-keywords) (scheme-mode . prettify-symbols-mode) (scheme-mode . thanos/add-custom-keywords))) (use-package dired :ensure nil :config (defun dired-watch-video () "Watch play file with mpv." (interactive) (call-process-shell-command (format "mpv \"%s\"" (dired-get-filename)) nil 0)) (defun dired-set-wallpaper () "Set NAME as wallpaper using feh." (interactive) (thanos/wallpaper-set (dired-get-filename))) (defun dired-delete-files-except () "Delete all files inside directory except match." (interactive) (let* ((directory (read-directory-name "Select directory: ")) (files (directory-files directory t)) (except-match (read-string "Except the ones that have: "))) (dolist (file files) (unless (or (string= "." (substring file -1)) (string= ".." (substring file -2)) (string-match except-match file)) (dired-delete-file file t))))) (defun dired-delete-files-match (&optional directory match) "Delete all files inside directory except match." (interactive) (let* ((directory (or directory (read-directory-name "Select directory: "))) (files (directory-files directory t)) (match (or match (read-string "Delete files that match: ")))) (dolist (file files) (when (string-match-p match file) (dired-delete-file file t))))) (defun dired-rename-capitalize-file () "Capitalize the base name of the file at point in a Dired buffer." (interactive) (let* ((file (dired-get-file-for-visit)) (new-file (capitalize (file-name-nondirectory file)))) (if (string-prefix-p "." file) (message "Skipping file starting with '.'") (progn (rename-file file (concat (file-name-directory file) new-file)) (revert-buffer) (message "Renamed %s to %s" file new-file))))) :bind ((:map dired-mode-map ("b" . 'dired-up-directory) ("v" . 'dired-watch-video) ("z" . 'wdired-change-to-wdired-mode) (";" . 'dired-set-wallpaper) ("C-c d" . 'dired-delete-files-except) ("C-c r" . 'dired-do-query-replace-regexp)))) (defun theme-invisible-dividers (_theme) "Make window dividers for THEME invisible." (let ((bg (face-background 'default))) (custom-set-faces `(fringe ((t :background ,bg :foreground ,bg))) `(window-divider ((t :background ,bg :foreground ,bg))) `(window-divider-first-pixel ((t :background ,bg :foreground ,bg))) `(window-divider-last-pixel ((t :background ,bg :foreground ,bg)))))) (add-hook 'enable-theme-functions #'theme-invisible-dividers) ;;;; Theming ;;;; (setf inhibit-startup-message t initial-scratch-message nil) (blink-cursor-mode -1) (global-visual-line-mode 0) (setf visible-bell nil display-line-numbers-type 'relative) (column-number-mode) (global-display-line-numbers-mode 1) ;; Transparency (add-to-list 'default-frame-alist '(alpha-background . 85)) ;; Theming (defun thanos/terminal-theming () "Customize theming when laucning Emacs as TUI." (unless (display-graphic-p (selected-frame)) (set-face-background 'default "unspecified-bg" (selected-frame)) (global-hl-line-mode 0))) (add-hook 'window-setup-hook 'thanos/terminal-theming) (when (equal is-phone nil) (scroll-bar-mode -1) (set-fringe-mode 10)) (tool-bar-mode -1) (tooltip-mode -1) (menu-bar-mode -1) (defun org-insert-book () "Insert org-link from ~/Library for book." (interactive) (let* ((book-path (read-file-name "Book: " "~/Library/"))) (org-insert-link nil book-path (file-name-base book-path)))) (use-package org-modern :ensure t :config (setf org-modern-table nil org-modern-todo nil org-modern-tag nil org-modern-timestamp nil org-modern-star 'replace org-modern-list '((?+ . "•") (?- . "•")) org-modern-replace-stars "☧") :hook ((org-mode . org-modern-mode))) ;; Create notes directory for org-roam (unless (file-exists-p "~/Notes") (make-directory "~/Notes")) ;; (use-package org-roam ;; :vc (:url "https://github.com/org-roam/org-roam")) ;; (use-package org-roam-ui ;; :vc (:url "https://github.com/org-roam/org-roam-ui") ;; :after org-roam ;; :config ;; (setq org-roam-ui-sync-theme t ;; org-roam-ui-follow t ;; org-roam-ui-update-on-save t ;; org-roam-ui-open-on-start t)) (unless (or is-phone is-uranus) (use-package modus-themes :ensure t :config (setf modus-themes-italic-constructs nil modus-themes-bold-constructs nil modus-themes-mixed-fonts nil modus-themes-variable-pitch-ui nil modus-themes-custom-auto-reload t modus-themes-disable-other-themes t modus-themes-prompts '(italic) modus-themes-completions '((matches . (underline)) (selection . (semibold italic text-also underline))) modus-themes-org-blocks 'tinted-background) ;; Palette overrides (setf modus-themes-common-palette-overrides '((fg-line-number-active cyan-intense) ;; (bg-main "#1d2021") ;;grubox-hard ;; (bg-main "#191919") ;; 1337 ;; (bg-main "#1d1f21") ;; tomorrow night ;; (bg-main "#151515") ;; jazz ;; (bg-main "#0C0C0C") ;; random black ;; (bg-main "#171717") ;; badger ;; (overline-heading-1 gold) (fg-heading-1 red-warmer) ;; (bg-heading-1 bg-blue-nuanced) (bg-line-number-inactive unspecified) (bg-line-number-active unspecified) (bg-paren-match bg-magenta-intense) (underline-paren-match fg-main) (underline-err red-intense) (underline-warning yellow-faint) (underline-note cyan-faint) ;; (string "#86B187") (border-mode-line-active unspecified) (border-mode-line-inactive unspecified) (bg-mode-line-active "#433F4f") ;; subtle lavender (bg-mode-line-inactive "#1D1D1D") ;; set fg from badger theme (fg-mode-line-active "#F6F3E8") (bg-hl-line bg-dim) (cursor slate) (prose-todo green-intense) (prose-done bg-term-white) (fg-prompt yellow-faint) ,@modus-themes-preset-overrides-intense)) ;; Headings (setf modus-themes-headings '((1 . (ultrabold 1.35)) (2 . (semibold 1.2)) (agenda-date . (1.3)) (agenda-structure . (variable-pitch light 1.8)) (t . (1.15)))) ;; Load modus (load-theme 'modus-vivendi t))) (use-package which-key :ensure t :config (which-key-mode 1)) ;; Python (add-to-list 'auto-mode-alist '("\\.py\\'" . python-ts-mode)) ;; Lisp (use-package sly :init (setf inferior-lisp-program "sbcl") :ensure t) (use-package vc :ensure t :config (defun vc-git--commit-hash-at-point () "Return commit hash at point." (let ((hash (thing-at-point 'word t))) (and (stringp hash) (string-match-p "^[0-9a-f]\\{7,40\\}$" hash) hash))) (defun vc-git-reset-to-commit-at-point (&optional hash) "Reset the current branch to the commit at point in the vc log buffer." (let ((commit-hash (or (thing-at-point 'word t) hash))) (vc-git--reset commit-hash))) (defun vc-git--reset (commit-hash) "Reset to commit HASH." (if (and commit-hash (string-match-p "^[0-9a-f]\\{7,40\\}$" commit-hash)) (if (yes-or-no-p (format "Reset current branch to commit %s?" commit-hash)) (progn (let ((default-directory (vc-root-dir))) (vc-git-command nil 0 nil "reset" "--hard" commit-hash) (message "Reset to commit %s completed." commit-hash))) (message "Reset cancelled.")) "Invalid commit hash")) (defun vc-git--select-commit () (let* ((history-add-new-input nil) (commit-strings (split-string (shell-command-to-string "git log --oneline") "\n" t)) (selected (completing-read "Select commit: " commit-strings nil t)) (selected-hash (car (split-string selected)))) selected-hash)) (defun vc-git-reset (&optional hash) (interactive) (vc-git--reset (or hash (vc-git--commit-hash-at-point) (vc-git--select-commit)))) (defun vc-git-format-patches (num) "Format patches for NUM of last commits" (interactive (list (read-number "Number of commits: "))) (vc-git-command nil 0 nil "format-patch" (format "-%d" num)) (message "Done.")) (defun vc-git-reword-commit () "Edit current commit message" (interactive) (vc-checkin nil 'git nil nil nil "") (vc-git-log-edit-toggle-amend))) ;; (use-package corfu ;; :ensure t ;; :config ;; (global-corfu-mode) ;; (corfu-popupinfo-mode) ;; (setf corfu-auto t ;; corfu-auto-delay 0.1 ;; corfu-auto-prefix 2 ;; corfu-cycle t ;; corfu-popupinfo-delay 0.3 ;; corfu-quit-at-boundary 'separator ;; corfu-quit-no-match t ;; corfu-preselect 'first ;; corfu-preview-current t ;; corfu-echo-mode t) ;; (setf indent-tabs-mode nil)) (when (or is-phone is-uranus) (use-package corfu-terminal)) (defun insert-brackets (&optional arg) "Insert ARG brackets." (interactive "P") (insert-pair arg ?\[ ?\])) (global-set-key (kbd "C-x M-[") 'insert-brackets) (use-package pdf-tools :ensure nil :config (add-to-list 'auto-mode-alist '("\\.pdf\\'" . pdf-view-mode)) :hook ((pdf-view-mode . (lambda () (display-line-numbers-mode 0) ;; Guix package emacs-pdf-tools does not enable ;; minor modes automatically (pdf-tools-enable-minor-modes))))) (use-package nov :ensure nil :config (add-to-list 'auto-mode-alist '("\\.epub\\'" . nov-mode))) (use-package eshell-syntax-highlighting :vc (:url "https://github.com/akreisher/eshell-syntax-highlighting") :ensure t :after eshell :config (eshell-syntax-highlighting-global-mode 1)) (use-package emojify :ensure t :hook (erc-mode . emojify-mode) :commands emojify-mode) (use-package flycheck :ensure t :config (setf flycheck-emacs-lisp-load-path 'inherit) (global-flycheck-mode)) (use-package eat :ensure t :config (setf eshell-visual-commands nil eat-term-name "xterm-256color") :bind (("C-c v" . eat)) :hook ((eshell-mode . eat-eshell-mode) (eshell-mode . eat-eshell-visual-command-mode))) (use-package shell :ensure t :bind (("C-c V" . shell) :map shell-mode-map ("C-l" . 'comint-clear-buffer)) :hook ((shell-mode . (lambda () (display-line-numbers-mode -1))))) (defvar thanos/aliases '((d . dired) (o . find-file) (oo . find-file-other-window) (ll . (lambda () (eshell/ls '-lha))) (eshell/clear . eshell/clear-scrollback))) (defun thanos/set-eshell-aliases (aliases) "Set ALIASES as eshell aliases." (mapc (lambda (alias) (defalias (car alias) (cdr alias))) aliases)) (defun thanos/eshell-clear () "Interactive call for clear-scrollback." (interactive) (eshell/clear-scrollback)) (defun thanos/eshell-preview-insert () (interactive) (completion-preview-insert) (delete-char -1)) (use-package eshell :ensure t :config (setf eshell-highlight-prompt t) :bind (("C-c e" . eshell) :map eshell-mode-map ("C-l" . 'thanos/eshell-clear) ("" . 'thanos/eshell-preview-insert)) :hook ((eshell-mode . (lambda () (thanos/set-eshell-aliases thanos/aliases) (display-line-numbers-mode -1))))) (use-package eshell-git-prompt :vc (:url "https://github.com/xuchunyang/eshell-git-prompt") :ensure t :config (defun eshell-git-prompt-multiline () "Eshell Git prompt inspired by spaceship-prompt." (let (separator hr dir git git-dirty time sign command) (setq separator (with-face " | " 'eshell-git-prompt-multiline-secondary-face)) (setq hr (with-face (concat "\n" (make-string (/ (window-total-width) 2) ?─) "\n") 'eshell-git-prompt-multiline-secondary-face)) (setq dir (concat (with-face "" 'eshell-git-prompt-directory-face) (concat (abbreviate-file-name (eshell/pwd))))) (setq git (concat (with-face "⎇" 'eshell-git-prompt-exit-success-face) (concat (eshell-git-prompt--branch-name)))) (setq git-dirty (when (eshell-git-prompt--branch-name) (if (eshell-git-prompt--collect-status) (with-face " ✎" 'eshell-git-prompt-modified-face) (with-face " ✔" 'eshell-git-prompt-exit-success-face)))) (setq time (with-face (format-time-string "%I:%M:%S %p") 'eshell-git-prompt-multiline-secondary-face)) (setq sign (if (= (user-uid) 0) (with-face "\n#" 'eshell-git-prompt-multiline-sign-face) (with-face "\nλ" 'eshell-git-prompt-multiline-sign-face))) (setq command (with-face " " 'eshell-git-prompt-multiline-command-face)) ;; Build prompt (eshell-git-prompt---str-read-only (concat hr dir separator git git-dirty separator time sign command)))) (eshell-git-prompt-use-theme 'multiline)) ;; Password-store (use-package password-store :init (define-prefix-command 'thanos/pass) :defer t :config (setf password-store-password-length (+ 20 (random 20))) :bind (("C-c p" . 'thanos/pass) :map thanos/pass ("i" . 'password-store-insert) ("e" . 'password-store-edit) ("g" . 'password-store-generate) ("c" . 'password-store-copy) ("s" . 'smtp-get-pass))) ;; Chat (use-package erc :ensure t :config (unless (expand-file-name "erc" user-emacs-directory) (make-directory (expand-file-name "erc" user-emacs-directory))) (setf erc-modules '(netsplit fill button match track completion readonly networks ring autojoin noncommands irccontrols move-to-prompt stamp menu list log notifications) erc-log-channels-directory (expand-file-name "erc" user-emacs-directory)) (defun thanos/erc-login () "Login to libera.chat" (interactive) (erc :server "uranus" :port 5555 :nick "thanosapollo" :user "thanosapollo" :password (password-store-get "znc/admin"))) :bind (("C-c E" . 'erc-libera) :map erc-mode-map ("C-c RET" . 'erc-cmd-QUERY))) (use-package sudo-edit :ensure t :config (setf sudo-edit-local-method "sudo")) (use-package dabbrev :defer t :config (setf dabbrev-ignored-buffer-regexps '("\\.\\(?:pdf\\|jpe?g\\|png\\)\\'"))) ;; My packages (when (or is-zeus is-hermes) (use-package yeetube :init (define-prefix-command 'thanos/yeetube-map) :vc t :load-path "~/Dev/emacs-lisp/yeetube" :ensure t :config (setf yeetube-results-limit 20 yeetube-mpv-disable-video t yeetube-display-thumbnails t yeetube-play-function #'yeetube-mpv-play) (defun yeetube-download-videos-ffmpeg () "Download videos using ffmpeg." (interactive) (let ((url "") (name "") (download-counter 1) (stored-contents nil)) ;; Read links and names until "q" is entered (while (not (string= url "q")) (setf url (read-string "Enter URL (q to quit): ")) (unless (string= url "q") (setf name (read-string (format "Custom name (download counter: %d) " download-counter))) (push (cons url name) stored-contents) (setf download-counter (1+ download-counter)))) ;; Process the collected links and names (dolist (pair stored-contents) (let ((url (car pair)) (name (cdr pair))) (async-shell-command (format "ffmpeg -protocol_whitelist file,crypto,data,https,tls,tcp -stats -i '%s' -codec copy '%s.mp4'" url name)))))) (defun yeetube-download-vimeo-videos () "Download videos from vimeo services." (interactive) (let ((url "") (name "") (download-counter 1)) (while (not (string= url "q")) (setf url (read-string "Enter URL (q to quit): ")) (unless (string= url "q") (setf name (read-string (format "Custom name (download counter: %d) " download-counter))) (setf download-counter (1+ download-counter)) (call-process-shell-command (format "yt-dlp '%s' -o '%s'" (replace-regexp-in-string "\\.json" ".m3u8" url) name) nil 0))))) :bind (("C-c y" . 'thanos/yeetube-map) :map thanos/yeetube-map ("s" . 'yeetube-search) ("b" . 'yeetube-play-saved-video) ("d" . 'yeetube-download-videos) ("C-d" . 'yeetube-download-vimeo-videos) ("p" . 'yeetube-mpv-toggle-pause) ("v" . 'yeetube-mpv-toggle-video) ("V" . 'yeetube-mpv-toggle-no-video-flag) ("C-p" . 'yeetube-mpv-toggle-video) ("k" . 'yeetube-remove-saved-video))) (use-package gnosis :load-path "~/Dev/emacs-lisp/gnosis" :ensure t :init (define-prefix-command 'thanos/gnosis-map) :config (setf gnosis-vc-auto-push t gnosis-mcq-display-choices nil gnosis-image-width (and is-hermes 150) gnosis-image-height (and is-hermes 150)) (gnosis-modeline-mode) (setf gnosis-custom-values '((:deck "Bulgarian" (:amnesia 0.55 :proto (0 1 3))) (:deck "Unking" (:amnesia 0.45)) (:tag "vocabulary" (:amnesia 0.65 :proto (0 1 3))))) :bind (("C-c g" . 'thanos/gnosis-map) :map thanos/gnosis-map ("a" . 'gnosis-add-note) ("d" . 'gnosis-dashboard))) ;; Sync gnosis on startup (gnosis-vc-pull) (use-package org-gnosis :vc t :load-path "~/Dev/emacs-lisp/org-gnosis" :init (define-prefix-command 'thanos/notes-map) (define-prefix-command 'thanos/journal-map) :config (setf org-gnosis-dir "~/Notes" org-gnosis-journal-template "* Daily Notes\n\n* Goals\n+ [] Πρωινὴ Προσευχή\n+ [] Ἑσπερινή Προσευχή\n\n* Extras" org-gnosis-show-tags t) (defun thanos/org-open-at-point () "Open Org link at point in the same window, if possible." (interactive) (let ((org-link-frame-setup '((file . find-file)))) (org-open-at-point))) :bind (("C-c n" . thanos/notes-map) ("C-c j" . thanos/journal-map) :map thanos/notes-map ("f" . org-gnosis-find) ("i" . org-gnosis-insert) :map thanos/journal-map ("j" . org-gnosis-journal) ("f" . org-gnosis-journal-find) ("i" . org-gnosis-journal-insert) :map org-mode-map ("C-c C-." . org-gnosis-insert-tag) ("C-c i" . org-id-get-create) ("C-c C-o" . thanos/org-open-at-point))) (use-package pcmpl-tailscale :vc t :load-path "~/Dev/emacs-lisp/pcmpl-tailscale" :ensure nil) (use-package greek-polytonic :vc t :ensure t :load-path "~/Dev/emacs-lisp/greek-polytonic")) (use-package elfeed :defer t :vc (:url "https://github.com/skeeto/elfeed") :config (setf elfeed-search-filter "@1-week-ago +unread" browse-url-browser-function #'browse-url-default-browser elfeed-db-directory "~/.config/elfeed") ;; Feeds (setf elfeed-feeds '(("https://hackaday.com/blog/feed/" hackaday linux) ("https://thanosapollo.org/index.xml" thanos) ("http://wikileaks.org/feed" wikileaks) ("https://hackernoon.com/feed" hackernoon) ("https://torrentfreak.com/feed" torrentfreak piracy) ("https://www.science.org/action/showFeed?type=etoc&feed=rss&jc=sciimmunol" science) ("http://localhost/?action=display&bridge=CssSelectorBridge&home_page=https%3A%2F%2Fwww.medscape.com%2Findex%2Flist_13470_0&url_selector=a.title&url_pattern=viewarticle%2F.*&content_selector=div.article__main-content&content_cleanup=&title_cleanup=+-+Index&limit=&format=Atom" medscape med) ("http://localhost/?action=display&bridge=CssSelectorBridge&home_page=https%3A%2F%2Fwww.tovima.gr%2Flatest-news%2F&url_selector=a.columns&url_pattern=&content_selector=div.main-content&content_cleanup=div.wrap-facebook%2Cdiv.googlenews&title_cleanup=&limit=&format=Atom" tovima greek news) ("http://localhost/?action=display&bridge=CssSelectorBridge&home_page=https%3A%2F%2Fwww.estianews.gr%2Feidiseis-arthra%2F&url_selector=h3.entry-title&url_pattern=&content_selector=div.col-lg-8&content_cleanup=&title_cleanup=&limit=&format=Atom" estia greek news) ("http://localhost/?action=display&bridge=CssSelectorBridge&home_page=https%3A%2F%2Fwww.estianews.gr%2Fkentriko-thema%2F&url_selector=h3.entry-title&url_pattern=&content_selector=div.col-md-8&content_cleanup=&title_cleanup=&limit=&format=Atom" estia greek kyrio) ("http://localhost/?action=display&bridge=CssSelectorBridge&home_page=https%3A%2F%2Fwww.estianews.gr%2Fapopseis%2F&url_selector=h3.entry-title&url_pattern=&content_selector=div.col-lg-8&content_cleanup=&title_cleanup=&limit=&format=Atom" estia greek opinions) ("http://localhost/?action=display&bridge=CssSelectorBridge&home_page=https%3A%2F%2Fwww.reuters.com%2Fbusiness%2Fhealthcare-pharmaceuticals%2F&url_selector=a.basic-card__title__37xHl&url_pattern=&content_selector=div.article-body__wrapper__3IxHM&content_cleanup=svg.link__new-tab-symbol__3T19s%2C+div.toolbar__container__3kIkw%2C+div.article-body__row__dFOPA+article-body__element__2p5pI&title_cleanup=&limit=&format=Atom" reuters med news) ("http://localhost/?action=display&bridge=CssSelectorBridge&home_page=https%3A%2F%2Fwww.reuters.com%2Ftechnology%2Fcybersecurity%2F&url_selector=a.media-story-card__headline__tFMEu%2C+a.media-story-card__heading__eqhp9&url_pattern=&content_selector=article.article__container__2MUeZ&content_cleanup=div.info-content__toolbar__3AkHm%2C+svg.link__new-tab-symbol__3T19s%2C+div.article-body__row__dFOPA%2C+div.read-next-tablet-up__container__3MpHN%2C+div.author-bio__multiple-authors__5YGrG%2C+div.article__read-next__Kjxdw&title_cleanup=&limit=&format=Atom" reuters cybersec news) ("https://annas-blog.org/ rss.xml" anna piracy) ("http://localhost/?action=display&bridge=CssSelectorBridge&home_page=https%3A%2F%2Fwww.ethnos.gr%2Ftag%2F842%2Fellhnotoyrkika&url_selector=a.single-title&url_pattern=&content_selector=div.content-section&content_cleanup=div.article-related-posts%2C+div.ReadMore%2C+script&title_cleanup=&limit=&format=Atom" greek ethnos ellinotourkika) ("https://planet.emacslife.com/atom.xml" emacs emacslife) ("https://localmonero.co/static/rss/the-monero-standard/feed.xml" monero) ("https://devonzuegel.com/feed" devon) ("https://www.addtoany.com/add_to/feed?linkurl=http%3A%2F%2Fwww.thelancet.com%2Frssfeed%2Flancet_online.xml&type=feed&linkname=The%20Lancet%20Online%20First&linknote=" lancet med) ("https://www.propublica.org/feeds/propublica/main" probublica news) ("http://tools.cdc.gov/podcasts/feed.asp?feedid=183" cdc med) ("http://planet.lisp.org/rss20.xml" lisp planetlisp) ("https://guix.gnu.org/feeds/blog.atom" guix) ("https://protesilaos.com/master.xml" prot) ("https://static.fsf.org/fsforg/rss/news.xml" gnu fsf) ;; YouTube ("https://www.youtube.com/feeds/videos.xml?channel_id=UCVls1GmFKf6WlTraIb_IaJg" distrotube yt linux) ("https://www.youtube.com/feeds/videos.xml?channel_id=UCld68syR8Wi-GY_n4CaoJGA" brodie yt linux) ("https://www.youtube.com/feeds/videos.xml?channel_id=UCAiiOTio8Yu69c3XnR7nQBQ" systemcrafters yt linux) ("https://www.youtube.com/feeds/videos.xml?channel_id=UC6QYFutt9cluQ3uSM963_KQ" ninja yt med) ("https://www.youtube.com/feeds/videos.xml?channel_id=UC1yNl2E66ZzKApQdRuTQ4tw" sabine yt physics) ("https://www.aartaka.me.eu.org/" artyom blog lisp) ("https://nyxt-browser.com/feed" nyxt lisp))) (defun elfeed-mpv (&optional use-generic-p) "Play video link with mpv." (interactive "P") (let ((entries (elfeed-search-selected))) (cl-loop for entry in entries do (elfeed-untag entry 'unread) when (elfeed-entry-link entry) do (start-process-shell-command "elfeed-video" nil (format "mpv \"%s\"" it))) (mapc #'elfeed-search-update-entry entries) (unless (use-region-p) (forward-line)))) :bind (("C-x f" . elfeed) :map elfeed-search-mode-map ("v" . 'elfeed-mpv) ("U" . 'elfeed-update)) :hook ((elfeed-searchacw-mode . (lambda () (display-line-numbers-mode 0))))) (use-package transmission :vc (:url "https://github.com/holomorph/transmission") :defer t) (use-package rainbow-delimiters :vc (:url "https://github.com/Fanael/rainbow-delimiters") :ensure t :hook ((emacs-lisp-mode . rainbow-delimiters-mode) (lisp-mode . rainbow-delimiters-mode) (clojure-mode . rainbow-delimiters-mode) (scheme-mode . rainbow-delimiters-mode))) ;; AI tools (use-package gptel :ensure t :config (setf gptel-api-key (password-store-get-field "openai/openai@thanosapollo.org" "api") gptel-default-mode 'org-mode gptel-directives '((default . "You are a large language model living in Emacs and a helpful assistant. Respond concisely.") (programming . "You are a large language model and an expert emacs lisp hacker. Provide code and only code as output without any additional text, prompt or note.") (epictetus . "You are Epictetus, the stoic philosopher from Nicopolis. Respond concisely as Epictetus.") (med . "You are a medical professor. Respond concisely to your student in bullet points.") (code-review . "You are an expert programmer within Emacs reviewing code. Respond concisely") (writer . "You are an expert writer and FOSS enthusiast. Improve only the article sections provided as a hacker, do not add extra paragraphs."))) (setq-default gptel-model "llama3.2:latest") (setq gptel-backend (gptel-make-ollama "Ollama" :host (if is-zeus "localhost:11434" "zeus:11434") :stream t :models '("llama3.2:latest" "dolphin-phi" "dolphin-llama3:latest"))) (gptel-make-anthropic "Claude" :stream t :key (password-store-get "claude/api1")) :bind (("C-c g" . 'gptel-send) :map gptel-mode-map ("C-c h" . 'gptel-menu))) (use-package package-lint :defer t) (use-package yaml-mode :defer t) (defun thanos/notmuch-decrypt-script () (let ((encrypted-script (expand-file-name "notmuch-hook.sh.gpg" "~/.scripts")) (script (expand-file-name "notmuch-hook.sh" "~/.scripts"))) (unless (file-exists-p script) (epa-decrypt-file encrypted-script script) (set-file-modes script #o700)))) (defun thanos/notmuch-update--command (new-buffer) (let ((default-directory "~/")) (async-shell-command "mbsync -a; .scripts/notmuch-hook.sh" new-buffer))) (defun thanos/notmuch-update () (interactive) (let ((new-buffer (generate-new-buffer "*notmuch update*")) (height (max 5 (truncate (* (frame-height) 0.1))))) (split-window-vertically (- height)) (other-window 1) (switch-to-buffer new-buffer) (thanos/notmuch-decrypt-script) (let ((proc (thanos/notmuch-update--command new-buffer))) (set-process-sentinel (get-buffer-process new-buffer) (lambda (process event) (when (memq (process-status process) '(exit signal)) (delete-window (get-buffer-window (process-buffer process))) (kill-buffer (process-buffer process)))))))) (use-package notmuch :defer t :bind (("C-x m" . notmuch-hello) :map notmuch-hello-mode-map ("u" . notmuch-hello-update) ("U" . thanos/notmuch-update) :map notmuch-search-mode-map ("u" . notmuch-refresh-all-buffers)) :hook ((notmuch-hello-mode . (lambda () (display-line-numbers-mode 0))) (notmuch-search-mode . (lambda () (display-line-numbers-mode 0) (emojify-mode))))) (setf notmuch-archive-tags '("-inbox" "-unread" "+archived") notmuch-show-all-tags-list t notmuch-hello-sections '(notmuch-hello-insert-header notmuch-hello-insert-saved-searches notmuch-hello-insert-alltags)) (setq notmuch-search-oldest-first nil) (setf notmuch-tag-formats '(("unread" (propertize tag 'face 'notmuch-tag-unread)) ("emacs-pm" (propertize tag 'face '((t :foreground "#ff9580")))))) (setf notmuch-identities '("public@thanosapollo.org" "public@thanosapollo.com" "104111@students.mu-sofia.bg")) (setf notmuch-saved-searches `((:name "Inbox" :query "tag:inbox" :sort-order newest-first :key ,(kbd "i")) (:name "Unread" :query "tag:unread" :sort-order newest-first :key ,(kbd "u")) (:name "Today's message" :query "tag:inbox date:today" :sort-order newest-first :key ,(kbd "t")) (:name "sent" :query "tag:sent" :sort-order newest-first :key ,(kbd "s")) (:name "drafts" :query "tag:draft" :sort-order newest-first :key ,(kbd "d")) (:name "all mail" :query "*" :sort-order newest-first :key ,(kbd "a")))) ;; Update notmuch on startup ;; (thanos/notmuch-update) ;; smtpmail settings (setf smtpmail-smtp-user (password-store-get-field "mailbox/thanosapollo" "user") smtpmail-smtp-server "smtp.mailbox.org" smtpmail-smtp-service 465 smtpmail-stream-type 'ssl message-send-mail-function 'smtpmail-send-it message-signature "Thanos Apollo ☧\nhttps://thanosapollo.org") ;; autosign messages (add-hook 'message-send-hook 'mml-secure-message-sign-pgpmime) ;; (use-package yasnippet ;; :ensure nil ;; :config ;; (when is-zeus (add-to-list 'yas-snippet-dirs "~/Dev/guile/guix/etc/snippets/yas"))) (use-package emms :ensure t :init (define-prefix-command 'thanos/emms) :config (with-eval-after-load 'emms (emms-all) (setf emms-source-file-default-directory "/hdd/Music" emms-info-asynchronously t emms-show-format "♪ %s" emms-player-list '(emms-player-mpv)) (add-to-list 'emms-info-functions 'emms-info-native)) (setf emms-player-mpv-parameters '("--quiet" "--really-quiet" "--no-audio-display" "--no-video")) :bind (("C-z" . thanos/emms) :map thanos/emms ("n" . 'emms-next) ("p" . 'emms-previous) ("SPC" . 'emms-pause) ("e" . 'emms) ("s" . 'emms-browser-search-by-names) ("C-e" . 'emms-smart-browse) :map emms-playlist-mode-map (("A" . 'emms-add-directory-tree)) :map emms-browser-search-mode-map (("s" . 'emms-browser-search-by-names)))) (use-package debbugs :ensure nil :config (require 'bug-reference) (add-hook 'prog-mode-hook #'bug-reference-prog-mode) (add-hook 'gnus-mode-hook #'bug-reference-mode) (add-hook 'erc-mode-hook #'bug-reference-mode) (add-hook 'gnus-summary-mode-hook #'bug-reference-mode) (add-hook 'gnus-article-mode-hook #'bug-reference-mode) ;; Change the default when run as 'M-x debbugs-gnu'. (setq debbugs-gnu-default-packages '("emacs" "guix")) ;; Show feature requests. (setq debbugs-gnu-default-severities '("serious" "important" "normal" "minor" "wishlist"))) ;;; Random commands ;;;; ;;;;;;;;;;;;;;;;;;;;;;;; ;; Virtual machines (defvar vm-directory "~/Virtual/") (defun vm-create-image () "Create qcow2 image." (interactive) (let ((name (format "%s%s.qcow2" vm-directory (read-string "Name: "))) (size (format "%s" (read-string "Size(G): ")))) (shell-command (format "qemu-img create -f qcow2 %s %sG" name size)))) (defun vm-run () "Spawn Virtual Machine with UEFI support." (interactive) (let ((memory (format "%sG" (read-string "Memory(G): "))) (cores (read-string "Cores: ")) (image (read-file-name "Image: " vm-directory)) (iso (if (y-or-n-p "Load iso? ") (read-file-name "ISO: ") nil)) (ovmf-path (string-trim (shell-command-to-string "echo $(guix build ovmf-x86-64)/share/firmware/ovmf_x64.bin")))) (async-shell-command (format "qemu-system-x86_64 -enable-kvm -m %s -smp %s -hda %s -bios %s -vga virtio -device virtio-serial-pci -netdev user,id=vmnic,hostfwd=tcp::2222-:22 -device e1000,netdev=vmnic %s" memory cores image ovmf-path (if iso (concat "-cdrom " iso) ""))))) (use-package 0x0 :ensure nil) ;; Misc Functions ;; ;;;;;;;;;;;;;;;;;;;; (defun thanos/run-in-background (command) "Run COMMAND in the background." (let ((command-parts (split-string command "[ ]+"))) (apply #'call-process `(,(car command-parts) nil 0 nil ,@(cdr command-parts))))) (defmacro thanos/make-frame (name &rest body) "Create temporary frame as NAME. Create a temporary frame to execute BODY, which will then be deleted." `(unwind-protect (with-selected-frame (make-frame '((name . ,name) (fullscreen . 0) (undecorated . t) (minibuffer . only) (width . 70) (height . 15))) ,@body (delete-frame)))) (defun create-text-scratch () "Create a scratch buffer." (interactive) (switch-to-buffer (get-buffer-create "*Text Scratch*")) (org-mode)) (defun create-scratch () "Create scratch buffer." (interactive) (switch-to-buffer (get-buffer-create "*scratch*")) (emacs-lisp-mode)) (defvar-keymap thanos/create-map :doc "Create custom buffers" "t" #'create-text-scratch "e" #'create-scratch) ;; Theming (defvar wallpapers-dir "~/wallpapers/" "Wallpaper directory.") (defvar wallpaper-current nil "Current wallpaper.") (defun thanos/load-theme (&optional theme) "Disable current theme and load a new THEME." (interactive) (let ((theme (or theme (intern (completing-read "Theme: " (custom-available-themes)))))) (disable-theme (car custom-enabled-themes)) (load-theme theme t))) (defun thanos/wallpaper-set (image) "Set IMAGE as wallpaper, using feh." (let ((command "swaybg -o '*' -i")) ;; Kill previous swaybg (call-process-shell-command "kill -15 $(pgrep swaybg | tail -n 1)") ;; Set wallpaper (call-process-shell-command (format "%s %s -m stretch" command image) nil 0))) (defun thanos/wallpaper-random () "Set random wallpaper." (interactive) (let ((wallpapers (directory-files "~/wallpapers" nil "^[^.].*"))) (thanos/wallpaper-set (format "%s%s" wallpapers-dir (nth (random (length wallpapers)) wallpapers))))) (defun thanos/wallpaper-select () "Set wallpaper." (interactive) (let ((wallpaper (format "%s%s" wallpapers-dir (completing-read "Choose wallpaper: " (directory-files wallpapers-dir nil "^[^.].*"))))) (thanos/wallpaper-set wallpaper) (setf wallpaper-current wallpaper))) (defun thanos/wallpaper-watcher (_symbol new-value _where _environment) "Watch for wallpaper changes." (with-temp-buffer (find-file (expand-file-name "wallpaper" user-emacs-directory)) (erase-buffer) (setf wallpaper-current new-value) (insert (pp-to-string wallpaper-current)) (save-buffer) (kill-buffer))) (defun thanos/load-wallpaper () "Load saved wallpaper." (let ((wallpaper-path (expand-file-name "wallpaper" user-emacs-directory))) (if (file-exists-p wallpaper-path) (with-temp-buffer (insert-file-contents wallpaper-path) (goto-char (point-min)) (let ((contents (read (current-buffer)))) (setf wallpaper-current contents))) (write-region "nil" nil wallpaper-path)))) (defun thanos/wallpaper-startup (&optional image) "Set wallpaper IMAGE on startup." (thanos/load-wallpaper) (let ((image (or image wallpaper-current))) (thanos/wallpaper-set image))) (add-variable-watcher 'wallpaper-current #'thanos/wallpaper-watcher) ;; Set wallpaper (when (or is-zeus is-hermes) (thanos/wallpaper-startup)) (defvar-keymap thanos/applications-map :doc "Thanos commonly used programs" "t" #'thanos/load-theme "w" #'thanos/wallpaper-select "C-c" thanos/create-map) (define-key global-map (kbd "C-c a") thanos/applications-map) (defun thanos/iimage-mode-buffer (arg) "Display images if ARG is non-nil, undisplay them otherwise." (let ((image-path (cons default-directory iimage-mode-image-search-path)) (edges (window-inside-pixel-edges (get-buffer-window))) file) (with-silent-modifications (save-excursion (dolist (pair iimage-mode-image-regex-alist) (goto-char (point-min)) (while (re-search-forward (car pair) nil t) (when (and (setq file (match-string (cdr pair))) (setq file (locate-file file image-path))) (if arg (add-text-properties (match-beginning 0) (match-end 0) `(display ,(create-image file nil nil :max-width 120 :max-height 120) keymap ,image-map modification-hooks (iimage-modification-hook))) (remove-list-of-text-properties (match-beginning 0) (match-end 0) '(display modification-hooks)))))))))) (define-minor-mode thanos/iimage-mode nil :group 'iimage :lighter " iImg" (thanos/iimage-mode-buffer thanos/iimage-mode)) ;; Password (defun thanos/pass-autotype (&optional entry) "Autotype password ENTRY." (let* ((entry (or entry (completing-read "Select entry: " (password-store-list)))) (user (password-store-get-field entry "user")) (pass (password-store-get entry))) (start-process-shell-command "wtype" nil (format "sleep 0.3 && wtype %s -P tab %s" (shell-quote-argument (if user user "thanosapollo")) (shell-quote-argument pass))))) (defun thanos/pass-launcher () "Launch Emacs as a front-end for pass." (interactive) (thanos/make-frame "thanos/pass-launcher" (let* ((choice (completing-read "Choose an action: " '("AUTO" "COPY PASS" "EDIT" "GENERATE"))) (action (pcase choice ("AUTO" #'thanos/pass-autotype) ("COPY PASS" #'password-store-copy) ("EDIT" #'password-store-edit) ("GENERATE" #'password-store-generate)))) (funcall action (completing-read "Search: " (password-store-list)))))) (defun smtp-get-pass () "Get password for smtp." (interactive) (password-store-copy-field "fastmail.com/thanosapollo@fastmail.com" "smtp")) (defun org-table-insert-numbers (str end) "Used to insert numbers for verses in org tables." (interactive "nStart: \nnEnd: ") (dotimes (i (1+ (- end str))) (insert (format "%s" (+ i str))) (org-table-hline-and-move))) (require 'server) (unless (server-running-p) (server-start)) ;;; init.el ends here