Introduce more opinionated backspace/del/newline behavior
+ Instead of remapping delete-backward-char to doom/delete-backward-char (which was unreliable, depending on the mode), it is now overridden with it, without sacrificing its original functionality. The new behavior is as follows: + Fall back to sp-backward-delete-char when it makes sense to delete the adjacent pair: {|} => | + Collapse an indented pair block, if at bolp in between: { | } => {|} + Refresh a pair's :post-handlers when deleting into pair: { | } => {|} => { | } (can be repeated) + When cursor is preceded by whitespace, delete in increments of tab-width. + newline-and-indent has been advised to: + Only newline when in a string. + Continue comment lines consistently (needs more testing!) + Falls back to basic newline-and-indent, without affecting whitespace in the origin line (it would originally delete-horizontal-space before creating a new line). + Incorporates a set of reasonable defaults for brace expansion on RET or SPC, as mentioned in #343 and #413. Affects #343, #413
This commit is contained in:
parent
b43743d565
commit
57adae5ec6
3 changed files with 181 additions and 113 deletions
|
@ -1,5 +1,33 @@
|
||||||
;;; core/autoload/editor.el -*- lexical-binding: t; -*-
|
;;; core/autoload/editor.el -*- lexical-binding: t; -*-
|
||||||
|
|
||||||
|
;;;###autoload
|
||||||
|
(defun doom-surrounded-p (&optional pair inline balanced)
|
||||||
|
"Returns t if point is surrounded by a brace delimiter: {[(
|
||||||
|
|
||||||
|
If INLINE is non-nil, only returns t if braces are on the same line, and
|
||||||
|
whitespace is balanced on either side of the cursor.
|
||||||
|
|
||||||
|
If INLINE is nil, returns t if the opening and closing braces are on adjacent
|
||||||
|
lines, above and below, with only whitespace in between."
|
||||||
|
(when-let* ((pair (or pair (sp-get-thing))))
|
||||||
|
(let ((op (plist-get pair :op))
|
||||||
|
(cl (plist-get pair :cl)))
|
||||||
|
(and op cl
|
||||||
|
(not (string-empty-p op))
|
||||||
|
(not (string-empty-p cl))
|
||||||
|
(let ((beg (+ (length op) (plist-get pair :beg)))
|
||||||
|
(end (- (plist-get pair :end) (length cl)))
|
||||||
|
(pt (point)))
|
||||||
|
(let ((content (buffer-substring-no-properties beg end)))
|
||||||
|
(and (string-match-p (format "[ %s]*" (if inline "" "\n")) content)
|
||||||
|
(or (not balanced)
|
||||||
|
(= (- pt beg) (- end pt))))))))))
|
||||||
|
|
||||||
|
|
||||||
|
;;
|
||||||
|
;; Commands
|
||||||
|
;;
|
||||||
|
|
||||||
;;;###autoload
|
;;;###autoload
|
||||||
(defun doom/backward-to-bol-or-indent ()
|
(defun doom/backward-to-bol-or-indent ()
|
||||||
"Jump between the indentation column (first non-whitespace character) and the
|
"Jump between the indentation column (first non-whitespace character) and the
|
||||||
|
@ -43,12 +71,6 @@ true end of the line. The opposite of `doom/backward-to-bol-or-indent'."
|
||||||
((/= bol boc)
|
((/= bol boc)
|
||||||
(goto-char boc)))))))
|
(goto-char boc)))))))
|
||||||
|
|
||||||
(defun doom--surrounded-p ()
|
|
||||||
(and (looking-back "[[{(]\\(\s+\\|\n\\)?\\(\s\\|\t\\)*" (line-beginning-position))
|
|
||||||
(let* ((whitespace (match-string 1))
|
|
||||||
(match-str (concat whitespace (match-string 2) "[])}]")))
|
|
||||||
(looking-at-p match-str))))
|
|
||||||
|
|
||||||
;;;###autoload
|
;;;###autoload
|
||||||
(defun doom/dumb-indent ()
|
(defun doom/dumb-indent ()
|
||||||
"Inserts a tab character (or spaces x tab-width)."
|
"Inserts a tab character (or spaces x tab-width)."
|
||||||
|
@ -78,7 +100,7 @@ true end of the line. The opposite of `doom/backward-to-bol-or-indent'."
|
||||||
;;;###autoload
|
;;;###autoload
|
||||||
(defun doom/backward-kill-to-bol-and-indent ()
|
(defun doom/backward-kill-to-bol-and-indent ()
|
||||||
"Kill line to the first non-blank character. If invoked again
|
"Kill line to the first non-blank character. If invoked again
|
||||||
afterwards, kill line to column 1."
|
afterwards, kill line to beginning of line."
|
||||||
(interactive)
|
(interactive)
|
||||||
(let ((empty-line-p (save-excursion (beginning-of-line)
|
(let ((empty-line-p (save-excursion (beginning-of-line)
|
||||||
(looking-at-p "[ \t]*$"))))
|
(looking-at-p "[ \t]*$"))))
|
||||||
|
@ -94,109 +116,97 @@ afterwards, kill line to column 1."
|
||||||
"Delete back to the previous column of whitespace, or as much whitespace as
|
"Delete back to the previous column of whitespace, or as much whitespace as
|
||||||
possible, or just one char if that's not possible."
|
possible, or just one char if that's not possible."
|
||||||
(interactive)
|
(interactive)
|
||||||
(let* ((delete-backward-char (if (derived-mode-p 'org-mode)
|
(let* ((context (sp-get-thing))
|
||||||
#'org-delete-backward-char
|
(op (plist-get context :op))
|
||||||
#'delete-backward-char))
|
(cl (plist-get context :cl))
|
||||||
(context (sp--get-pair-list-context 'navigate))
|
|
||||||
(open-pair-re (sp--get-opening-regexp context))
|
|
||||||
(close-pair-re (sp--get-closing-regexp context))
|
|
||||||
open-len close-len)
|
open-len close-len)
|
||||||
(cond ;; When in strings (sp acts weird with quotes; this is the fix)
|
(cond ;; When in strings (sp acts weird with quotes; this is the fix)
|
||||||
;; Also, skip closing delimiters
|
;; Also, skip closing delimiters
|
||||||
((and (and (sp--looking-back open-pair-re)
|
((and (string= op cl)
|
||||||
(setq open-len (- (match-beginning 0) (match-end 0))))
|
(and (string= (char-to-string (char-before)) op)
|
||||||
(and (looking-at close-pair-re)
|
(setq open-len (length op)))
|
||||||
(setq close-len (- (match-beginning 0) (match-end 0))))
|
(and (string= (char-to-string (char-after)) cl)
|
||||||
(string= (plist-get (sp-get-thing t) :op)
|
(setq close-len (length cl))))
|
||||||
(plist-get (sp-get-thing) :cl)))
|
(delete-char (- open-len))
|
||||||
(delete-char (- 0 open-len))
|
|
||||||
(delete-char close-len))
|
(delete-char close-len))
|
||||||
|
|
||||||
;; Delete up to the nearest tab column IF only whitespace between
|
;; Delete up to the nearest tab column IF only whitespace between
|
||||||
;; point and bol.
|
;; point and bol.
|
||||||
((save-match-data (looking-back "^[\\t ]*" (line-beginning-position)))
|
((and (not indent-tabs-mode)
|
||||||
(let ((movement (% (current-column) tab-width))
|
(not (bolp))
|
||||||
(p (point)))
|
(not (sp-point-in-string))
|
||||||
|
(save-excursion (>= (- (skip-chars-backward " \t")) tab-width)))
|
||||||
|
(let ((movement (% (current-column) tab-width)))
|
||||||
(when (= movement 0)
|
(when (= movement 0)
|
||||||
(setq movement tab-width))
|
(setq movement tab-width))
|
||||||
(save-match-data
|
(delete-char (- movement)))
|
||||||
(if (string-match "\\w*\\(\\s-+\\)$"
|
(unless (memq (char-before) (list ?\n ?\ ))
|
||||||
(buffer-substring-no-properties (max (point-min) (- p movement)) p))
|
(insert " ")))
|
||||||
(sp-delete-char
|
|
||||||
(- 0 (- (match-end 1)
|
|
||||||
(match-beginning 1))))
|
|
||||||
(call-interactively delete-backward-char)))))
|
|
||||||
|
|
||||||
;; Otherwise do a regular delete
|
;; Otherwise do a regular delete
|
||||||
(t (call-interactively delete-backward-char)))))
|
(t (delete-char -1)))))
|
||||||
|
|
||||||
;;;###autoload
|
;;;###autoload
|
||||||
(defun doom/inflate-space-maybe ()
|
(defun doom/delete-backward-char (n &optional killflag)
|
||||||
"Checks if point is surrounded by {} [] () delimiters and adds a
|
"Same as `delete-backward-char', but preforms these additional checks:
|
||||||
space on either side of the point if so."
|
|
||||||
(interactive)
|
|
||||||
(let ((command (or (command-remapping #'self-insert-command)
|
|
||||||
#'self-insert-command)))
|
|
||||||
(cond ((doom--surrounded-p)
|
|
||||||
(call-interactively command)
|
|
||||||
(save-excursion (call-interactively command)))
|
|
||||||
(t
|
|
||||||
(call-interactively command)))))
|
|
||||||
|
|
||||||
;;;###autoload
|
+ If point is surrounded by (balanced) whitespace and a brace delimiter ({} []
|
||||||
(defun doom/deflate-space-maybe ()
|
()), delete a space on either side of the cursor.
|
||||||
"Checks if point is surrounded by {} [] () delimiters, and deletes
|
+ If point is at BOL and surrounded by braces on adjacent lines, collapse
|
||||||
spaces on either side of the point if so. Resorts to
|
newlines:
|
||||||
`doom/backward-delete-whitespace-to-column' otherwise."
|
{
|
||||||
(interactive)
|
|
|
||||||
(save-match-data
|
} => {|}
|
||||||
(if (doom--surrounded-p)
|
+ Otherwise, resort to `doom/backward-delete-whitespace-to-column'.
|
||||||
(let ((whitespace-match (match-string 1)))
|
+ Resorts to `delete-char' if n > 1"
|
||||||
(cond ((not whitespace-match)
|
(interactive "p\nP")
|
||||||
(call-interactively #'delete-backward-char))
|
(unless (integerp n)
|
||||||
((string-match "\n" whitespace-match)
|
(signal 'wrong-type-argument (list 'integerp n)))
|
||||||
(funcall (if (featurep 'evil)
|
(cond ((and (use-region-p)
|
||||||
#'evil-delete
|
delete-active-region
|
||||||
#'delete-region)
|
(= n 1))
|
||||||
(point-at-bol) (point))
|
;; If a region is active, kill or delete it.
|
||||||
(call-interactively #'delete-backward-char)
|
(if (eq delete-active-region 'kill)
|
||||||
(save-excursion (call-interactively #'delete-char)))
|
(kill-region (region-beginning) (region-end) 'region)
|
||||||
(t (just-one-space 0))))
|
(funcall region-extract-function 'delete-only)))
|
||||||
(doom/backward-delete-whitespace-to-column))))
|
;; In Overwrite mode, maybe untabify while deleting
|
||||||
|
((null (or (null overwrite-mode)
|
||||||
;;;###autoload
|
(<= n 0)
|
||||||
(defun doom/newline-and-indent ()
|
(memq (char-before) '(?\t ?\n))
|
||||||
"Inserts a newline and possibly indents it. Also continues comments if
|
(eobp)
|
||||||
executed from a commented line; handling special cases for certain languages
|
(eq (char-after) ?\n)))
|
||||||
with weak native support."
|
(let ((ocol (current-column)))
|
||||||
(interactive)
|
(delete-char (- n) killflag)
|
||||||
(cond ((sp-point-in-string)
|
(save-excursion
|
||||||
(newline))
|
(insert-char ?\s (- ocol (current-column)) nil))))
|
||||||
((sp-point-in-comment)
|
;;
|
||||||
(pcase major-mode
|
((and (= n 1) (not (minibufferp)))
|
||||||
((or 'js2-mode 'rjsx-mode)
|
(let* ((pair (sp-get-thing))
|
||||||
(call-interactively #'js2-line-break))
|
(op (plist-get pair :op))
|
||||||
((or 'java-mode 'php-mode)
|
(cl (plist-get pair :cl))
|
||||||
(c-indent-new-comment-line))
|
(beg (plist-get pair :beg))
|
||||||
((or 'c-mode 'c++-mode 'objc-mode 'css-mode 'scss-mode 'js2-mode)
|
(end (plist-get pair :end)))
|
||||||
(newline-and-indent)
|
(cond ((and end beg (= end (+ beg (length op) (length cl))))
|
||||||
(insert "* ")
|
(sp-backward-delete-char 0))
|
||||||
(indent-according-to-mode))
|
((doom-surrounded-p pair :inline :balanced)
|
||||||
(_
|
(delete-char -1 killflag)
|
||||||
;; Fix an off-by-one cursor-positioning issue
|
(delete-char 1)
|
||||||
;; with `indent-new-comment-line'
|
(when (= (point) (+ (length cl) beg))
|
||||||
(let ((col (save-excursion (comment-beginning) (current-column))))
|
(sp-backward-delete-char 1)
|
||||||
(indent-new-comment-line)
|
(sp-insert-pair op)))
|
||||||
(unless (= col (current-column))
|
((and (bolp) (doom-surrounded-p pair))
|
||||||
(insert " "))))))
|
(delete-region beg end)
|
||||||
(t
|
(sp-insert-pair op))
|
||||||
(newline nil t)
|
(t
|
||||||
(indent-according-to-mode))))
|
(doom/backward-delete-whitespace-to-column)))))
|
||||||
|
;; Otherwise, do simple deletion.
|
||||||
|
(t (delete-char (- n) killflag))))
|
||||||
|
|
||||||
;;;###autoload
|
;;;###autoload
|
||||||
(defun doom/retab (&optional beg end)
|
(defun doom/retab (&optional beg end)
|
||||||
"Changes all tabs to spaces or spaces to tabs, so that indentation is
|
"Converts tabs-to-spaces or spaces-to-tabs within BEG and END (defaults to
|
||||||
consistent throughout a selected region, depending on `indent-tab-mode'."
|
buffer start and end, to make indentation consistent. Which it does depends on
|
||||||
|
the value of `indent-tab-mode'."
|
||||||
(interactive "r")
|
(interactive "r")
|
||||||
(unless (and beg end)
|
(unless (and beg end)
|
||||||
(setq beg (point-min)
|
(setq beg (point-min)
|
||||||
|
@ -228,7 +238,33 @@ Inspired from http://demonastery.org/2013/04/emacs-evil-narrow-region/"
|
||||||
(t
|
(t
|
||||||
(widen))))
|
(widen))))
|
||||||
|
|
||||||
|
|
||||||
|
;;
|
||||||
|
;; Advice
|
||||||
|
;;
|
||||||
|
|
||||||
|
;;;###autoload
|
||||||
|
(defun doom*newline-and-indent (orig-fn)
|
||||||
|
"Inserts a newline and possibly indents it. Also continues comments if
|
||||||
|
executed from a commented line; handling special cases for certain languages
|
||||||
|
with weak native support."
|
||||||
|
(interactive)
|
||||||
|
(cond ((sp-point-in-string)
|
||||||
|
(newline))
|
||||||
|
((and (sp-point-in-comment)
|
||||||
|
comment-line-break-function)
|
||||||
|
(funcall comment-line-break-function))
|
||||||
|
(t
|
||||||
|
(newline nil t)
|
||||||
|
(indent-according-to-mode))))
|
||||||
|
|
||||||
|
|
||||||
|
;;
|
||||||
|
;; Hooks
|
||||||
|
;;
|
||||||
|
|
||||||
;;;###autoload
|
;;;###autoload
|
||||||
(defun doom|enable-delete-trailing-whitespace ()
|
(defun doom|enable-delete-trailing-whitespace ()
|
||||||
"Attaches `delete-trailing-whitespace' to a buffer-local `before-save-hook'."
|
"Enables the automatic deletion of trailing whitespaces upon file save, by
|
||||||
|
attaching `delete-trailing-whitespace' to a buffer-local `before-save-hook'."
|
||||||
(add-hook 'before-save-hook #'delete-trailing-whitespace nil t))
|
(add-hook 'before-save-hook #'delete-trailing-whitespace nil t))
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
|
|
||||||
(map! [remap evil-jump-to-tag] #'projectile-find-tag
|
(map! [remap evil-jump-to-tag] #'projectile-find-tag
|
||||||
[remap find-tag] #'projectile-find-tag
|
[remap find-tag] #'projectile-find-tag
|
||||||
|
[remap newline] #'newline-and-indent
|
||||||
|
|
||||||
;; Ensure there are no conflicts
|
;; Ensure there are no conflicts
|
||||||
:nmvo doom-leader-key nil
|
:nmvo doom-leader-key nil
|
||||||
|
@ -698,27 +699,13 @@
|
||||||
:i "C-u" #'doom/backward-kill-to-bol-and-indent
|
:i "C-u" #'doom/backward-kill-to-bol-and-indent
|
||||||
|
|
||||||
;; textmate-esque newline insertion
|
;; textmate-esque newline insertion
|
||||||
:i [M-return] #'evil-open-below
|
:i [M-return] #'evil-open-below
|
||||||
:i [S-M-return] #'evil-open-above
|
:i [S-M-return] #'evil-open-above
|
||||||
;; textmate-esque deletion
|
;; textmate-esque deletion
|
||||||
[M-backspace] #'doom/backward-kill-to-bol-and-indent
|
:ig [M-backspace] #'doom/backward-kill-to-bol-and-indent
|
||||||
:i [backspace] #'delete-backward-char
|
|
||||||
:i [M-backspace] #'doom/backward-kill-to-bol-and-indent
|
|
||||||
;; Emacsien motions for insert mode
|
;; Emacsien motions for insert mode
|
||||||
:i "C-b" #'backward-word
|
:i "C-b" #'backward-word
|
||||||
:i "C-f" #'forward-word
|
:i "C-f" #'forward-word
|
||||||
|
|
||||||
;; Highjacks space/backspace to:
|
|
||||||
;; a) balance spaces inside brackets/parentheses ( | ) -> (|)
|
|
||||||
;; b) delete space-indented blocks intelligently
|
|
||||||
;; c) do none of this when inside a string
|
|
||||||
:i "SPC" #'doom/inflate-space-maybe
|
|
||||||
:i [remap delete-backward-char] #'doom/deflate-space-maybe
|
|
||||||
:i [remap newline] #'doom/newline-and-indent
|
|
||||||
|
|
||||||
(:after org
|
|
||||||
(:map org-mode-map
|
|
||||||
:i [remap doom/inflate-space-maybe] #'org-self-insert-command))
|
|
||||||
|
|
||||||
;; Restore common editing keys (and ESC) in minibuffer
|
;; Restore common editing keys (and ESC) in minibuffer
|
||||||
(:map (minibuffer-local-map
|
(:map (minibuffer-local-map
|
||||||
|
|
|
@ -23,6 +23,51 @@
|
||||||
epa-pinentry-mode 'loopback))
|
epa-pinentry-mode 'loopback))
|
||||||
|
|
||||||
|
|
||||||
|
;; disable :unless predicates with (sp-pair "'" nil :unless nil)
|
||||||
|
;; disable :post-handlers with (sp-pair "{" nil :post-handlers nil)
|
||||||
|
;; ...or specific :post-handlers with (sp-pair "{" nil :post-handlers '(:rem ("| " "SPC")))
|
||||||
|
(after! smartparens
|
||||||
|
;; Autopair quotes more conservatively; if I'm next to a word/before another
|
||||||
|
;; quote, I likely don't want another pair.
|
||||||
|
(let ((unless-list '(sp-point-before-word-p
|
||||||
|
sp-point-after-word-p
|
||||||
|
sp-point-before-same-p)))
|
||||||
|
(sp-pair "'" nil :unless unless-list)
|
||||||
|
(sp-pair "\"" nil :unless unless-list))
|
||||||
|
|
||||||
|
;; Expand {|} => { | }
|
||||||
|
;; Expand {|} => {
|
||||||
|
;; |
|
||||||
|
;; }
|
||||||
|
(dolist (brace '("(" "{" "["))
|
||||||
|
(sp-pair brace nil
|
||||||
|
:post-handlers '(("||\n[i]" "RET") ("| " "SPC"))
|
||||||
|
;; I likely don't want a new pair if adjacent to a word or opening brace
|
||||||
|
:unless '(sp-point-before-word-p sp-point-before-same-p)))
|
||||||
|
|
||||||
|
;; Don't do square-bracket space-expansion where it doesn't make sense to
|
||||||
|
(sp-local-pair '(emacs-lisp-mode org-mode markdown-mode gfm-mode)
|
||||||
|
"[" nil :post-handlers '(:rem ("| " "SPC")))
|
||||||
|
|
||||||
|
;; Highjacks backspace to:
|
||||||
|
;; a) balance spaces inside brackets/parentheses ( | ) -> (|)
|
||||||
|
;; b) delete space-indented `tab-width' steps at a time
|
||||||
|
;; c) close empty multiline brace blocks in one step:
|
||||||
|
;; {
|
||||||
|
;; |
|
||||||
|
;; }
|
||||||
|
;; becomes {|}
|
||||||
|
;; d) refresh smartparens' :post-handlers, so SPC and RET expansions work
|
||||||
|
;; even after a backspace.
|
||||||
|
;; e) properly delete smartparen pairs when they are encountered, without the
|
||||||
|
;; need for strict mode.
|
||||||
|
;; f) do none of this when inside a string
|
||||||
|
(advice-add #'delete-backward-char :override #'doom/delete-backward-char)
|
||||||
|
|
||||||
|
;; Makes `newline-and-indent' smarter when dealing with comments
|
||||||
|
(advice-add #'newline-and-indent :around #'doom*newline-and-indent))
|
||||||
|
|
||||||
|
|
||||||
(when (featurep 'evil)
|
(when (featurep 'evil)
|
||||||
(when (featurep! +evil-commands)
|
(when (featurep! +evil-commands)
|
||||||
(load! +evil-commands))
|
(load! +evil-commands))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue