feat: Progress towards comfortable Lisp editing
This commit is contained in:
12
README.org
12
README.org
@@ -179,6 +179,8 @@ sydnix-cli is a command-line utility written in Clojure wrapping various sydnix-
|
||||
|
||||
** Emacs from scratch
|
||||
|
||||
*** TODO Block escaping with ~jk~ whilst recording a macro
|
||||
|
||||
*** TODO Many editing commands should re-indent after use
|
||||
|
||||
Particularly in Lisps where indentation is reliable.
|
||||
@@ -263,18 +265,14 @@ Seems old and broken?
|
||||
|
||||
**** TODO Evil stuff
|
||||
|
||||
**** TODO Text objects
|
||||
**** DONE Text objects
|
||||
|
||||
**** DONE [[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
|
||||
**** DONE 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]]
|
||||
@@ -648,3 +646,5 @@ Beloved Faye's Wishsys is an incredibly impressive 3-kloc NixOS config with seve
|
||||
- [[https://github.com/neeasade/emacs.d][neeasade/emacs.d]] — Has an interesting 'module' system.
|
||||
- [[https://github.com/oantolin/emacs-config][oantolin/emacs-config]] — Has some internal packages.
|
||||
- [[https://github.com/noctuid/evil-guide][noctuid/evil-guide]]
|
||||
- [[https://github.com/drym-org/symex.el][symex.el]]
|
||||
- [[https://github.com/Fuco1/smartparens][Smartparens]]
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
;; Initialise Straight.el
|
||||
|
||||
(+ 1 2)
|
||||
|
||||
(load (locate-user-emacs-file "init-straight"))
|
||||
(syd-initialise-straight)
|
||||
|
||||
@@ -53,8 +51,9 @@
|
||||
(require 'syd-completion)
|
||||
(require 'syd-custom)
|
||||
(require 'syd-display-startup-time)
|
||||
(require 'syd-evil)
|
||||
(require 'syd-editing)
|
||||
(require 'syd-eshell)
|
||||
(require 'syd-evil)
|
||||
(require 'syd-keybinds)
|
||||
(require 'syd-lang)
|
||||
(require 'syd-org)
|
||||
|
||||
15
users/crumb/programs/emacs/lib/clj-lib.el
Normal file
15
users/crumb/programs/emacs/lib/clj-lib.el
Normal file
@@ -0,0 +1,15 @@
|
||||
;;; clj-lib.el -*- lexical-binding: t; -*-
|
||||
|
||||
(require 'dash)
|
||||
|
||||
(defmacro clj-condp (pred expr &rest clauses)
|
||||
"TODO: Very unfinished."
|
||||
(declare (indent defun))
|
||||
(unless (symbolp pred)
|
||||
(signal 'wrong-type-argument `(symbolp ,pred)))
|
||||
(let ((expr* (gensym "expr")))
|
||||
`(let ((,expr* ,expr))
|
||||
(cond ,@(mapcar (lambda (x) `((,pred ,expr ,(car x)) ,(nth 1 x)))
|
||||
clauses)))))
|
||||
|
||||
(provide 'clj-lib)
|
||||
485
users/crumb/programs/emacs/lib/syd-lisp-lib.el
Normal file
485
users/crumb/programs/emacs/lib/syd-lisp-lib.el
Normal file
@@ -0,0 +1,485 @@
|
||||
;;; syd-lisp-lib.el -*- lexical-binding: t; -*-
|
||||
|
||||
(require 'general)
|
||||
(require 'clj-lib)
|
||||
|
||||
(use-package smartparens
|
||||
:defer t)
|
||||
|
||||
(use-package evil-surround
|
||||
:defer t)
|
||||
|
||||
;; Include various lispy symbols as word constituents.
|
||||
(dolist (c '(?- ?_ ?? ?! ?+ ?* ?/ ?: ?> ?< ?= ?&))
|
||||
(modify-syntax-entry c "w" lisp-data-mode-syntax-table))
|
||||
|
||||
;;;###autoload
|
||||
(defvar-keymap syd-lisp-mode-map
|
||||
:doc "Keymap for `syd-lisp-mode'.")
|
||||
|
||||
;;;###autoload
|
||||
(define-minor-mode syd-lisp-mode
|
||||
"A minor mode for editing lispy languages."
|
||||
:keymap syd-lisp-mode-map)
|
||||
|
||||
;;;###autoload
|
||||
(defun syd-wrap-sexp (char)
|
||||
"Wrap the sexp at point (using `smartparens') with the pair corresponding to
|
||||
CHAR (using `evil-surround'). Unlike other `evil-surround' operations, the
|
||||
point will be preserved and the wrapped region will be re-indented."
|
||||
(interactive (evil-surround-input-char))
|
||||
(sp-get (sp-get-thing)
|
||||
(save-excursion
|
||||
(evil-surround-region :beg :end 'inclusive char)
|
||||
(indent-region :beg :end))))
|
||||
|
||||
;;;###autoload
|
||||
(evil-define-motion syd-get-enclosing-sexp ()
|
||||
"Like `sp-get-enclosing-sexp', but with a slightly different meaning of
|
||||
\"enclosing sexp\" that matches Vim-sexp's"
|
||||
(or (let ((sexp-at-point (sp-get-sexp)))
|
||||
(sp-get sexp-at-point
|
||||
(when (or (and :beg (= (point) :beg))
|
||||
(and :end (= (point) (- :end 1))))
|
||||
sexp-at-point)))
|
||||
(let ((sp-enclosing-sexp (sp-get-enclosing-sexp)))
|
||||
(sp-get sp-enclosing-sexp
|
||||
(when :beg
|
||||
sp-enclosing-sexp)))))
|
||||
|
||||
;;;###autoload
|
||||
(evil-define-motion syd-backward-up-sexp (count)
|
||||
"Move point to the opening bracket of the enclosing sexp. The precise meaning
|
||||
of \"enclosing sexp\" differs slightly from that used by Smartparens for the
|
||||
sake of a more Vim-like feel inspired by vim-sexp."
|
||||
:type exclusive
|
||||
(dotimes (_ (or count 1))
|
||||
;; REVIEW: Is there a better way to do this? I'm slightly uncomfortable
|
||||
;; calling two different `sp-get-*' functions.
|
||||
(or (sp-get (sp-get-sexp)
|
||||
(when (and :end (= (point) (- :end 1)))
|
||||
(goto-char :beg)))
|
||||
(sp-get (sp-get-enclosing-sexp)
|
||||
(when :beg
|
||||
(goto-char :beg))))))
|
||||
|
||||
;;;###autoload
|
||||
(evil-define-motion syd-forward-up-sexp (&optional count)
|
||||
"Move point to the closing bracket of the enclosing sexp. See
|
||||
`syd-backward-up-sexp'."
|
||||
:type exclusive
|
||||
(dotimes (_ (or count 1))
|
||||
(or (sp-get (sp-get-sexp)
|
||||
(when (and :beg (= (point) :beg))
|
||||
(goto-char (- :end 1))))
|
||||
(sp-get (sp-get-enclosing-sexp)
|
||||
(when :end
|
||||
(if (= (point) (- :end 1))
|
||||
(sp-get (save-excursion (forward-char)
|
||||
(sp-get-enclosing-sexp))
|
||||
(when :end
|
||||
(goto-char (- :end 1))))
|
||||
(goto-char (- :end 1))))))))
|
||||
|
||||
;;;###autoload
|
||||
(defun syd-get-top-level-sexp ()
|
||||
"Get the top-level sexp enclosing point. Destructure with `sp-get'.'"
|
||||
;; The end position returned by `bounds-of-thing-at-point' includes an
|
||||
;; unpredictable amount of trailing whitespace, so we discard it and compute
|
||||
;; our own figure.
|
||||
(let ((original-point (point)))
|
||||
(-when-let ((beg . _) (bounds-of-thing-at-point 'defun))
|
||||
(save-excursion
|
||||
(goto-char beg)
|
||||
;; We can trust Smarparents to get the desired end position.
|
||||
(-let* ((top-level-sexp (sp-get-sexp))
|
||||
((_ . end) (sp-get top-level-sexp (cons :beg :end))))
|
||||
;; If the sexp is behind point, we aren't interested in it; find one
|
||||
;; /ahead/ of point.
|
||||
(if (< original-point end)
|
||||
top-level-sexp
|
||||
(goto-char end)
|
||||
(sp-next-sexp)
|
||||
(sp-get-sexp)))))))
|
||||
|
||||
;;;###autoload
|
||||
(defun syd-get-top-level-sexp-and-attached-comment-bounds ()
|
||||
"Get the bounds of top-level sexp enclosing point and the \"attached\"
|
||||
comment, if there is one. Returns nil or a pair (BEG . END)."
|
||||
(-when-let ((beg . end) (sp-get (syd-get-top-level-sexp) (cons :beg :end)))
|
||||
(let ((attached-comment-beg (save-excursion
|
||||
(goto-char beg)
|
||||
(syd-sexp--backward-attached-comment))))
|
||||
(cons (or attached-comment-beg beg)
|
||||
end))))
|
||||
|
||||
(evil-define-motion syd-forward-defun (count)
|
||||
:jump t
|
||||
(sp-get (syd-get-top-level-sexp)
|
||||
(goto-char :beg)
|
||||
(dotimes (_ (or count 1))
|
||||
(sp-next-sexp))))
|
||||
|
||||
(defvar syd-sexp-cleanup-operators '(evil-delete)
|
||||
"When `syd-evil-a-defun' is used in combination with one of these operators,
|
||||
some cleanup will be performed.")
|
||||
|
||||
(defun syd-sexp--backward-attached-comment ()
|
||||
"Assuming point is on the opening delimiter of a sexp, move point backward to
|
||||
the beginning of the \"attached\" comment."
|
||||
(let ((sexp-line (line-number-at-pos))
|
||||
(sexp-column (current-column)))
|
||||
(-when-let ((beg . _end) (save-excursion
|
||||
(goto-line (- sexp-line 1))
|
||||
(evil-forward-char sexp-column t)
|
||||
(sp-get-comment-bounds)))
|
||||
(goto-char beg))))
|
||||
|
||||
;;;###autoload
|
||||
(evil-define-text-object syd-evil-a-defun (count _beg _end _type)
|
||||
"Selects the enclosing top-level sexp. With a COUNT of N, that many
|
||||
consequtive top-level sexps will be selected. TODO: Special care will be taken
|
||||
to clean up whitespace following certain operators."
|
||||
:type inclusive
|
||||
(when (< count 0)
|
||||
(user-error "TODO: Negative count"))
|
||||
(-let ((cleanup-p (memq evil-this-operator syd-sexp-cleanup-operators))
|
||||
((beg-0 . end-0)
|
||||
(syd-get-top-level-sexp-and-attached-comment-bounds)))
|
||||
(if (or (null count) (= count 1))
|
||||
(list beg-0 end-0)
|
||||
(goto-char end-0)
|
||||
(dotimes (_ (- count 1))
|
||||
(sp-next-sexp))
|
||||
(sp-get (sp-get-sexp)
|
||||
(list beg-0 :end)))))
|
||||
|
||||
;; IDEA: How about the inner-defun text object selects the defun /without/ the
|
||||
;; comment? Is that more useful, or less? I can't think of the last time I've
|
||||
;; needed the top-level sexp without the brackets.
|
||||
|
||||
;;;###autoload
|
||||
(evil-define-text-object syd-evil-inner-defun (_count _beg _end _type)
|
||||
"Select the *content* of the enclosing top-level sexp, i.e. without the
|
||||
delimiters."
|
||||
:type inclusive
|
||||
(sp-get (syd-get-top-level-sexp)
|
||||
(list (+ :beg 1)
|
||||
(- :end 1))))
|
||||
|
||||
(defun syd-sexp--forward-trailing-whitespace (sexp)
|
||||
"Move point to the end of the whitespace trailing after SEXP."
|
||||
(goto-char (sp-get sexp :end))
|
||||
(skip-chars-forward "[:blank:]")
|
||||
(when (= (char-after) ?\n)
|
||||
(forward-char)
|
||||
(skip-chars-forward "[:blank:]")))
|
||||
|
||||
(defun syd-sexp--backward-leading-whitespace (sexp)
|
||||
"Move point to the beginning of the whitespace preceding SEXP."
|
||||
(goto-char (sp-get sexp :beg))
|
||||
(skip-chars-backward "[:blank:]")
|
||||
(when (= (char-before) ?\n)
|
||||
(backward-char)
|
||||
(skip-chars-backward "[:blank:]")))
|
||||
|
||||
;;;###autoload
|
||||
(evil-define-text-object syd-evil-a-form (count _beg _end _type)
|
||||
(let* ((cleanup-p (memq evil-this-operator syd-sexp-cleanup-operators))
|
||||
(sexp (syd-get-enclosing-sexp)))
|
||||
(if cleanup-p
|
||||
(save-excursion
|
||||
(if (syd-sexp--looking-at-last-p)
|
||||
(progn (syd-sexp--backward-leading-whitespace sexp)
|
||||
(list (point) (sp-get sexp :end)))
|
||||
(syd-sexp--forward-trailing-whitespace sexp)
|
||||
(list (sp-get sexp :beg) (point))))
|
||||
(sp-get sexp (list :beg :end)))))
|
||||
|
||||
;;;###autoload
|
||||
(evil-define-text-object syd-evil-inner-form (count _beg _end _type)
|
||||
(sp-get (syd-get-enclosing-sexp)
|
||||
(list (+ :beg 1) (- :end 1))))
|
||||
|
||||
;;;###autoload
|
||||
(evil-define-command syd-open-sexp-below ()
|
||||
"Insert a newline with appropriate indentation after the enclosing sexp. A
|
||||
sexp-wise analogue to Evil's line-wise `evil-open-below'."
|
||||
:suppress-operator t
|
||||
(evil-with-single-undo
|
||||
;; We want to add an additional blank line when operating at the top level.
|
||||
;; Instead of parsing upward until we can no longer find an enclosing sexp, we
|
||||
;; simply check if the opening bracket is on the first column. This is not
|
||||
;; very correct, but it's way less work (for myself and the CPU). If we
|
||||
;; switch to a tree-sitter–based parser, I'd love to switch to the correct
|
||||
;; algorithm.
|
||||
(-let* (((beg . end) (sp-get (syd-get-enclosing-sexp) (cons :beg :end)))
|
||||
(col (save-excursion (goto-char beg) (current-column))))
|
||||
(goto-char end)
|
||||
(if (= col 0)
|
||||
(newline 2)
|
||||
(newline-and-indent))))
|
||||
(evil-insert-state 1))
|
||||
|
||||
;;;###autoload
|
||||
(evil-define-command syd-open-sexp-above ()
|
||||
"Insert a newline with appropriate indentation above the enclosing sexp. A
|
||||
sexp-wise analogue to Evil's line-wise `evil-open-above'."
|
||||
:suppress-operator t
|
||||
(evil-with-single-undo
|
||||
(let ((beg (sp-get (syd-get-enclosing-sexp) :beg)))
|
||||
(goto-char beg)
|
||||
(syd-sexp--backward-attached-comment)
|
||||
(let ((col (current-column)))
|
||||
(save-excursion
|
||||
;; We want to add an additional blank line when operating at the top
|
||||
;; level. Instead of parsing upward until we can no longer find an
|
||||
;; enclosing sexp, we simply check if the opening bracket is on the
|
||||
;; first column. This is not very correct, but it's way less work (for
|
||||
;; myself and the CPU). If we switch to a tree-sitter–based parser, I'd
|
||||
;; love to switch to the correct algorithm.
|
||||
(if (= col 0)
|
||||
(newline 2)
|
||||
(newline-and-indent)))
|
||||
(indent-to col)
|
||||
(evil-insert-state 1)))))
|
||||
|
||||
(defun syd-sexp-get-last-thing ()
|
||||
(-let (((enclosing-beg . enclosing-end)
|
||||
(sp-get (syd-get-enclosing-sexp) (cons :beg :end))))
|
||||
(save-excursion
|
||||
;; Imperative andy. }:\
|
||||
(let (thing)
|
||||
(while (sp-get (syd-get-thing)
|
||||
(and (< enclosing-beg :beg enclosing-end)
|
||||
(< enclosing-beg :end enclosing-end))))))))
|
||||
|
||||
(defun syd-sexp--looking-at-last-p ()
|
||||
"Return non-nil if the sexp beginning at point is the last element of its
|
||||
enclosing sexp."
|
||||
(save-excursion
|
||||
(let ((point-0 (point))
|
||||
(sexp (sp-get-enclosing-sexp)))
|
||||
(sp-next-sexp)
|
||||
(if sexp
|
||||
(or
|
||||
;; If `sp-next-sexp' moved backwards, `point-0' was the last
|
||||
;; element.
|
||||
(<= (point) point-0)
|
||||
;; If `sp-next-sexp' moved outside of the previously-enclosing
|
||||
;; sexp, `point-0' was final.
|
||||
(<= (sp-get sexp :end) (point)))
|
||||
;; No enclosing sexp — we're looking at a top-level sexp.
|
||||
(= (point) point-0)))))
|
||||
|
||||
(defun syd-sexp--next-thing ()
|
||||
"Helper for `syd-sexo->'. Find the next thing relative to the sexp assumed to
|
||||
begin at point, and the region covering the closing delimiters."
|
||||
(save-excursion
|
||||
(condition-case err
|
||||
(cl-loop for relative-height from 0
|
||||
while (syd-sexp--looking-at-last-p)
|
||||
do (or (sp-backward-up-sexp)
|
||||
;; Nothing to slurp!
|
||||
(signal 'top))
|
||||
finally return (cons (sp-next-sexp) relative-height))
|
||||
(top nil))))
|
||||
|
||||
(defun syd-sexp--slurp-forward ()
|
||||
"Slurp forward. Do not call this function directly; see `syd-sexp->'."
|
||||
;; REVIEW: This is rather unoptimised when used with a count.
|
||||
(when-let* ((consumer (sp-get-sexp)))
|
||||
(goto-char (sp-get consumer :beg))
|
||||
(-if-let ((next-thing . relative-height) (syd-sexp--next-thing))
|
||||
(progn (goto-char (sp-get consumer :beg-in))
|
||||
(sp-forward-slurp-sexp (+ 1 relative-height))
|
||||
(sp-get (sp-get-enclosing-sexp)
|
||||
(goto-char (- :end 1))))
|
||||
(user-error "ra"))))
|
||||
|
||||
(defun syd-sexp--barf-forward ()
|
||||
"Barf forward. Do not call this function directly; see `syd-sexp-<'."
|
||||
(sp-forward-barf-sexp))
|
||||
|
||||
;;;###autoload
|
||||
(evil-define-command syd-sexp-> (&optional count)
|
||||
(interactive "<c>")
|
||||
(evil-with-single-undo
|
||||
(when-let* ((sexp (sp-get-sexp)))
|
||||
(let ((fn (cond ((= (point) (sp-get sexp (- :end 1)))
|
||||
#'syd-sexp--slurp-forward))))
|
||||
(dotimes (_ (or count 1))
|
||||
(funcall fn))))))
|
||||
|
||||
;;;###autoload
|
||||
(evil-define-command syd-sexp-< (&optional count)
|
||||
(interactive "<c>")
|
||||
(evil-with-single-undo
|
||||
(when-let* ((sexp (sp-get-sexp)))
|
||||
(let ((fn (cond ((= (point) (sp-get sexp (- :end 1)))
|
||||
#'syd-sexp--barf-forward))))
|
||||
(dotimes (_ (or count 1))
|
||||
(funcall fn))))))
|
||||
|
||||
(defun syd-sexp--looking-at-delimiter-p ()
|
||||
(sp-get (sp-get-sexp)
|
||||
(and (not (sp-point-in-string-or-comment))
|
||||
(or (= (point) :beg)
|
||||
(= (point) (- :end 1))))))
|
||||
|
||||
;; REVIEW: It might be neat to, iff the point is already in a comment/string,
|
||||
;; goto delimiters that are also in comments/strings. For now, let's just
|
||||
;; ignore comments.
|
||||
(defun syd-sexp--goto-delimiter (delimiter-type direction count)
|
||||
(let* ((point-0 (point))
|
||||
(delimiters (mapcar (clj-condp eq delimiter-type
|
||||
('opening #'car)
|
||||
('closing #'cdr))
|
||||
sp-pair-list))
|
||||
(delimiter-regexp (rx-to-string `(or ,@delimiters)))
|
||||
(forward-p (clj-condp eq direction
|
||||
('forward t)
|
||||
('backward nil)
|
||||
(t (error "todo errrrare"))))
|
||||
(move (lambda ()
|
||||
;; `forward-p' never changes between calls to `move'; we are
|
||||
;; doing many more checks than we need to.
|
||||
(and (condition-case er
|
||||
(prog1 t (when forward-p
|
||||
(forward-char)))
|
||||
(end-of-buffer (throw 'no-move 'no-move)))
|
||||
(if (if forward-p
|
||||
(re-search-forward delimiter-regexp nil t)
|
||||
(re-search-backward delimiter-regexp nil t))
|
||||
(goto-char (match-beginning 0))
|
||||
(throw 'no-move 'no-move))))))
|
||||
;; If `syd-sexp--looking-at-delimiter-p' returns nil, we may be looking at
|
||||
;; the right string of characters, but we are likely inside of a string,
|
||||
;; or a comment, or something. If we aren't at a "real" delimiter, move
|
||||
;; again.
|
||||
(let ((r (catch 'no-move
|
||||
(dotimes (_ count)
|
||||
(while (and (funcall move)
|
||||
(not (syd-sexp--looking-at-delimiter-p))))))))
|
||||
(if (eq r 'no-move)
|
||||
(progn (goto-char point-0)
|
||||
(user-error "Nowhere to go"))
|
||||
r))))
|
||||
|
||||
(evil-define-motion syd-sexp-forward-opening (count)
|
||||
(syd-sexp--goto-delimiter 'opening 'forward (or count 1)))
|
||||
|
||||
(evil-define-motion syd-sexp-backward-opening (count)
|
||||
(syd-sexp--goto-delimiter 'opening 'backward (or count 1)))
|
||||
|
||||
(evil-define-motion syd-sexp-forward-closing (count)
|
||||
(syd-sexp--goto-delimiter 'closing 'forward (or count 1)))
|
||||
|
||||
(evil-define-motion syd-sexp-backward-closing (count)
|
||||
(syd-sexp--goto-delimiter 'closing 'backward (or count 1)))
|
||||
|
||||
(defun syd-sexp-get-sexp-with-prefix ()
|
||||
(-when-let* ((thing (sp-get-thing))
|
||||
;; TODO: Rewrite using :beg-prf
|
||||
((beg . prefix) (sp-get thing (cons :beg :prefix)))
|
||||
(prefix-beg (- beg (length prefix))))
|
||||
;; HACK: Relies on Smartparen's internal representation, which
|
||||
;; they explicitly recommend against. This could break at any
|
||||
;; time!
|
||||
;; Reminder that `plist-put' is an in-place update. }:)
|
||||
(plist-put thing :beg prefix-beg)
|
||||
(plist-put thing :prefix "")
|
||||
(goto-char prefix-beg)
|
||||
thing))
|
||||
|
||||
(evil-define-motion syd-sexp-next (count)
|
||||
"Like `sp-next-sexp', but prefixes will be considered as part of the sexp."
|
||||
;; If point is resting on a prefix when `syd-sexp-next' is called,
|
||||
;; `sp-next-sexp' will move to the beginning of the prefixed form. This is
|
||||
;; undesirable, as `syd-sexp-next' considers the prefix and the prefixed form
|
||||
;; to be a single thing. To get around this, we make sure to move point past
|
||||
;; the prefixed sexp.
|
||||
(let ((count* (or count 1)))
|
||||
(when-let* ((_ (<= 0 count*))
|
||||
(first-prefixed-sexp (syd-sexp-get-sexp-with-prefix)))
|
||||
(sp-get first-prefixed-sexp
|
||||
(when (<= :beg (point) :end)
|
||||
(goto-char :end))))
|
||||
(let ((current-prefix-arg count*))
|
||||
(call-interactively #'sp-next-sexp)))
|
||||
(syd-sexp-get-sexp-with-prefix))
|
||||
|
||||
(evil-define-motion syd-sexp-previous (count)
|
||||
"Like `sp-next-sexp' (as if called with a negative count), but prefixes will
|
||||
be considered as part of the sexp."
|
||||
(syd-sexp-next (- (or count 1))))
|
||||
|
||||
;;;###autoload
|
||||
(evil-define-command syd-sexp-insert ()
|
||||
(evil-with-single-undo
|
||||
(sp-get (syd-get-enclosing-sexp)
|
||||
(goto-char (+ 1 :beg))
|
||||
(save-excursion (insert-char ?\s))
|
||||
(evil-insert-state 1))))
|
||||
|
||||
;;;###autoload
|
||||
(evil-define-command syd-sexp-append ()
|
||||
(evil-with-single-undo
|
||||
(sp-get (syd-get-enclosing-sexp)
|
||||
(goto-char (- :end 1))
|
||||
(evil-insert-state 1))))
|
||||
|
||||
;; Text objects.
|
||||
(general-def
|
||||
:keymaps 'syd-lisp-mode-map
|
||||
:states '(visual operator)
|
||||
"ad" #'syd-evil-a-defun
|
||||
"id" #'syd-evil-inner-defun
|
||||
"af" #'syd-evil-a-form
|
||||
"if" #'syd-evil-inner-form)
|
||||
|
||||
;; Bind editing commands in normal node, and motion commands in motion
|
||||
;; mode.
|
||||
(general-def
|
||||
:keymaps 'syd-lisp-mode-map
|
||||
:states 'normal
|
||||
">" #'syd-sexp->
|
||||
"<" #'syd-sexp-<
|
||||
"M-w" #'syd-wrap-sexp
|
||||
"M-r" #'sp-raise-sexp
|
||||
"M-c" #'sp-clone-sexp
|
||||
"M-S" #'sp-split-sexp
|
||||
"M-J" #'sp-join-sexp
|
||||
"M-u" #'sp-splice-sexp-killing-backward
|
||||
"M-U" #'sp-splice-sexp-killing-around
|
||||
"M-v" #'sp-convolute-sexp
|
||||
"M-o" #'syd-open-sexp-below
|
||||
"M-O" #'syd-open-sexp-above
|
||||
"M-i" #'syd-sexp-insert
|
||||
"M-a" #'syd-sexp-append)
|
||||
|
||||
;; Bind editing commands in normal node, and motion commands in motion
|
||||
;; mode.
|
||||
(general-def
|
||||
:keymaps 'syd-lisp-mode-map
|
||||
:states 'motion
|
||||
"C-h" #'sp-backward-up-sexp
|
||||
"C-j" #'syd-sexp-next
|
||||
"C-k" #'syd-sexp-previous
|
||||
"C-l" #'sp-down-sexp
|
||||
"(" #'syd-backward-up-sexp
|
||||
")" #'syd-forward-up-sexp
|
||||
"{" #'syd-sexp-backward-opening
|
||||
"}" #'syd-sexp-forward-opening
|
||||
"M-{" #'syd-sexp-backward-closing
|
||||
"M-}" #'syd-sexp-forward-closing)
|
||||
|
||||
(with-eval-after-load 'smartparens
|
||||
(setq
|
||||
;; By default, Smartparens will move backwards to the initial character of
|
||||
;; the enclosing expression, and only move forwards when the point is already
|
||||
;; on that initial character. This is not expected behaviour for an ex-Vim
|
||||
;; user.
|
||||
sp-navigate-interactive-always-progress-point t))
|
||||
|
||||
(provide 'syd-lisp-lib)
|
||||
@@ -66,4 +66,12 @@
|
||||
(put ',hook-name 'permanent-local-hook t)
|
||||
(add-hook ,hook-or-function* #',hook-name))))))
|
||||
|
||||
(defun syd-plist-put (plist prop new-val)
|
||||
"Immutably update a single property of PLIST. Like `plist-put', but PLIST is
|
||||
not mutated; a new plist is returned."
|
||||
(cl-loop for (prop* old-val) on plist by #'cddr
|
||||
appending (if (eq prop prop*)
|
||||
(list prop* new-val)
|
||||
(list prop* old-val))))
|
||||
|
||||
(provide 'syd-prelude)
|
||||
|
||||
@@ -69,16 +69,24 @@ in some cases."
|
||||
(read-string (if (stringp prompt) prompt "")))))
|
||||
|
||||
;;;###autoload
|
||||
(defun syd-insert-newline-above ()
|
||||
(defun syd-insert-newline-above (count)
|
||||
"Insert a blank line below the current line."
|
||||
(interactive)
|
||||
(save-excursion (evil-insert-newline-above)))
|
||||
(interactive "p")
|
||||
(dotimes (_ count)
|
||||
(let ((point-was-at-bol-p (= (current-column) 0)))
|
||||
(save-excursion
|
||||
(evil-insert-newline-above))
|
||||
;; Special case: with `syd-insert-newline-above' is called with point at
|
||||
;; BOL, the point unexpectedly fails to "stick" to its original position.
|
||||
(when point-was-at-bol-p
|
||||
(next-line)))))
|
||||
|
||||
;;;###autoload
|
||||
(defun syd-insert-newline-below ()
|
||||
(defun syd-insert-newline-below (count)
|
||||
"Insert a blank line below the current line."
|
||||
(interactive)
|
||||
(save-excursion (evil-insert-newline-below)))
|
||||
(interactive "p")
|
||||
(dotimes (_ count)
|
||||
(save-excursion (evil-insert-newline-below))))
|
||||
|
||||
;;;###autoload
|
||||
(defun syd-render-ansi-escape-codes (beg end)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
(require 'syd-handle-repl)
|
||||
(require 'syd-handle-lookup)
|
||||
(require 'syd-handle-eval)
|
||||
(require 'syd-lisp-lib)
|
||||
;; (require 'handle)
|
||||
|
||||
;; Don't `use-package' `ielm', since it's loaded by Emacs. You'll get weird
|
||||
@@ -12,6 +13,7 @@
|
||||
:custom ((ielm-history-file-name ; Stay out of my config dir!
|
||||
(file-name-concat syd-cache-dir "ielm-history.eld"))))
|
||||
|
||||
;;;###autoload
|
||||
(defun syd/open-emacs-lisp-repl ()
|
||||
(interactive)
|
||||
(pop-to-buffer
|
||||
@@ -23,21 +25,19 @@
|
||||
(bury-buffer b)
|
||||
b)))))
|
||||
|
||||
;;;###autoload
|
||||
(defun syd-emacs-lisp-lookup-documentation (identifier)
|
||||
"Lookup IDENTIFIER with `describe-symbol'"
|
||||
;; HACK: Much to my frustration, `describe-symbol' has no defined
|
||||
;; return value. To test if the call was successful or not, we
|
||||
;; check if any window is displaying the help buffer. This probably
|
||||
;; breaks if `syd-emacs-lisp-lookup-documentation' is called while
|
||||
;; the help buffer is already open.
|
||||
;; HACK: Much to my frustration, `describe-symbol' has no defined return
|
||||
;; value. To test if the call was successful or not, we check if any window
|
||||
;; is displaying the help buffer. This probably breaks if
|
||||
;; `syd-emacs-lisp-lookup-documentation' is called while the help buffer is
|
||||
;; already open.
|
||||
(describe-symbol (intern identifier))
|
||||
(let ((buffer (get-buffer (help-buffer))))
|
||||
(and (get-buffer-window-list buffer)
|
||||
buffer)))
|
||||
|
||||
(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
|
||||
@@ -60,13 +60,35 @@ to a pop up buffer."
|
||||
:source-buffer (current-buffer)
|
||||
:force-popup current-prefix-arg))
|
||||
|
||||
(set-repl-handler! 'emacs-lisp-mode
|
||||
#'syd/open-emacs-lisp-repl)
|
||||
|
||||
(set-eval-handler! 'emacs-lisp-mode
|
||||
#'syd-emacs-lisp-eval)
|
||||
|
||||
(defun syd-emacs-set-handlers ()
|
||||
(add-hook 'emacs-lisp-mode-hook #'syd-lisp-mode)
|
||||
|
||||
(defun syd-emacs-set-handlers-h ()
|
||||
(setq-local syd-lookup-documentation-handlers
|
||||
(list #'syd-emacs-lisp-lookup-documentation)))
|
||||
|
||||
(add-hook 'emacs-lisp-mode-hook #'syd-emacs-set-handlers)
|
||||
(add-hook 'emacs-lisp-mode-hook #'syd-emacs-set-handlers-h)
|
||||
(add-hook 'help-mode-hook #'syd-emacs-set-handlers-h)
|
||||
|
||||
;; Semantic highlighting for Elisp.
|
||||
(use-package highlight-defined
|
||||
:hook (emacs-lisp-mode-hook . highlight-defined-mode))
|
||||
|
||||
;; Automatically and inteligently expand abbreviations. E.g. `wcb` will be
|
||||
;; expanded to `(with-current-buffer)`, but only where it makes sense for a
|
||||
;; function/macro call to be.
|
||||
(use-package sotlisp
|
||||
:straight (:host github
|
||||
:repo "Malabarba/speed-of-thought-lisp")
|
||||
:hook (emacs-lisp-mode . speed-of-thought-mode))
|
||||
|
||||
;; Give different pairs of delimiters different colours.
|
||||
(use-package rainbow-delimiters
|
||||
:hook (emacs-lisp-mode . rainbow-delimiters-mode))
|
||||
|
||||
(provide 'syd-lang-emacs-lisp)
|
||||
|
||||
11
users/crumb/programs/emacs/modules/syd-editing.el
Normal file
11
users/crumb/programs/emacs/modules/syd-editing.el
Normal file
@@ -0,0 +1,11 @@
|
||||
;;; syd-editing.el -*- lexical-binding: t; -*-
|
||||
|
||||
(use-package emacs
|
||||
:hook ((on-init-ui-hook . whitespace-mode))
|
||||
:custom ((fill-column 80)
|
||||
(indent-tabs-mode nil)
|
||||
(whitespace-style '(face tabs tab-mark))
|
||||
;; Disable synchronization between the kill ring and clipboard.
|
||||
(select-enable-clipboard nil)))
|
||||
|
||||
(provide 'syd-editing)
|
||||
@@ -1,5 +1,8 @@
|
||||
;;; syd-eshell.el -*- lexical-binding: t; -*-
|
||||
|
||||
(require 'ring)
|
||||
(require 'cl-lib)
|
||||
|
||||
(defvar eshell-buffer-name "*eshell*")
|
||||
|
||||
(defvar syd-eshell-buffers (make-ring 25)
|
||||
@@ -72,7 +75,11 @@
|
||||
(eshell-kill-processes-on-exit t)
|
||||
(eshell-hist-ignoredups t)
|
||||
(eshell-glob-case-insensitive t)
|
||||
(eshell-error-if-no-glob t))
|
||||
(eshell-error-if-no-glob t)
|
||||
(eshell-history-file-name (file-name-concat
|
||||
syd-data-dir "eshell" "history"))
|
||||
(eshell-last-dir-ring-file-name (file-name-concat
|
||||
syd-data-dir "eshell" "lastdir")))
|
||||
:general
|
||||
(:keymaps 'syd-leader-open-map
|
||||
"e" #'syd-eshell/toggle)
|
||||
@@ -80,6 +87,7 @@
|
||||
:states '(normal insert)
|
||||
"C-j" #'eshell-next-matching-input-from-input
|
||||
"C-k" #'eshell-previous-matching-input-from-input)
|
||||
|
||||
:config
|
||||
(require 'syd-buffers)
|
||||
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
;;; syd-evil.el -*- lexical-binding: t; -*-
|
||||
|
||||
;; More sensible undo functionality. Emacs' default is very weird, not
|
||||
;; maintaining a proper history.
|
||||
(use-package undo-fu)
|
||||
|
||||
;; Vim emulation.
|
||||
(use-package evil
|
||||
:preface
|
||||
@@ -60,18 +64,24 @@ Otherwise, nil."
|
||||
nil)))
|
||||
(add-hook 'syd-escape-hook #'syd-evil-nohl-h)
|
||||
|
||||
(general-def
|
||||
:states 'motion
|
||||
"/" #'evil-ex-search-forward
|
||||
"?" #'evil-ex-search-backward
|
||||
"n" #'evil-ex-search-next
|
||||
"N" #'evil-ex-search-previous
|
||||
"*" #'evil-ex-search-word-forward)
|
||||
|
||||
(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
|
||||
;; :after evil
|
||||
;; :defer t
|
||||
:custom (evil-collection-setup-minibuffer t)
|
||||
:config
|
||||
:preface
|
||||
(defvar evil-collection-key-blacklist)
|
||||
(unless noninteractive
|
||||
(defvar syd-evil-collection-disabled-list
|
||||
'(anaconda-mode buff-menu calc comint company custom eldoc elisp-mode ert
|
||||
@@ -314,4 +324,29 @@ modules."
|
||||
:bind (:map evil-visual-state-map
|
||||
("*" . evil-visualstar/begin-search-forward)))
|
||||
|
||||
(defvar syd-evil-last-eval-expression-register ?e
|
||||
"An Evil-mode register in which the last expression evaluated with an
|
||||
interactive call to `eval-expression' is stored.")
|
||||
|
||||
(with-eval-after-load 'evil
|
||||
(defun syd-set-eval-expression-register-a (expr &rest _)
|
||||
"If called interactively, set the register
|
||||
`syd-evil-last-eval-expression-register' to a printed form of EXPR."
|
||||
(when (called-interactively-p 'interactive)
|
||||
(->> (pp-to-string expr)
|
||||
(string-remove-suffix "\n")
|
||||
(evil-set-register syd-evil-last-eval-expression-register))))
|
||||
(advice-add #'eval-expression
|
||||
:after #'syd-set-eval-expression-register-a))
|
||||
|
||||
;; HACK: '=' unpredictably moves the cursor when it really doesn't need to.
|
||||
(defun syd-evil-dont-move-point-a (fn &rest args)
|
||||
"Used as :around advice on Evil operators to avoid moving the point."
|
||||
;; We don't use `save-excursion', as we /only/ want to restore the point.
|
||||
(save-excursion (apply fn args)))
|
||||
|
||||
(with-eval-after-load 'evil
|
||||
(advice-add #'evil-indent
|
||||
:around #'syd-evil-dont-move-point-a))
|
||||
|
||||
(provide 'syd-evil)
|
||||
|
||||
@@ -108,13 +108,6 @@ 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
|
||||
|
||||
Reference in New Issue
Block a user