455 lines
13 KiB
EmacsLisp
455 lines
13 KiB
EmacsLisp
|
|
|
|
|
|
|
|
;;; relative line numbers
|
|
(setq display-line-numbers 'relative)
|
|
|
|
|
|
;;; set my org directory
|
|
(setq org-directory "~/work/things/org")
|
|
|
|
|
|
;;; set my name and email
|
|
(setq user-full-name "Sebastian Moser"
|
|
user-mail-address "c2vi.dev@gmail.com")
|
|
|
|
|
|
;;; theme
|
|
(setq doom-theme 'doom-one)
|
|
|
|
|
|
;;; C-p to be M-x
|
|
(map! "C-p" #'execute-extended-command)
|
|
;;(map! :leader "a" #'execute-extended-command)
|
|
;;(map! :nv ":" #'execute-extended-command)
|
|
(after! evil
|
|
;;(map! :nv ":" #'execute-extended-command)
|
|
(map! :gnv "C-p" #'execute-extended-command)
|
|
)
|
|
|
|
|
|
|
|
;;; projectile find file - quick switcher
|
|
(after! 'better-jumper
|
|
(map! :gnv "C-i" #'projectile-find-file)
|
|
)
|
|
(with-eval-after-load 'projectile
|
|
(add-to-list 'projectile-globally-ignored-directories "node_modules")
|
|
(add-to-list 'projectile-globally-ignored-directories ".obsidian")
|
|
(setq projectile-indexing-method 'native)
|
|
)
|
|
|
|
|
|
|
|
;;; obsidian
|
|
(after! obsidian
|
|
(setq obsidian-directory "~/work/things"
|
|
obsidian-inbox-directory "semi-public"
|
|
markdown-enable-wiki-links t
|
|
)
|
|
(global-obsidian-mode t)
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
;;; clipboard
|
|
(after! evil
|
|
(setq interprogram-cut-function nil
|
|
interprogram-paste-function nil)
|
|
)
|
|
(defun my/clipboard-copy (beg end)
|
|
"Copy region to the OS clipboard (bypassing the kill-ring)."
|
|
(interactive "r")
|
|
(let ((text (buffer-substring-no-properties beg end))
|
|
(inhibit-message t))
|
|
(gui-select-text text)
|
|
(deactivate-mark)
|
|
(when (called-interactively-p 'any)
|
|
(message "Copied to clipboard: %d chars" (length text)))))
|
|
|
|
(defun my/clipboard-paste ()
|
|
"Paste from the OS clipboard, replacing the region if active."
|
|
(interactive)
|
|
(let ((text (gui-get-selection 'CLIPBOARD)))
|
|
(unless text
|
|
(user-error "OS clipboard is empty"))
|
|
(when (use-region-p)
|
|
(delete-region (region-beginning) (region-end)))
|
|
(insert-for-yank text)))
|
|
(map! :v "C-c" #'my/clipboard-copy
|
|
:v "C-v" #'my/clipboard-paste
|
|
:i "C-v" #'my/clipboard-paste) ; insert-state paste, terminal-style
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;;; in buffer completions
|
|
(setq tab-always-indent 'complete)
|
|
(after! corfu
|
|
; possible: basic, initials, orderless, partial-completion substring, flex
|
|
(setq completion-styles '(orderless flex partial-completion substring))
|
|
(setq corfu-cycle t
|
|
corfu-preview-current nil
|
|
corfu-min-width 20
|
|
corfu-on-exact-match 'insert
|
|
corfu-quit-no-match t
|
|
corfu-quit-at-boundary t
|
|
corfu-popupinfo-delay '(1.25 . 0.5))
|
|
(corfu-popupinfo-mode 1)
|
|
;; history-based sorting, persisted across restarts
|
|
(corfu-history-mode 1)
|
|
(after! savehist
|
|
(add-to-list 'savehist-additional-variables 'corfu-history)))
|
|
|
|
|
|
|
|
;;; C-i to pick first completion thing
|
|
(after! corfu
|
|
(defun my/corfu-insert-first ()
|
|
"Insert the first candidate immediately."
|
|
(interactive)
|
|
(corfu-next)
|
|
(corfu-insert)
|
|
)
|
|
(map! :map corfu-map
|
|
"<insert-state> C-i" #'my/corfu-insert-first
|
|
)
|
|
)
|
|
|
|
|
|
;;; better eval mappings
|
|
(map! :leader "m e" #'eval-last-sexp)
|
|
(map! :leader "m r" #'eval-print-last-sexp)
|
|
|
|
|
|
|
|
;;;; font/zoom
|
|
(setq doom-font (font-spec
|
|
;:family "Hack Mono"
|
|
:size 23 :weight 'regular))
|
|
|
|
|
|
;;; have authinfo file in my $SECRETS_DIR
|
|
(setq auth-sources '("~/secrets/authinfo"))
|
|
|
|
|
|
|
|
;;; empv
|
|
(after! empv
|
|
(customize-set-variable 'empv-subsonic-username "c2vi")
|
|
(customize-set-variable 'empv-subsonic-url "https://tunes.c2vi.dev")
|
|
(customize-set-variable 'empv-subsonic-password (f-read-text "~/secrets/tunes-password"))
|
|
)
|
|
|
|
|
|
|
|
;;; supersonic
|
|
(after! supersonic
|
|
(customize-set-variable 'supersonic-host "https://tunes.c2vi.dev")
|
|
)
|
|
|
|
|
|
;;; emms
|
|
(use-package! emms
|
|
:config
|
|
(emms-all) ;; Loads stable EMMS features
|
|
(setq emms-source-file-default-directory "~/work/music/"
|
|
emms-player-list '(emms-player-mpv))
|
|
)
|
|
(when (featurep 'dbusbind)
|
|
(after! emms
|
|
(require 'emms-mpris)
|
|
(emms-mpris-enable)))
|
|
|
|
;;; infrasonic
|
|
(use-package! infrasonic)
|
|
(defvar my-subsonic-client
|
|
(infrasonic-make-client :url "tunes.c2vi.dev")
|
|
)
|
|
(defun my-pick-song ()
|
|
(interactive)
|
|
(let* (
|
|
(playlist (infrasonic-get-playlist my-subsonic-client "6ad5avelak6Lnu2yEbWwjj"))
|
|
(entries (cdr (assoc 'entry playlist)))
|
|
(candidates (list))
|
|
(urls '())
|
|
(void (dolist (song entries)
|
|
(let* (
|
|
(artist (cdr (assoc 'artist song)))
|
|
(cropped-artist (truncate-string-to-width artist 50))
|
|
(title (cdr (assoc 'title song)))
|
|
(id (cdr (assoc 'id song)))
|
|
(line (format "%-50s - %s" cropped-artist title))
|
|
(stream-url (infrasonic-get-stream-url my-subsonic-client id))
|
|
)
|
|
(add-to-list 'candidates line)
|
|
(push (cons line stream-url) urls)
|
|
)
|
|
))
|
|
(completion-result (completing-read "Select track: " candidates))
|
|
(url (cdr (assoc completion-result urls)))
|
|
)
|
|
(emms-play-url url)
|
|
)
|
|
)
|
|
(defun my-get-song-url ()
|
|
(interactive)
|
|
(let* (
|
|
(playlist (infrasonic-get-playlist my-subsonic-client "6ad5avelak6Lnu2yEbWwjj"))
|
|
(entries (cdr (assoc 'entry playlist)))
|
|
(candidates (list))
|
|
(urls '())
|
|
(void (dolist (song entries)
|
|
(let* (
|
|
(artist (cdr (assoc 'artist song)))
|
|
(cropped-artist (truncate-string-to-width artist 50))
|
|
(title (cdr (assoc 'title song)))
|
|
(id (cdr (assoc 'id song)))
|
|
(line (format "%-50s - %s" cropped-artist title))
|
|
(stream-url (infrasonic-get-stream-url my-subsonic-client id))
|
|
)
|
|
(add-to-list 'candidates line)
|
|
(push (cons line stream-url) urls)
|
|
)
|
|
))
|
|
(completion-result (completing-read "Select track: " candidates))
|
|
(url (cdr (assoc completion-result urls)))
|
|
)
|
|
(message url)
|
|
)
|
|
)
|
|
|
|
(defun my-load-playlist ()
|
|
(interactive)
|
|
(emms-playlist-mode-go)
|
|
(emms-playlist-clear)
|
|
|
|
(let* (
|
|
(playlist-id "6ad5avelak6Lnu2yEbWwjj")
|
|
(playlist (infrasonic-get-playlist
|
|
my-subsonic-client
|
|
playlist-id
|
|
))
|
|
(entries (cdr (assoc 'entry playlist)))
|
|
(track-count 0)
|
|
(void (dolist (track entries)
|
|
(let* (
|
|
(track-id (cdr (assoc 'id track)))
|
|
(title (cdr (assoc 'title track)))
|
|
(artist (cdr (assoc 'artist track)))
|
|
(stream-url (infrasonic-get-stream-url my-subsonic-client track-id))
|
|
(emmstrack (emms-track 'url stream-url))
|
|
)
|
|
;(message title)
|
|
(emms-track-set emmstrack 'info-title title)
|
|
(emms-track-set emmstrack 'info-artist artist)
|
|
(funcall emms-playlist-insert-track-function emmstrack)
|
|
)
|
|
))
|
|
)
|
|
)
|
|
)
|
|
|
|
|
|
|
|
|
|
;;; the file buffer opening I used to have in zed
|
|
(defun my-toggle-dired-current-dir ()
|
|
"Toggle between Dired for the current file's directory and the original file."
|
|
(interactive)
|
|
(if (eq major-mode 'dired-mode)
|
|
;; If in Dired, cleanly kill the buffer and jump back
|
|
(quit-window t)
|
|
;; Otherwise, check if visiting a file, then open its directory
|
|
(if (buffer-file-name)
|
|
(dired (file-name-directory (buffer-file-name)))
|
|
(message "Current buffer is not visiting a file."))))
|
|
|
|
;; Doom Emacs keybinding macro for Ctrl+Shift+P
|
|
(map! "C-S-p" #'my-toggle-dired-current-dir)
|
|
|
|
|
|
|
|
|
|
;;; open the config.el file directly instead of the whole config folder
|
|
(map! :leader
|
|
:desc "Open Doom config.el"
|
|
"f p" (lambda ()
|
|
(interactive)
|
|
(find-file (expand-file-name "config.el" doom-private-dir))))
|
|
|
|
|
|
|
|
|
|
;;; xwidget don't ask when killing a buffer
|
|
(after! xwidget
|
|
(remove-hook 'kill-buffer-query-functions #'xwidget-kill-buffer-query-function)
|
|
)
|
|
|
|
|
|
|
|
;;; markdown stuff
|
|
(use-package! md-mode
|
|
:mode ("\\.md\\'" . md-mode))
|
|
(after! md-mode
|
|
(define-key md-mode-map (kbd "C-h") 'md-mode-goto-heading)
|
|
)
|
|
|
|
|
|
;;; evil - use visial line for down/up
|
|
(after! evil
|
|
(define-key evil-motion-state-map (kbd "j") 'evil-next-visual-line)
|
|
(define-key evil-motion-state-map (kbd "k") 'evil-previous-visual-line)
|
|
)
|
|
|
|
;;; workspace setup
|
|
(after! persp-mode
|
|
;; Automatically create a "main" workspace for miscellaneous tasks
|
|
(setq +workspaces-main "things")
|
|
;; Prevent emacsclient from forcing everything into a generic or new blank workspace
|
|
(setq persp-emacsclient-init-frame-behaviour-override nil)
|
|
|
|
(defun my/switch-to-project-workspace-on-client (frame)
|
|
"When an emacsclient frame is created, check if a file/folder was passed,
|
|
and automatically switch to its matching Doom project workspace."
|
|
(with-selected-frame frame
|
|
;; Only execute if a target file or directory is actually loaded
|
|
(when-let* ((target-path (or (buffer-file-name) default-directory))
|
|
;; Ask Projectile to find if this path belongs to a known project root
|
|
(project-root (projectile-project-root target-path)))
|
|
;; Instruct Doom to switch to the project's named workspace
|
|
(+workspace-switch project-root t)
|
|
;; Update Projectile's current project state for that workspace context
|
|
(setq projectile-project-root project-root))))
|
|
|
|
;; Hook this function to run every time an emacsclient connection spawns a frame
|
|
(add-hook 'after-make-frame-functions #'my/switch-to-project-workspace-on-client)
|
|
)
|
|
|
|
|
|
|
|
;;; dired
|
|
(after! dired
|
|
(define-key evil-motion-state-map (kbd "C-n") nil)
|
|
(define-key evil-normal-state-map (kbd "C-n") nil)
|
|
(define-key dired-mode-map (kbd "C-n") #'dired-create-empty-file)
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
;;; contacts
|
|
(use-package! ecard
|
|
:defer t
|
|
:config
|
|
;; Ensure CardDAV capabilities are loaded
|
|
(require 'ecard-carddav)
|
|
(require 'ecard-sync)
|
|
|
|
;; Configure your remote CardDAV repository mapping
|
|
(setq ecard-carddav-accounts
|
|
'(("oxi"
|
|
:url "https://oxi.ppc.social"
|
|
:user "c2vi"
|
|
;; ecard-carddav uses auth-source to securely look up your password/app-password
|
|
:auth auth-source)))
|
|
|
|
;; Set a path where your contacts will be saved locally as vCard files
|
|
(setq ecard-sync-local-directory (expand-file-name "~/work/app-data/ecard/")))
|
|
(after! ecard
|
|
(defun my/ecard-import-vcard-and-sync (file)
|
|
"Import a .vcf FILE, save it to the local ecard directory, and sync to CardDAV."
|
|
(interactive "fSelect vCard file to import: ")
|
|
(unless (file-exists-p file)
|
|
(user-error "File does not exist: %s" file))
|
|
|
|
;; Ensure the target directory exists
|
|
(unless (file-directory-p ecard-sync-local-directory)
|
|
(make-directory ecard-sync-local-directory t))
|
|
|
|
(message "Importing %s..." (file-name-nondirectory file))
|
|
|
|
;; 1. Read and parse the external vCard
|
|
(with-temp-buffer
|
|
(insert-file-contents file)
|
|
(let ((cards (ecard-parse-buffer)))
|
|
(if (null cards)
|
|
(message "No valid vCards found in file.")
|
|
|
|
;; 2. Write each parsed card to your local sync directory
|
|
(dolist (card cards)
|
|
;; ecard-write-card automatically names the file appropriately (often via UUID or Name)
|
|
(ecard-write-card card ecard-sync-local-directory))
|
|
|
|
(message "Successfully imported %d contact(s) locally." (length cards))
|
|
|
|
;; 3. Push the new local additions to the CardDAV server
|
|
(if (y-or-n-p "Sync newly imported contacts to CardDAV now? ")
|
|
(progn
|
|
(message "Synchronizing with CardDAV...")
|
|
(ecard-carddav-sync)
|
|
(message "Sync complete!"))
|
|
(message "Contacts imported locally. Run M-x ecard-carddav-sync later to upload.")))))))
|
|
(use-package! org-contacts
|
|
:after org
|
|
:config
|
|
;; Tell Emacs where your contacts file lives
|
|
(setq org-contacts-files '("~/work/app-data/org/contacts.org"))
|
|
|
|
;; (Optional) Add a custom Org-Capture template for quick entry
|
|
(add-to-list 'org-capture-templates
|
|
'("c" "Contact" entry (file "~/org/contacts.org")
|
|
"* %(org-contacts-template-name)\n :PROPERTIES:\n :EMAIL: %(org-contacts-template-email)\n :PHONE:\n :BIRTHDAY:\n :NOTE:\n :END:")))
|
|
|
|
|
|
|
|
|
|
;;; matrix client
|
|
(setq my/secrets-dir (getenv "SECRETS_DIR"))
|
|
(use-package! ement
|
|
:config
|
|
;; Enable session saving so you don't have to re-authenticate on every launch
|
|
(setq ement-save-sessions t)
|
|
|
|
|
|
(when (fboundp 'set-fontset-font)
|
|
(set-fontset-font t 'emoji '("Noto Color Emoji" . "iso10646-1") nil 'prepend))
|
|
|
|
(run-with-idle-timer 2 nil (lambda ()
|
|
(ement-connect :user-id "@me:matrix.c2vi.dev"
|
|
:uri-prefix "http://100.88.79.207:8000"
|
|
:password (f-read (expand-file-name "priv.matrix.pwd" my/secrets-dir)))
|
|
|
|
;; my account on ppc's matrix
|
|
(ement-connect :user-id "@c2vi:ppc.social"
|
|
:uri-prefix "https://matrix.ppc.social")
|
|
))
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
;;; scratch
|
|
(defun my/scratch ()
|
|
(lookup-key dired-mode-map (kbd "C-n"))
|
|
(lookup-key corfu-map (kbd "C-i"))
|
|
(define-key corfu-map (kbd "C-i") 'my/corfu-insert-first)
|
|
|
|
|
|
(emms-insert-url "https://tunes.c2vi.dev/rest/stream.view?u=c2vi&t=59248d02914f93abee797adcf0f14d6a&s=8a3988f950895ee7bd87e17651e65eb1&v=1.16.1&c=infrasonic&f=json&id=0FnN5Sb3mCItKdIiJZcG7x")
|
|
(infrasonic-get-stream-url my-subsonic-client)
|
|
(message emms-playlist-buffer)
|
|
|
|
(+workspace-current-name)
|
|
(selected-frame)
|
|
|
|
)
|