init-package.el (21534B)
1 ;;; init-package.el --- some other packages -*- lexical-binding: t -*- 2 3 ;;; Commentary: 4 5 ;;; Code: 6 7 ;; keycast 8 (my-use-package keycast my-enable-unessentials) 9 10 ;; spell checker 11 (my-use-package jinx (not *is-windows*) 12 :delight 13 (jinx-mode " Jinx" jinx) 14 :config 15 (setq jinx-languages "en_US") 16 (add-to-list 'jinx-exclude-regexps '(t "\\cc")) 17 (dolist (range '((#x4E00 . #x9FFF) 18 (#x3400 . #x4DBF) 19 (#x3000 . #x303F) 20 (#xFF00 . #xFFEF) 21 (#x20000 . #x2A6DF))) 22 (let ((start (car range)) 23 (end (cdr range))) 24 (dotimes (i (- end start)) 25 (modify-syntax-entry (+ start i) "_" jinx--syntax-table))))) 26 27 (my-use-package consult-jinx (not *is-windows*) 28 :straight 29 (consult-jinx :type git 30 :repo "https://git.trogloxene.org/consult-jinx.git")) 31 32 ;; grammar checkers 33 (autoload #'eglot-managed-p "eglot") 34 (defun my-toggle-grammar-checker () 35 "Toggle grammar checker depend on `my-grammar-checker-backend'." 36 (interactive) 37 (if text-mode-variant 38 (pcase my-grammar-checker-backend 39 ('harper (if (eglot-managed-p) 40 (call-interactively #'eglot-shutdown) 41 (call-interactively #'eglot))) 42 ('languagetool (if (eglot-managed-p) 43 (call-interactively #'eglot-shutdown) 44 (call-interactively #'eglot))) 45 ('nsspell (nsspell-grammar-mode)) 46 ('nil (message "No grammar checker backend"))) 47 (message "Not in a text-mode buffer"))) 48 49 (my-use-package nsspell 50 (and *is-mac* (equal my-grammar-checker-backend 'nsspell)) 51 :straight (nsspell 52 :type git 53 :host github 54 :repo "LdBeth/nsspell" 55 :file ("lisp/")) 56 :init 57 (setq ispell-program-name "nsspell") 58 (setq ispell-dictionary "en")) 59 60 (my-use-package eglot-ltex 61 (equal my-grammar-checker-backend 'languagetool) 62 :straight 63 (eglot-ltex :type git :host github :repo "emacs-languagetool/eglot-ltex") 64 :hook 65 (text-mode . (lambda () (require 'eglot-ltex))) 66 :init 67 (setq eglot-ltex-server-path "ltex-ls-plus" 68 eglot-ltex-communication-channel 'stdio)) 69 70 (my-use-package harper 71 (equal my-grammar-checker-backend 'harper) 72 :no-require t 73 :straight nil 74 :init 75 (with-eval-after-load 'eglot 76 (add-to-list 'eglot-server-programs 77 '((markdown-mode :language-id "markdown") . ("harper-ls" "--stdio"))) 78 (add-to-list 'eglot-server-programs 79 '((org-mode :language-id "org") . ("harper-ls" "--stdio"))) 80 (setq-default eglot-workspace-configuration 81 '(:harper-ls (:linters (:SpellCheck :json-false :Spaces :json-false)))))) 82 83 ;; terminal emulator 84 (my-use-package ghostel 85 :hook 86 (eshell-load . ghostel-eshell-visual-command-mode) 87 (ghostel-mode . (lambda () (cns-mode -1))) 88 :init 89 (defun my-ghostel-send-C-k-and-kill () 90 "Send `C-k' to ghostel. 91 Like normal Emacs `C-k'. Kill to end of line and put content in kill-ring." 92 (interactive) 93 (kill-ring-save (point) (line-end-position)) 94 (ghostel-send-key "k" "ctrl")) 95 96 (defun my-scroll-other-ghostel-window (fun lines) 97 (let* ((buffer (window-buffer (other-window-for-scrolling))) 98 (mode (buffer-local-value 'major-mode buffer)) 99 (running-p (buffer-local-value 'ghostel--command-running buffer))) 100 (if (and (equal mode 'ghostel-mode) 101 running-p) 102 (with-current-buffer buffer 103 (ghostel-send-key "next")) 104 (funcall fun)))) 105 106 (defun my-scroll-other-ghostel-window-down (fun lines) 107 (let* ((buffer (window-buffer (other-window-for-scrolling))) 108 (mode (buffer-local-value 'major-mode buffer)) 109 (running-p (buffer-local-value 'ghostel--command-running buffer))) 110 (if (and (equal mode 'ghostel-mode) 111 running-p) 112 (with-current-buffer buffer 113 (ghostel-send-key "prior")) 114 (funcall fun)))) 115 116 :config 117 (setq eshell-destroy-buffer-when-process-dies t 118 ghostel-ssh-install-terminfo t) 119 (defalias 'eshell/v 'eshell/ghostel) 120 (advice-add #'scroll-other-window :around #'my-scroll-other-ghostel-window) 121 (advice-add #'scroll-other-window-down :around #'my-scroll-other-ghostel-window-down)) 122 123 (my-use-package consult-ghostel 124 :straight 125 (consult-ghostel :type git 126 :host github 127 :repo "dakra/ghostel" 128 :files ("extensions/consult-ghostel/"))) 129 130 ;; shell 131 (my-use-package shell 132 :straight nil 133 :init 134 (defvar my-shell-setenv 135 '(("PAGER" . "cat") 136 ("GIT_PAGER" . "cat") 137 ("MANPAGER" . "cat") 138 ("SYSTEMD_PAGER" . "cat") 139 ("APT_PAGER" . "cat"))) 140 141 (defun my-setenv-local (var val buffer) 142 (with-current-buffer buffer 143 (unless (local-variable-p 'process-environment) 144 (setq-local process-environment (copy-sequence process-environment))) 145 (setenv var val))) 146 147 (defun my-shell-setenv (buffer name &rest _) 148 (when (string= name "shell") 149 (dolist (pair my-shell-setenv) 150 (my-setenv-local (car pair) (cdr pair) buffer)))) 151 (advice-add #'comint-exec :before #'my-shell-setenv) 152 153 ;; from Protesilaos's Emacs configuration 154 (defun my-shell--build-input-history () 155 "Return `comint-input-ring' as a list." 156 (when (and (ring-p comint-input-ring) 157 (not (ring-empty-p comint-input-ring))) 158 (let (history) 159 (dotimes (index (ring-length comint-input-ring)) 160 (push (ring-ref comint-input-ring index) history)) 161 (delete-dups history)))) 162 163 (defvar my-shell--input-history-completion-history nil 164 "Minibuffer history of `my-shell--input-history-prompt'. 165 Not to be confused with the shell input history, which is stored 166 in the `comint-input-ring' (see `my-shell--build-input-history').") 167 168 (defun my-shell--input-history-prompt () 169 "Prompt for completion against `my-shell--build-input-history'." 170 (let* ((history (my-shell--build-input-history)) 171 (default (car history))) 172 (completing-read 173 (format-prompt "Insert input from history" default) 174 history nil :require-match nil 175 'my-shell--input-history-completion-history 176 default))) 177 178 (defun my-shell-input-from-history () 179 "Insert command from shell input history. 180 Only account for the history Emacs knows about, ignoring 181 `comint-input-ring-file-name' (e.g. ~/.bash_history)." 182 (declare (interactive-only t)) 183 (interactive) 184 (insert (my-shell--input-history-prompt)))) 185 186 ;; eshell-syntax-highlighting 187 (my-use-package eshell-syntax-highlighting 188 :hook 189 (eshell-mode . eshell-syntax-highlighting-global-mode) 190 (eshell-mode . (lambda () (setenv "TERM" "xterm-256color")))) 191 192 ;; environment sync 193 (my-use-package exec-path-from-shell (not *is-windows*) 194 :hook 195 (after-init . exec-path-from-shell-initialize)) 196 197 ;; workspace 198 (my-use-package perspective 199 :hook 200 (after-init . persp-mode) 201 :init 202 (defun my-persp-scratch-buffer (arg) 203 "Switch to the current perspective's scratch buffer. 204 With a prefix ARG create a new *scratch* buffer." 205 (interactive "P") 206 (let ((default-directory (expand-file-name "~/"))) 207 (if arg 208 (let* ((name (persp-current-name)) 209 (initial-persp (equal name persp-initial-frame-name)) 210 (buf (generate-new-buffer (concat "*scratch*" 211 (unless initial-persp 212 (format " (%s)" name)))))) 213 (switch-to-buffer buf) 214 (funcall initial-major-mode)) 215 (persp-switch-to-scratch-buffer)))) 216 217 (defun my-persp-scratch-set-default-dir (fun &rest name) 218 (let ((default-directory (expand-file-name "~/"))) 219 (apply fun name))) 220 (advice-add #'persp-get-scratch-buffer :around #'my-persp-scratch-set-default-dir) 221 222 (defvar my-persp-ibuffer-predicates 223 (list (lambda (buf) (persp-buffer-filter buf t)) 224 (lambda (buf) (persp--ignore-buffer-p (buffer-name buf))))) 225 226 (with-eval-after-load 'ibuffer 227 (setq ibuffer-maybe-show-predicates 228 (append ibuffer-maybe-show-predicates 229 my-persp-ibuffer-predicates))) 230 231 (defun my-persp-ibuffer (arg) 232 "Like `persp-ibuffer' but better." 233 (interactive "P") 234 (unless (featurep 'ibuffer) 235 (require 'ibuffer)) 236 (ibuffer) 237 (setq-local ibuffer-maybe-show-predicates 238 (if (and persp-mode (null arg)) 239 (append ibuffer-maybe-show-predicates 240 my-persp-ibuffer-predicates) 241 '(ibuffer-hidden-buffer-p))) 242 (ibuffer-update nil t)) 243 244 (defun my-persp-remove-marked-ibuffers () 245 "Remove marked buffers from perspective." 246 (interactive) 247 (let ((buffers (ibuffer-get-marked-buffers))) 248 (dolist (buf buffers) 249 (persp-remove-buffer buf))) 250 (ibuffer-update nil t)) 251 252 (defun my-persp-add-marked-ibuffers (persp-name) 253 "Add marked buffers to perspective." 254 (interactive 255 (list (completing-read "Perspective: " 256 (persp-names) 257 nil nil))) 258 (let ((buffers (ibuffer-get-marked-buffers))) 259 (persp-switch persp-name) 260 (dolist (buf buffers) 261 (persp-add-buffer buf)))) 262 263 (defun my-persp-set-marked-ibuffers (persp-name) 264 "Set marked buffers to perspective." 265 (interactive 266 (list (completing-read "Perspective: " 267 (persp-names) 268 nil nil))) 269 (let ((buffers (ibuffer-get-marked-buffers))) 270 (persp-switch persp-name) 271 (dolist (buf buffers) 272 (persp-set-buffer buf)))) 273 274 :config 275 (setopt persp-mode-prefix-key (kbd "C-c p")) 276 (general-def perspective-map "d" #'persp-kill) 277 (setq persp-switch-to-buffer-behavior 'switch 278 persp-sort 'oldest) 279 (with-eval-after-load 'consult 280 (consult-customize consult-source-buffer :hidden t :default nil) 281 (add-to-list 'consult-buffer-sources persp-consult-source) 282 (setq switch-to-prev-buffer-skip 283 (lambda (win buff bury-or-kill) 284 (or (not (persp-is-current-buffer buff)) 285 (cl-some (lambda (re) (string-match-p re (buffer-name buff))) 286 consult-buffer-filter))))) 287 (setq persp-initial-frame-name "main")) 288 289 ;; emms 290 (my-use-package emms my-enable-unessentials 291 :config 292 (add-to-list 'recentf-exclude "~/.emacs.d/emms/history") 293 (emms-all) 294 (setq emms-player-list '(emms-player-vlc) 295 emms-info-functions '(emms-info-native)) 296 (emms-history-load)) 297 298 ;; tokei 299 (my-use-package tokei my-enable-unessentials) 300 301 ;; helpful 302 (my-use-package helpful my-enable-unessentials 303 :straight 304 (helpful :fork (:type git :host nil :repo "https://git.trogloxene.org/helpful.git")) 305 :init 306 (defun remove-hook-at-point () 307 "Remove the hook at the point in the *Help* buffer." 308 (interactive) 309 (unless (memq major-mode '(help-mode helpful-mode)) 310 (error "Only for help-mode or helpful-mode")) 311 (let ((orig-point (point))) 312 (save-excursion 313 (when-let* 314 ((hook (progn (goto-char (point-min)) (symbol-at-point))) 315 (func (when (and 316 (or (re-search-forward (format "^Value:?[\s|\n]") nil t) 317 (goto-char orig-point)) 318 (thing-at-point 'sexp)) 319 (thing-at-point--end-of-sexp) 320 (backward-char 1) 321 (catch 'break 322 (while t 323 (condition-case _err 324 (backward-sexp) 325 (scan-error (throw 'break nil))) 326 (let ((bounds (bounds-of-thing-at-point 'sexp))) 327 (when (<= (car bounds) orig-point (cdr bounds)) 328 (throw 'break (thing-at-point 'sexp))))))))) 329 (when (yes-or-no-p (format "Remove %s from %s? " func hook)) 330 (remove-hook hook (intern func)) 331 (if (memq major-mode '(helpful-mode)) 332 (helpful-update))))))) 333 334 (defun my-helpful-view-source () 335 "View the source of the current symbol." 336 (interactive) 337 (let* ((find-file-visit-truename t) 338 (location 339 (helpful--definition helpful--sym helpful--callable-p)) 340 (buffer (car location)) 341 (position (cadr location))) 342 (pop-to-buffer buffer) 343 (set-auto-mode t) 344 (if position 345 (progn 346 (when (or (< position (point-min)) 347 (> position (point-max))) 348 (widen)) 349 (unless (= (point) position) 350 (push-mark nil t)) 351 (goto-char position)) 352 (message "Unable to find location in file"))))) 353 354 ;; typing game 355 (my-use-package typit my-enable-unessentials) 356 357 ;; z-machine interpreter 358 (my-use-package malyon my-enable-unessentials 359 :straight 360 (malyon :fork (:host github :repo "cjennings/malyon"))) 361 362 ;; guix 363 (my-use-package guix my-enable-guix) 364 365 ;; process manager 366 (my-use-package proced 367 :straight nil 368 :init 369 (defun proced-sort-rss (&optional arg) 370 "Sort Proced buffer by time the command rss. 371 Prefix ARG controls sort order, see `proced-sort-interactive'." 372 (interactive (list (or current-prefix-arg 'no-arg)) proced-mode) 373 (proced-sort-interactive 'rss arg)) 374 (general-def proced-mode-map "s r" #'proced-sort-rss) 375 376 :config 377 (setopt proced-auto-update-flag (if *is-windows* nil t) 378 proced-goal-attribute nil 379 proced-show-remote-processes t 380 proced-enable-color-flag t 381 proced-format 'custom) 382 383 (add-to-list 384 'proced-format-alist 385 (if *is-mac* 386 '(custom user pid ppid rss start (args comm)) 387 '(custom user pid ppid sess tree pcpu pmem rss start time state (args comm)))) 388 (setq proced-low-memory-usage-threshold 0.025 389 proced-medium-memory-usage-threshold 0.125)) 390 391 ;; syncthing 392 (my-use-package syncthing my-enable-unessentials 393 :hook 394 (syncthing-mode . enable-truncate-lines)) 395 396 ;; pinentry 397 (my-use-package pinentry (and *is-linux* my-enable-unessentials) 398 :hook 399 (after-init . pinentry-start)) 400 401 ;; pass 402 (my-use-package pass my-enable-unessentials 403 :init 404 (defvar my-pass-meta-fields '("url" "username" "email") 405 "List of pass metadata fields for completion.") 406 407 (defun my-password-store-append-field (entry field value) 408 "Append metadata to ENTRY." 409 (interactive 410 (let* ((entry (password-store--completing-read t)) 411 (field (completing-read "Field: " my-pass-meta-fields)) 412 (value (read-string (format "%s: " field)))) 413 (list entry field value))) 414 (let* ((old (string-trim-right (password-store--run-show entry))) 415 (new (format "%s\n%s: %s" old field value)) 416 (code (process-file-shell-command 417 (format "printf '%%s' %s | %s insert --multiline --force %s" 418 (shell-quote-argument new) 419 password-store-executable 420 (shell-quote-argument entry))))) 421 (unless (zerop code) (user-error "Update %s failed" entry)) 422 (message "Add %s: %s → %s" field value entry))) 423 424 (defun my-pass-append-field () 425 "Append metadata to the entry at point." 426 (interactive) 427 (pass--with-closest-entry 428 entry 429 (call-interactively 430 (lambda (field value) 431 (interactive 432 (let* ((f (completing-read "Field: " my-pass-meta-fields)) 433 (v (read-string (format "%s: " f)))) 434 (list f v))) 435 (my-password-store-append-field entry field value))))) 436 437 (defun my-pass-copy-email () 438 "Copy email of entry at point to kill ring." 439 (interactive) 440 (pass-copy-field "email")) 441 442 (general-def pass-mode-map 443 "a" #'my-pass-append-field 444 "E" #'pass-edit 445 "e" #'my-pass-copy-email)) 446 447 ;; hyperbole 448 (my-use-package hyperbole 449 :commands hui-search-web 450 :straight 451 (hyperbole :type git :host github :repo "rswgnu/hyperbole") 452 :delight 453 (hywiki-mode nil hywiki) 454 (hyperbole-mode nil hyperbole) 455 :init 456 (defun my-hkey-either-lazy (&optional arg) 457 (interactive "P") 458 (require 'hyperbole) 459 (hyperbole-mode 1) 460 (keymap-global-unset "M-RET") 461 (hkey-either arg)) 462 463 ;; a very interesting part 464 ;; not as trusty as original expand-region though 465 (defvar my-hui-er/history nil) 466 467 (defvar my-hui-er/original-point nil) 468 469 (defun my-hui-er/snapshot () 470 "Return a copy of current hui-select state, or nil." 471 (when hui-select-region 472 (cons hui-select-previous 473 (cons (car hui-select-region) (cdr hui-select-region))))) 474 475 (defun my-hui-er/select (start end type) 476 "Select START to END as thing TYPE and update hui-select state." 477 (setq hui-select-previous type) 478 (hui-select-set-region start end) 479 (goto-char start) 480 (set-mark end) 481 (activate-mark)) 482 483 (defun my-hui-er/expand () 484 "Select the syntactical thing at point." 485 (interactive) 486 (require 'hyperbole) 487 (when (not (use-region-p)) 488 (setq my-hui-er/history nil 489 my-hui-er/original-point (point)) 490 (hui-select-reset)) 491 (let ((snap (my-hui-er/snapshot))) 492 (hui-select-thing) 493 (if (not (equal snap (my-hui-er/snapshot))) 494 (progn 495 (when snap (push snap my-hui-er/history)) 496 (goto-char (car hui-select-region)) 497 (set-mark (cdr hui-select-region)) 498 (activate-mark) 499 (message "%s" hui-select-previous)) 500 (message "(hui-er): `%s' is the largest selectable region" hui-select-previous)))) 501 502 (defun my-hui-er/contract () 503 "Shrink region back to the previous selection." 504 (interactive) 505 (if-let* ((top (pop my-hui-er/history)) 506 (start (cadr top)) 507 (end (cddr top)) 508 (type (car top))) 509 (progn (my-hui-er/select start end type) 510 (message "%s" hui-select-previous)) 511 (my-hui-er/reset t) 512 (message "(hui-er): `%s' is the smallest selectable region" hui-select-previous))) 513 514 (defun my-hui-er/reset (&optional silent) 515 "Restore original point, clear history." 516 (interactive) 517 (setq my-hui-er/history nil) 518 (when my-hui-er/original-point 519 (goto-char my-hui-er/original-point)) 520 (deactivate-mark) 521 (hui-select-reset) 522 (unless silent 523 (message "(hui-er): selection reset"))) 524 525 (defun my-hui-er/quit-hack () 526 (when (and (region-active-p) 527 (or (eq last-command #'my-hui-er/expand) 528 (eq last-command #'my-hui-er/contract))) 529 (my-hui-er/reset t))) 530 (advice-add #'keyboard-quit :before #'my-hui-er/quit-hack) 531 532 (defvar-keymap my-hui-er-repeat-map 533 :repeat t 534 "e" #'my-hui-er/expand 535 "c" #'my-hui-er/contract 536 "r" #'my-hui-er/reset) 537 (put 'my-hui-er/expand 'repeat-map 'my-hui-er-repeat-map) 538 (put 'my-hui-er/contract 'repeat-map 'my-hui-er-repeat-map) 539 (put 'my-hui-er/reset 'repeat-map 'my-hui-er-repeat-map) 540 541 :config 542 (setq hsys-org-enable-smart-keys :buttons) 543 (hyperbole-mode 1) 544 (repeat-mode 1)) 545 546 ;; elfeed 547 (my-use-package elfeed my-enable-unessentials 548 :init 549 (defun my-elfeed-search-preview-other-window () 550 "Preview currently selected elfeed entry in another window." 551 (interactive) 552 (let ((display-buffer-alist '(("\\*elfeed-entry\\*" 553 (display-buffer-reuse-window 554 display-buffer-pop-up-window)))) 555 (current-prefix-arg '(4))) 556 (call-interactively #'elfeed-search-show-entry))) 557 :config 558 (setq elfeed-curl-timeout 600) 559 (setq-default elfeed-search-filter "")) 560 561 ;; posframe 562 (my-use-package posframe my-enable-unessentials) 563 564 ;; translator 565 (my-use-package gt my-enable-unessentials 566 :config 567 (setq gt-default-translator 568 (gt-translator 569 :engines (gt-youdao-dict-engine) 570 :taker (gt-taker :text 'word :langs '(en zh)) 571 :render (gt-posframe-pin-render)))) 572 573 ;; thesaurus 574 (my-use-package synosaurus my-enable-unessentials 575 :init 576 (defun my-synosaurus-lookup (arg) 577 "Lookup word in thesaurus. 578 With a prefix ARG replace the word under the cursor by a synonym." 579 (interactive "P") 580 (if arg 581 (call-interactively #'synosaurus-choose-and-replace) 582 (call-interactively #'synosaurus-lookup))) 583 :config 584 (setq synosaurus-prefix nil 585 synosaurus-choose-method 'default)) 586 587 ;; dashboard 588 (my-use-package dashboard my-enable-dashboard 589 :autoload dashboard-setup-startup-hook 590 :hook after-init 591 :config 592 (setq dashboard-banner-logo-title (format "%s" emacs-version) 593 dashboard-banner-ascii 594 " 595 ##### # # # # ####### # # # ##### ##### 596 # # ## # # # # ## ## # # # # # # 597 # # # # # # # # # # # # # # # 598 # #### # # # # # ##### # # # # # # ##### 599 # # # # # # # # # # ####### # # 600 # # # ## # # # # # # # # # # # 601 ##### # # ##### ####### # # # # ##### ##### 602 " 603 dashboard-startup-banner 'ascii 604 dashboard-footer-messages '("hello") 605 dashboard-center-content t 606 dashboard-navigation-cycle t 607 dashboard-items '((recents . 8) 608 (bookmarks . 5) 609 (projects . 5)) 610 dashboard-item-shortcuts '((recents . "r") (bookmarks . "b") (projects . "p") (agenda . "a") (registers . "e"))) 611 (dashboard-setup-startup-hook)) 612 613 (provide 'init-package) 614 ;;; init-package.el ends here