diff --git a/README.org b/README.org index 4268f9d..eb7f43f 100755 --- a/README.org +++ b/README.org @@ -214,6 +214,32 @@ Disassemble project-ideas.org and reading-list.org into a individual roam nodes. - variable-pitch: Overpass - backup: Julia Mono +*** TODO Lisp editing + +**** TODO [[https://github.com/promethial/paxedit][Paxedit]] + +**** TODO Evil stuff + +**** TODO Text objects + +**** TODO [[https://github.com/Malabarba/speed-of-thought-lisp][speed-of-thought]] + +**** TODO [[https://github.com/Lindydancer/lisp-extra-font-lock][lisp-extra-font-lock]] + +**** TODO rainbow-delimiters + +**** TODO [[https://github.com/Wilfred/emacs-refactor][emacs-refactor]] + +**** TODO [[https://github.com/Malabarba/aggressive-indent-mode][aggressive-indent-mode]] + +**** TODO [[https://github.com/riscy/elfmt][elfmt]] + +**** TODO [[https://github.com/magnars/string-edit.el][string-edit]] + +**** TODO [[https://github.com/Fuco1/elisp-docstring-mod][elisp-docstring]] + +*** TODO [[https://github.com/purcell/page-break-lines][page-break-lines]] + *** DONE project.el CLOSED: [2025-01-16 Thu 18:19] @@ -225,9 +251,8 @@ CLOSED: [2025-01-16 Thu 18:19] **** TODO Spell-checking -*** TODO Sexp editing - -*** TODO Lookup handlers +*** DONE Lookup handlers +CLOSED: [2025-02-01 Sat 16:56] *** DONE Repl-handling CLOSED: [2025-01-31 Fri] diff --git a/users/crumb/programs/emacs/eshell/history b/users/crumb/programs/emacs/eshell/history new file mode 100644 index 0000000..46f13fc --- /dev/null +++ b/users/crumb/programs/emacs/eshell/history @@ -0,0 +1,3 @@ +doge +soge +grep -r xref diff --git a/users/crumb/programs/emacs/init.el b/users/crumb/programs/emacs/init.el index 006120b..1e01de1 100755 --- a/users/crumb/programs/emacs/init.el +++ b/users/crumb/programs/emacs/init.el @@ -2,6 +2,8 @@ ;; Initialise Straight.el +(+ 1 2) + (load (locate-user-emacs-file "init-straight")) (syd-initialise-straight) @@ -52,6 +54,7 @@ (require 'syd-custom) (require 'syd-display-startup-time) (require 'syd-evil) +(require 'syd-eshell) (require 'syd-keybinds) (require 'syd-lang) (require 'syd-org) diff --git a/users/crumb/programs/emacs/lib/syd-buffers.el b/users/crumb/programs/emacs/lib/syd-buffers.el index d141cbe..428a4b4 100644 --- a/users/crumb/programs/emacs/lib/syd-buffers.el +++ b/users/crumb/programs/emacs/lib/syd-buffers.el @@ -118,6 +118,15 @@ See `syd-real-buffer-p' for details on what that means." (when (syd-unreal-buffer-p (window-buffer)) (switch-to-buffer (syd-fallback-buffer))))))) +;;;###autoload +(defun syd-set-buffer-realness (buffer realness) + (with-current-buffer buffer + (setq syd-real-buffer-p realness))) + +;;;###autoload +(defun syd-mark-buffer-as-real () + (syd-set-buffer-realness (current-buffer) t)) + (defun syd-kill-buffer-fixup-windows (buffer) "Kill the BUFFER and ensure all the windows it was displayed in have switched to a real buffer or the fallback buffer." diff --git a/users/crumb/programs/emacs/lib/syd-handle-eval.el b/users/crumb/programs/emacs/lib/syd-handle-eval.el new file mode 100644 index 0000000..0ccbc25 --- /dev/null +++ b/users/crumb/programs/emacs/lib/syd-handle-eval.el @@ -0,0 +1,258 @@ +;;; syd-handle-eval.el -*- lexical-binding: t; -*- + +(defvar syd-eval-runners '()) + +;; Remove ellipsis when printing sexps in message buffer. +(setq eval-expression-print-length nil + eval-expression-print-level nil) + +(set-popup-rule! + "*eval*" + :size 0.2) + + +;; Packages + +(use-package quickrun + :defer t) + +(use-package eros + :hook (emacs-lisp-mode . eros-mode)) + +;; (with-eval-after-load quickrun +;; (setq quickrun-focus-p nil) + +;; (set-popup-rule! "^\\*quickrun" :size 0.3 :ttl 0) + +;; (defadvice! +eval--quickrun-fix-evil-visual-region-a () +;; "Make `quickrun-replace-region' recognize evil visual selections." +;; :override #'quickrun--outputter-replace-region +;; (let ((output (buffer-substring-no-properties (point-min) (point-max)))) +;; (with-current-buffer quickrun--original-buffer +;; (cl-destructuring-bind (beg . end) +;; ;; Because `deactivate-mark', the function, was used in +;; ;; `quickrun--region-command-common' instead of `deactivate-mark', +;; ;; the variable, the selection is disabled by this point. +;; (if (bound-and-true-p evil-local-mode) +;; (cons evil-visual-beginning evil-visual-end) +;; (cons (region-beginning) (region-end))) +;; (delete-region beg end) +;; (insert output)) +;; (setq quickrun-option-outputter quickrun--original-outputter)))) + +;; (defadvice! +eval--quickrun-auto-close-a (&rest _) +;; "Silently re-create the quickrun popup when re-evaluating." +;; :before '(quickrun quickrun-region) +;; (when-let (win (get-buffer-window quickrun--buffer-name)) +;; (let ((inhibit-message t)) +;; (quickrun--kill-running-process) +;; (message "")) +;; (delete-window win))) + +;; (add-hook! 'quickrun-after-run-hook +;; (defun +eval-quickrun-shrink-window-h () +;; "Shrink the quickrun output window once code evaluation is complete." +;; (when-let (win (get-buffer-window quickrun--buffer-name)) +;; (with-selected-window (get-buffer-window quickrun--buffer-name) +;; (let ((ignore-window-parameters t)) +;; (shrink-window-if-larger-than-buffer))))) +;; (defun +eval-quickrun-scroll-to-bof-h () +;; "Ensures window is scrolled to BOF on invocation." +;; (when-let (win (get-buffer-window quickrun--buffer-name)) +;; (with-selected-window win +;; (goto-char (point-min)))))) + +;; ;; Display evaluation results in an overlay at the end of the current line. If +;; ;; the output is more than `+eval-popup-min-lines' (4) lines long, it is +;; ;; displayed in a popup. +;; (when (modulep! +overlay) +;; (defadvice! +eval--show-output-in-overlay-a (fn) +;; :filter-return #'quickrun--make-sentinel +;; (lambda (process event) +;; (funcall fn process event) +;; (with-current-buffer quickrun--buffer-name +;; (when (> (buffer-size) 0) +;; (+eval-display-results +;; (string-trim (buffer-string)) +;; quickrun--original-buffer))))) + +;; ;; Suppress quickrun's popup window because we're using an overlay instead. +;; (defadvice! +eval--inhibit-quickrun-popup-a (buf cb) +;; :override #'quickrun--pop-to-buffer +;; (setq quickrun--original-buffer (current-buffer)) +;; (save-window-excursion +;; (with-current-buffer (pop-to-buffer buf) +;; (setq quickrun-option-outputter #'ignore) +;; (funcall cb)))) + +;; ;; HACK Without this, `+eval--inhibit-quickrun-popup-a' throws a +;; ;; window-live-p error because no window exists to be recentered! +;; (advice-add #'quickrun--recenter :override #'ignore))) + + + +;;;###autoload +(cl-defun syd-eval-region-as-major-mode + (beg end &key (runner-major-mode major-mode)) + "Evaluate a region between BEG and END and display the output. + +Evaluate as in RUNNER-MAJOR-MODE. If RUNNER-MAJOR-MODE is nil, use major-mode +of the buffer instead." + (if-let* ((runner (alist-get runner-major-mode syd-eval-runners))) + (funcall runner beg end) + (and (require 'quickrun nil t) + (let ((quickrun-option-cmdkey + (quickrun--command-key + (buffer-file-name (buffer-base-buffer))))) + (quickrun-region beg end))))) + +;;;###autoload +(defun syd-eval-region (beg end) + (interactive "r") + ;; (message "syd: %s" (pp-to-string + ;; (buffer-substring-no-properties (syd-region-beginning) + ;; (syd-region-end)))) + ;; (message "r: %s" (pp-to-string (buffer-substring-no-properties beg end))) + (syd-eval-region-as-major-mode beg end)) + +(with-eval-after-load 'evil + (evil-define-operator syd-eval-operator (beg end) + "Evaluate selection." + :move-point nil + (interactive "") + (syd-eval-region beg end)) + + (general-def + :states 'normal + "gR" #'syd-eval-buffer) + + (general-def + :states '(normal visual) + "gr" #'syd-eval-operator)) + +;;;###autoload +(defun set-eval-handler! (modes command) + "Define a code evaluator for major mode MODES with `quickrun'. + +MODES can be list of major mode symbols, or a single one. + +1. If MODE is a string and COMMAND is the string, MODE is a file regexp and + COMMAND is a string key for an entry in `quickrun-file-alist'. +2. If MODE is not a string and COMMAND is a string, MODE is a major-mode symbol + and COMMAND is a key (for `quickrun--language-alist'), and will be registered + in `quickrun--major-mode-alist'. +3. If MODE is not a string and COMMAND is an alist, see `quickrun-add-command': + (quickrun-add-command MODE COMMAND :mode MODE). +4. If MODE is not a string and COMMANd is a symbol, add it to + `syd-eval-runners', which is used by `syd-eval-region'." + (declare (indent defun)) + (dolist (mode (ensure-list modes)) + (cond ((symbolp command) + (push (cons mode command) syd-eval-runners)) + ((stringp command) + (with-eval-after-load 'quickrun + (push (cons mode command) + (if (stringp mode) + quickrun-file-alist + quickrun--major-mode-alist)))) + ((listp command) + (with-eval-after-load 'quickrun + (quickrun-add-command + (or (cdr (assq mode quickrun--major-mode-alist)) + (string-remove-suffix "-mode" (symbol-name mode))) + command :mode mode)))))) + +(defvar syd-eval-overlay-max-lines 4 + "The maximum number of lines allowed to be displayed in an eval overlay; any +more and a popup buffer will be used instead.") + +;;;###autoload +(defun syd-eval-display-results-in-popup (output) + "Display OUTPUT in a popup buffer." + (let ((output-buffer (get-buffer-create "*eval*")) + (origin (selected-window))) + (with-current-buffer output-buffer + (setq-local scroll-margin 0) + (erase-buffer) + (insert output) + (goto-char (point-min)) + ;; (if (fboundp '+word-wrap-mode) + ;; (+word-wrap-mode +1) + ;; (visual-line-mode +1)) + ) + (when-let* ((win (display-buffer output-buffer))) + ;; (fit-window-to-buffer + ;; win (/ (frame-height) 2) + ;; nil (/ (frame-width) 2)) + ) + (select-window origin) + output-buffer)) + +;;;###autoload +(defun syd-eval-buffer () + "Evaluate the entire buffer." + (interactive) + (syd-eval-region (point-min) (point-max))) + +;;;###autoload +(defun syd-eval-buffer-or-region () + "Evaluate the region if it is active, or the entire buffer, or not." + (if (use-region-p) + (call-interactively #'syd-eval-region) + (call-interactively #'syd-eval-buffer))) + +;;;###autoload +(cl-defun syd-eval-display-results-in-overlay (output &key source-buffer) + "Display OUTPUT in a floating overlay next to the cursor." + (require 'eros) + (with-current-buffer (or source-buffer (current-buffer)) + (let* ((this-command #'syd-eval/buffer-or-region) + (prefix eros-eval-result-prefix) + (lines (split-string output "\n")) + (prefixlen (length prefix)) + (len (+ (apply #'max (mapcar #'length lines)) + prefixlen)) + (next-line? (or (cdr lines) + (< (- (window-width) + (save-excursion (goto-char (line-end-position)) + (- (current-column) + (window-hscroll)))) + len))) + (pad (if next-line? + (+ (window-hscroll) prefixlen) + 0)) + eros-overlays-use-font-lock) + (eros--make-result-overlay + (concat (make-string (max 0 (- pad prefixlen)) ?\s) + prefix + (string-join lines (concat hard-newline (make-string pad ?\s)))) + :where (if next-line? + (line-beginning-position 2) + (line-end-position)) + :duration eros-eval-result-duration + :format "%s")))) + +;;;###autoload +(cl-defun syd-eval-display-results (output &key source-buffer force-popup) + "Display OUTPUT in an overlay, or if it's too long, a popup buffer." + (if (or force-popup + ;; EROS is used to display overlays. Without it, just use a popup. + (not (require 'eros nil t)) + (with-temp-buffer + (insert output) + (or + ;; Too tall! + (<= syd-eval-overlay-max-lines + (count-lines (point-min) (point-max))) + ;; Too wide! + (<= (window-width) + (string-width + (buffer-substring (point-min) + (save-excursion + (goto-char (point-min)) + (line-end-position)))))))) + (syd-eval-display-results-in-popup output) + (syd-eval-display-results-in-overlay output :source-buffer source-buffer)) + output) + +(provide 'syd-handle-eval) diff --git a/users/crumb/programs/emacs/lib/syd-handle-lookup.el b/users/crumb/programs/emacs/lib/syd-handle-lookup.el index a74dfaa..437699d 100644 --- a/users/crumb/programs/emacs/lib/syd-handle-lookup.el +++ b/users/crumb/programs/emacs/lib/syd-handle-lookup.el @@ -6,11 +6,59 @@ (require 'better-jumper) -(defvar syd-lookup-documentation-handlers '() - "An list of lookup handlers used to find documentation. A lookup handler +(defvar syd-lookup-online-documentation-backends + `(("Kagi" . "https://kagi.com/search?q=%s") + ("DuckDuckGo" . "https://duckduckgo.com/?q=%s") + ("Nixpkgs" . "https://search.nixos.org/packages?query=%s") + ("Hackage" . "https://hackage.haskell.org/packages/search?terms=%s")) + "A list of pairs (NAME . BACKEND) describing the various backends +`syd-lookup-online-documentation' may delegate to. + +NAME is a string used when speaking to the user about BACKEND. + +If BACKEND is an interactive command, it will be called interactively. + +If BACKEND is a procedure, it will be called with the search string as the lone +argument. + +If BACKEND is a string, the user's browser will be opened to the URL returned by +(format BACKEND QUERY), where QUERY is the appropriately-encoded search +string.") + +(defvar syd-lookup-documentation-handlers '(syd-lookup-online-documentation) + "A list of lookup handlers used to find documentation. A lookup handler receives an identifier, and is expected to return nil on failure, and non-nil on success. The specific return value is unused outside of the test for nil.") +(defun syd-lookup--prompt-for-online-backend () + (assoc-string + (completing-read "Search with: " + (mapcar #'car syd-lookup-online-documentation-backends) + nil + t) + syd-lookup-online-documentation-backends)) + +(cl-defun syd-lookup--call-online-backend (backend &key query-string) + (pcase-let ((`(,name . ,backend-fn) backend)) + (cond ((functionp backend-fn) (if (commandp backend-fn) + (call-interactively backend-fn) + (funcall backend-fn query-string))) + ((stringp backend-fn) + (browse-url (format backend-fn + (url-encode-url + (read-string (format "Search for (on %s): " + name) + query-string))))) + (t (signal 'wrong-type-argument `("backend" ,backend-fn)))))) + +;;;###autoload +(cl-defun syd-lookup-online-documentation (backend &key query-string) + (interactive (list (syd-lookup--prompt-for-online-backend) + :query-string (when (use-region-p) + (syd-thing-at-point-or-region)))) + (syd-lookup--call-online-backend (syd-lookup--prompt-for-online-backend) + :query-string query-string)) + ;;;###autoload (defun syd-lookup-documentation (identifier) "Try to find documentation on IDENTIFIER, and " @@ -28,6 +76,8 @@ success. The specific return value is unused outside of the test for nil.") (category identifier &key (display-fn #'switch-to-buffer)) (let* ((handlers (alist-get category syd-lookup--handlers-by-category)) (origin (point-marker)) + ;; TODO: If called with a prefix argument, prompt the user to select a + ;; handler. (result (run-hook-wrapped handlers #'syd-lookup--run-handler identifier origin))) (message "result wrap hok: %S" result) diff --git a/users/crumb/programs/emacs/lib/syd-prelude.el b/users/crumb/programs/emacs/lib/syd-prelude.el index 11f4de6..f5eaf9b 100644 --- a/users/crumb/programs/emacs/lib/syd-prelude.el +++ b/users/crumb/programs/emacs/lib/syd-prelude.el @@ -12,4 +12,52 @@ ,todo (error ,todo)))) +(cl-defun syd--lift-lambdas (forms &key with-each with-all) + ;; Call the continuation if non-nil. Wraps the return value in a singleton + ;; list for "affine" use with unquote-splicing. + (let ((call-cont (lambda (cont) + (if cont + (lambda (name) (list (funcall with-each name))) + (lambda (_) nil)))) + names) + `(progn ,@(mapconcat (lambda (form) + (cond ((and (symbolp form) (functionp form)) + (push form names) + (call-cont with-each form)) + ((eq (car-safe form) 'defun) + (let ((name (nth 1 form))) + (push name names) + `(,form + ,@(call-cont with-each)))) + ((eq (car-safe form) 'lambda) + (let ((name (gensym "lifted-lambda"))) + (push name names) + `((defun ,name (&rest args) + (,form args)) + ,@(call-cont with-each)))))) + (ensure-list forms)) + ,@(call-cont with-all names)))) + +;; (defun syd-hform-defun (hform) +;; "If HFORM is a defun form, return the defun's name. Otherwise, return nil" +;; (and (listp hform) +;; (<= 2 (length hform)) +;; (nth 1 hform))) + +(defmacro with-transient-after (hook-or-function &rest forms) + (let ((hook-name (gensym "transient-hook")) + (hook-or-function* (gensym "hook-or-function"))) + `(let* ((,hook-or-function* ,hook-or-function)) + (defun ,hook-name (&rest _) + "Transient hook defined by `with-transient-after'." + (cond ((functionp ,hook-or-function*) + (advice-remove ,hook-or-function* (function ,hook-name))) + ((symbolp ,hook-or-function*) + (remove-hook ,hook-or-function* (function ,hook-name)))) + ,@forms) + (cond ((functionp ,hook-or-function*) + (advice-add ,hook-or-function* :before (function ,hook-name))) + ((symbolp ,hook-or-function*) + (add-hook ,hook-or-function* (function ,hook-name))))))) + (provide 'syd-prelude) diff --git a/users/crumb/programs/emacs/modules/lang/syd-lang-emacs-lisp.el b/users/crumb/programs/emacs/modules/lang/syd-lang-emacs-lisp.el index 901d035..22c04ba 100644 --- a/users/crumb/programs/emacs/modules/lang/syd-lang-emacs-lisp.el +++ b/users/crumb/programs/emacs/modules/lang/syd-lang-emacs-lisp.el @@ -2,6 +2,7 @@ (require 'syd-handle-repl) (require 'syd-handle-lookup) +(require 'syd-handle-eval) ;; (require 'handle) ;; Don't `use-package' `ielm', since it's loaded by Emacs. You'll get weird @@ -37,6 +38,31 @@ (set-repl-handler! 'emacs-lisp-mode #'syd/open-emacs-lisp-repl) +;;;###autoload +(defun syd-emacs-lisp-eval (beg end) + "Evaluate a region and print it to the echo area (if one line long), otherwise +to a pop up buffer." + (syd-eval-display-results + (string-trim-right + (let ((buffer (generate-new-buffer " *eval-output*")) + (debug-on-error t)) + (unwind-protect + (condition-case-unless-debug e + (progn (eval-region beg end buffer load-read-function) + (with-current-buffer buffer + (let ((pp-max-width nil)) + (require 'pp) + (pp-buffer) + (replace-regexp-in-string + "\\\\n" "\n" (string-trim-left (buffer-string)))))) + (error (format "ERROR: %s" e))) + (kill-buffer buffer)))) + :source-buffer (current-buffer) + :force-popup current-prefix-arg)) + +(set-eval-handler! 'emacs-lisp-mode + #'syd-emacs-lisp-eval) + (defun syd-emacs-set-handlers () (setq-local syd-lookup-documentation-handlers (list #'syd-emacs-lisp-lookup-documentation))) diff --git a/users/crumb/programs/emacs/modules/syd-completion.el b/users/crumb/programs/emacs/modules/syd-completion.el index ffb3873..0b32ac0 100755 --- a/users/crumb/programs/emacs/modules/syd-completion.el +++ b/users/crumb/programs/emacs/modules/syd-completion.el @@ -1,7 +1,5 @@ ;;; syd-completion.el -*- lexical-binding: t; -*- -(require 'syd-general) - (use-package emacs :custom ;; Allow the opening of new minibuffers from inside existing minibuffers. diff --git a/users/crumb/programs/emacs/modules/syd-eshell.el b/users/crumb/programs/emacs/modules/syd-eshell.el new file mode 100644 index 0000000..420b464 --- /dev/null +++ b/users/crumb/programs/emacs/modules/syd-eshell.el @@ -0,0 +1,110 @@ +;;; syd-eshell.el -*- lexical-binding: t; -*- + +(defvar eshell-buffer-name "*eshell*") + +(defvar syd-eshell-buffers (make-ring 25) + "List of open eshell buffers.") + +(defun syd-eshell-buffers () + "TODO" + (ring-elements syd-eshell-buffers)) + +;;;###autoload +(defun syd-eshell-run-command (command &optional buffer) + "TODO" + (let ((buffer + (or buffer + (if (eq major-mode 'eshell-mode) + (current-buffer) + (cl-find-if #'buffer-live-p (syd-eshell-buffers)))))) + (unless buffer + (user-error "No living eshell buffers available")) + (unless (buffer-live-p buffer) + (user-error "Cannot operate on a dead buffer")) + (with-current-buffer buffer + (goto-char eshell-last-output-end) + (goto-char (line-end-position)) + (insert command) + (eshell-send-input nil t)))) + +;;;###autoload +(defun syd-eshell/toggle (arg &optional command) + "Toggle eshell popup window." + (interactive "P") + (let ((eshell-buffer + (get-buffer-create + (format "*eshell-popup:%s*" + (if (bound-and-true-p persp-mode) + (safe-persp-name (get-current-persp)) + "main")))) + confirm-kill-processes + current-prefix-arg) + (when arg + (when-let* ((win (get-buffer-window eshell-buffer))) + (delete-window win)) + (when (buffer-live-p eshell-buffer) + (with-current-buffer eshell-buffer + (fundamental-mode) + (erase-buffer)))) + (if-let* ((win (get-buffer-window eshell-buffer))) + (let (confirm-kill-processes) + (delete-window win) + (ignore-errors (kill-buffer eshell-buffer))) + (with-current-buffer eshell-buffer + (syd-mark-buffer-as-real) + (if (eq major-mode 'eshell-mode) + (run-hooks 'eshell-mode-hook) + (eshell-mode)) + (when command + (syd-eshell-run-command command eshell-buffer))) + (pop-to-buffer eshell-buffer)))) + +(use-package eshell + :custom + ((eshell-banner-message + '(format "🦌 %s %s }:3\n" + (propertize (format " %s " (string-trim (buffer-name))) + 'face 'mode-line-highlight) + (propertize (current-time-string) + 'face 'font-lock-keyword-face))) + (eshell-scroll-to-bottom-on-input 'all) + (eshell-scroll-to-bottom-on-output 'all) + (eshell-kill-processes-on-exit t) + (eshell-hist-ignoredups t) + (eshell-glob-case-insensitive t) + (eshell-error-if-no-glob t)) + :general + (:keymaps 'syd-leader-open-map + "e" #'syd-eshell/toggle) + (:keymaps 'eshell-mode-map + :states '(normal insert) + "C-j" #'eshell-next-matching-input-from-input + "C-k" #'eshell-previous-matching-input-from-input) + :config + (require 'syd-buffers) + + (add-hook 'eshell-mode-hook #'syd-mark-buffer-as-real) + + ;; UI enhancements. + (defun syd-eshell-remove-fringes-h () + (set-window-fringes nil 0 0) + (set-window-margins nil 1 nil)) + (defun syd-eshell-enable-text-wrapping-h () + (visual-line-mode +1) + (set-display-table-slot standard-display-table 0 ?\ )) + (add-hook 'eshell-mode-hook #'syd-eshell-remove-fringes-h) + (add-hook 'eshell-mode-hook #'syd-eshell-enable-text-wrapping-h) + + (with-eval-after-load 'hide-mode-line + (add-hook 'eshell-mode-hook #'hide-mode-line-mode)) + + ;; Remove hscroll-margin in shells, otherwise you get jumpiness when the + ;; cursor comes close to the left/right edges of the window. + (defun syd-eshell-disable-hscroll-margin () + (setq hscroll-margin 0)) + (add-hook 'eshell-mode-hook #'syd-eshell-disable-hscroll-margin)) + +(set-popup-rule! "^\\*eshell-popup" + :vslot -5 :size 0.35 :select t :modeline nil :quit nil :ttl nil) + +(provide 'syd-eshell) diff --git a/users/crumb/programs/emacs/modules/syd-evil.el b/users/crumb/programs/emacs/modules/syd-evil.el index b8ebd19..f00da06 100755 --- a/users/crumb/programs/emacs/modules/syd-evil.el +++ b/users/crumb/programs/emacs/modules/syd-evil.el @@ -33,24 +33,168 @@ ;; These two are required for evil-collection. (setq evil-want-keybinding nil evil-want-integration t) + :config ;; 'M-:' starts off in insert mode, yet initialises with the normal mode ;; cursor. Quick fix! }:P (add-hook 'minibuffer-setup-hook #'evil-refresh-cursor) + ;; Unbind 'C-k'. Normally, it inserts digraphs; I have a compose key. It ;; often gets in the way of buffers with navigation, e.g. scrolling through ;; shell/REPL history, navigating Vertico completions, etc. (keymap-set evil-insert-state-map "C-k" nil) + + ;; In imitation of Vim's :mes[sages] command, define an Evil analogue to show + ;; the echo area. + (evil-ex-define-cmd "mes[sages]" + #'view-echo-area-messages) + (evil-mode 1)) +(defvar evil-collection-key-blacklist) + + ;; A large, community-sourced collection of preconfigured Evil-mode ;; integrations. (use-package evil-collection :after evil + :defer t :custom (evil-collection-setup-minibuffer t) :config - (evil-collection-init)) + (unless noninteractive + (defvar syd-evil-collection-disabled-list + '(anaconda-mode buff-menu calc comint company custom eldoc elisp-mode ert + free-keys helm help image indent kmacro kotlin-mode lispy outline + replace shortdoc simple slime tab-bar) + "A list of `evil-collection' modules to ignore. See +`evil-collection-mode-list' for a list of available options.") + + ;; We do this ourselves. + (defvar evil-collection-want-unimpaired-p nil) + ;; We binds goto-reference on gD and goto-assignments on gA ourselves + (defvar evil-collection-want-find-usages-bindings-p nil) + ;; Reduces keybind conflicts between outline-mode and org-mode (which is + ;; derived from outline-mode). + (defvar evil-collection-outline-enable-in-minor-mode-p nil) + ;; We handle loading evil-collection ourselves + (defvar evil-collection--supported-modes nil) + ;; This has to be defined here since evil-collection doesn't autoload its own. + ;; It must be updated whenever evil-collection updates theirs. Here's an easy + ;; way to update it: + (defvar evil-collection-mode-list + `(2048-game ag alchemist anaconda-mode apropos arc-mode atomic-chrome + auto-package-update beginend bluetooth bm bookmark + (buff-menu "buff-menu") bufler calc calendar cider citre cmake-mode + color-rg comint company compile consult corfu crdt (csv "csv-mode") + (custom cus-edit) cus-theme dape dashboard daemons deadgrep debbugs + debug devdocs dictionary diff-hl diff-mode dired dired-sidebar + disk-usage distel doc-view docker eat ebib ebuku edbi edebug ediff eglot + elpaca ement explain-pause-mode eldoc elfeed elisp-mode elisp-refs + elisp-slime-nav embark emms ,@(if (> emacs-major-version 28) '(emoji)) + epa ert eshell eval-sexp-fu evil-mc eww fanyi finder flycheck flymake + forge free-keys geiser ggtags git-timemachine gited gnus go-mode gptel + grep guix hackernews helm help helpful hg-histedit hungry-delete hyrolo + ibuffer (image image-mode) image-dired image+ imenu imenu-list + (indent "indent") indium info ivy js2-mode + ,@(if (>= emacs-major-version 30) '(kmacro)) leetcode lispy lms log-edit + log-view lsp-ui-imenu lua-mode kotlin-mode macrostep man + (magit magit-repos magit-submodule) magit-repos magit-section + magit-todos markdown-mode monky mpc mpdel mu4e mu4e-conversation neotree + newsticker notmuch nov omnisharp org org-present org-roam osx-dictionary + outline p4 (package-menu package) pass (pdf pdf-tools) popup proced + prodigy profiler p-search python quickrun racer racket-describe realgud + reftex replace restclient rg ripgrep rjsx-mode robe rtags ruby-mode + scheme scroll-lock selectrum sh-script + ,@(if (> emacs-major-version 27) '(shortdoc)) simple simple-mpc slime + sly smerge-mode snake so-long speedbar tab-bar tablist tar-mode telega + (term term ansi-term multi-term) tetris thread tide timer-list + transmission trashed tuareg typescript-mode vc-annotate vc-dir vc-git + vdiff vertico view vlf vterm vundo w3m wdired wgrep which-key + with-editor woman xref xwidget yaml-mode youtube-dl zmusic + (ztree ztree-diff))) + + (cl-defun syd-evil-collection-init (module &key disabled-modules) + "Initialise evil-collection-MODULE. + +A wrapper for `evil-collection-init' that respects a given list of disabled +modules." + (let ((module* (or (car-safe module) module))) + (unless (memq module* disabled-modules) + (message "Loading evil-collection-%s%s" + module* + (if after-init-time "" " too early! }:(")) + (with-demoted-errors "error loading evil-collection: %s" + (evil-collection-init (list module)))))) + + (defun syd-evil-collection-disable-blacklist-a (fn) + (let (evil-collection-key-blacklist) + (funcall-interactively fn))) + + ;; Allow binding to ESC. + (advice-add #'evil-collection-vterm-toggle-send-escape + :around #'syd-evil-collection-disable-blacklist-a) + + ;; These modes belong to packages that Emacs always loads at startup, causing + ;; evil-collection and it's co-packages to all load immediately. We avoid + ;; this by loading them after evil-collection has first loaded... + (with-eval-after-load 'evil-collection + (require 'syd-prelude) + (require 'syd-keybinds) + ;; Don't let evil-collection interfere with certain keys + (setq evil-collection-key-blacklist + (append (list syd-leader-key syd-localleader-key + syd-alt-leader-key) + evil-collection-key-blacklist + ;; Reserved for goto definition; lookup docs; eval; eval + ;; buffer; movement prefix; movement prefix; escaping };). + '("gd" "K" "gr" "gR" "[" "]" ""))) + + (mapc #'syd-evil-collection-init '(comint custom)) + + (with-eval-after-load 'evil + ;; Emacs loads these two packages immediately, at startup, which needlessly + ;; convolutes load order for evil-collection-help. + (with-transient-after 'help-mode + (syd-evil-collection-init 'help)) + (with-transient-after 'Buffer-menu-mode + (syd-evil-collection-init '(buff-menu "buff-menu"))) + (with-transient-after 'calc-mode + (syd-evil-collection-init 'calc)) + (with-transient-after 'image-mode + (syd-evil-collection-init 'image)) + (with-transient-after 'emacs-lisp-mode + (syd-evil-collection-init 'elisp-mode)) + (with-transient-after 'occur-mode + (syd-evil-collection-init 'replace)) + (with-transient-after 'indent-rigidly + (syd-evil-collection-init '(indent "indent"))) + (when (>= emacs-major-version 30) + (with-transient-after 'kmacro-menu-mode + (syd-evil-collection-init 'kmacro))) + (with-transient-after 'minibuffer-setup-hook + (when evil-collection-setup-minibuffer + (syd-evil-collection-init 'minibuffer) + (evil-collection-minibuffer-insert))) + (with-transient-after 'process-menu-mode + (syd-evil-collection-init '(process-menu simple))) + (with-transient-after 'shortdoc-mode + (syd-evil-collection-init 'shortdoc)) + (with-transient-after 'tabulated-list-mode + (syd-evil-collection-init 'tabulated-list)) + (with-transient-after 'tab-bar-mode + (syd-evil-collection-init 'tab-bar)) + + ;; HACK: Do this ourselves because evil-collection break's + ;; `eval-after-load' load order by loading their target plugin before + ;; applying keys. This makes it hard for end-users to overwrite these + ;; keybinds with a simple `after!' or `with-eval-after-load'. + (dolist (mode evil-collection-mode-list) + (dolist (req (or (cdr-safe mode) (list mode))) + (with-eval-after-load req + (syd-evil-collection-init + mode + :disabled-modules syd-evil-collection-disabled-list)))))))) ;; Tim Pope's famous `surround.vim' for Evil. (use-package evil-surround diff --git a/users/crumb/programs/emacs/modules/syd-general.el b/users/crumb/programs/emacs/modules/syd-general.el deleted file mode 100755 index 17d266d..0000000 --- a/users/crumb/programs/emacs/modules/syd-general.el +++ /dev/null @@ -1,8 +0,0 @@ -;;; syd-general.el -*- lexical-binding: t; -*- - -(use-package general - :custom (general-use-package-emit-autoloads t)) - -(require 'general) - -(provide 'syd-general) diff --git a/users/crumb/programs/emacs/modules/syd-keybinds.el b/users/crumb/programs/emacs/modules/syd-keybinds.el index 0051875..7edf8e6 100755 --- a/users/crumb/programs/emacs/modules/syd-keybinds.el +++ b/users/crumb/programs/emacs/modules/syd-keybinds.el @@ -1,8 +1,6 @@ ;;; syd-keybinds.el -*- lexical-binding: t; -*- ;;; Universal keybindings, not /too/ tied to any particular packages. -(require 'syd-general) - (defvar syd-leader-key "SPC" "A prefix key akin to Vim's .") @@ -54,9 +52,9 @@ are active.") (general-override-mode 1)) (defvar syd-escape-hook nil - "A hook run when C-g is pressed (or ESC in normal mode, for evil users). + "A hook run when C-g is pressed (or ESC in Evil's normal state). -More specifically, when `syd/escape' is pressed. If any hook returns non-nil, +More specifically, when `syd/escape' is pressed. If any hook returns non-nil, all hooks after it are ignored.") ;; @@ -111,6 +109,13 @@ all hooks after it are ignored.") (global-set-key [remap keyboard-quit] #'syd/escape) + (general-def + :states 'motion + "/" #'evil-ex-search-forward + "?" #'evil-ex-search-backward + "n" #'evil-ex-search-next + "N" #'evil-ex-search-previous) + ;; Buffer (require 'syd-buffers) (general-def @@ -138,7 +143,7 @@ all hooks after it are ignored.") "D" `("Delete file" . ,#'syd/delete-this-file) "R" `("Move file" . ,#'syd/move-this-file) "C" `("Copy file" . ,#'syd/copy-this-file) - ;; "F" `("Find file under current" . ,#'syd/find-file-under-here) + ;; "F" `("Find file under here" . ,#'syd/find-file-under-here) ;; "p" `("Find under Emacs config" . ,#'syd/find-file-under-emacs-user-directory) "P" `("Browse Emacs config" . ,#'syd/find-file-in-emacs-user-directory) "u" `("Find file as root" . ,#'syd/find-file-as-root) diff --git a/users/crumb/programs/emacs/modules/syd-popups.el b/users/crumb/programs/emacs/modules/syd-popups.el index cdd497d..b82c884 100644 --- a/users/crumb/programs/emacs/modules/syd-popups.el +++ b/users/crumb/programs/emacs/modules/syd-popups.el @@ -11,13 +11,8 @@ (popper-mode 1)) (use-package doom-popup - ;; :after popper - ;; :load-path "/persist/dots/users/crumb/programs/emacs/lib/doom-popup" - ;; :straight nil - :straight - (:type git - :host gitlab - :repo "crumbtoo/doom-popup")) + :straight (:type git + :host gitlab + :repo "crumbtoo/doom-popup")) (provide 'syd-popups) -;;; syd-popups.el ends here diff --git a/users/crumb/programs/emacs/modules/syd-smartparens.el b/users/crumb/programs/emacs/modules/syd-smartparens.el index 827187d..cd9cb2c 100755 --- a/users/crumb/programs/emacs/modules/syd-smartparens.el +++ b/users/crumb/programs/emacs/modules/syd-smartparens.el @@ -7,6 +7,8 @@ ;; activated. (use-package smartparens :hook (on-first-buffer . smartparens-global-mode) + :commands (sp-pair sp-local-pair sp-with-modes sp-point-in-comment + sp-point-in-string) :custom ;; Overlays are too distracting and not terribly helpful. show-parens does ;; this for us already (and is faster), so... diff --git a/users/crumb/programs/emacs/modules/syd-ui.el b/users/crumb/programs/emacs/modules/syd-ui.el index e7681e3..81139e1 100755 --- a/users/crumb/programs/emacs/modules/syd-ui.el +++ b/users/crumb/programs/emacs/modules/syd-ui.el @@ -17,14 +17,34 @@ ;; probably not what you want;" I personally don't see it, and it's ;; usually what I want. (vc-follow-symlinks t) - ;; Log native-compiler warnings, but don't display the - ;; buffer. Most of the warnings are "`X' is not known to - ;; be defined" which are typically nothing worth concerning. - (native-comp-async-report-warnings-errors 'silent)) + ;; Log native-compiler warnings, but don't display the + ;; buffer. Most of the warnings are "`X' is not known to + ;; be defined" which are typically nothing worth concerning. + (native-comp-async-report-warnings-errors 'silent) + ;; Don't recenter the view unless >10 lines are scrolled off-screen + ;; in a single movement. + (scroll-conservatively 10)) :config ;; Disable the menu bar, scroll bar, and tool bar. (menu-bar-mode -1) (scroll-bar-mode -1) (tool-bar-mode -1)) +(use-package persp-mode + :unless noninteractive + :commands persp-switch-to-buffer + :hook (on-init-ui . persp-mode) + :config + (setq persp-autokill-buffer-on-remove 'kill-weak + persp-reset-windows-on-nil-window-conf nil + persp-nil-hidden t + persp-auto-save-fname "autosave" + persp-save-dir (concat syd-data-dir "workspaces/") + persp-set-last-persp-for-new-frames t + persp-switch-to-added-buffer nil + persp-kill-foreign-buffer-behaviour 'kill + persp-remove-buffers-from-nil-persp-behaviour nil + persp-auto-resume-time -1 ; Don't auto-load on startup + persp-auto-save-opt (if noninteractive 0 1))) + (provide 'syd-ui)