diff --git a/core/autoload/editor.el b/core/autoload/editor.el index 5973198c2..c4de816f9 100644 --- a/core/autoload/editor.el +++ b/core/autoload/editor.el @@ -1,5 +1,33 @@ ;;; 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 (defun doom/backward-to-bol-or-indent () "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) (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 (defun doom/dumb-indent () "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 (defun doom/backward-kill-to-bol-and-indent () "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) (let ((empty-line-p (save-excursion (beginning-of-line) (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 possible, or just one char if that's not possible." (interactive) - (let* ((delete-backward-char (if (derived-mode-p 'org-mode) - #'org-delete-backward-char - #'delete-backward-char)) - (context (sp--get-pair-list-context 'navigate)) - (open-pair-re (sp--get-opening-regexp context)) - (close-pair-re (sp--get-closing-regexp context)) + (let* ((context (sp-get-thing)) + (op (plist-get context :op)) + (cl (plist-get context :cl)) open-len close-len) (cond ;; When in strings (sp acts weird with quotes; this is the fix) ;; Also, skip closing delimiters - ((and (and (sp--looking-back open-pair-re) - (setq open-len (- (match-beginning 0) (match-end 0)))) - (and (looking-at close-pair-re) - (setq close-len (- (match-beginning 0) (match-end 0)))) - (string= (plist-get (sp-get-thing t) :op) - (plist-get (sp-get-thing) :cl))) - (delete-char (- 0 open-len)) + ((and (string= op cl) + (and (string= (char-to-string (char-before)) op) + (setq open-len (length op))) + (and (string= (char-to-string (char-after)) cl) + (setq close-len (length cl)))) + (delete-char (- open-len)) (delete-char close-len)) ;; Delete up to the nearest tab column IF only whitespace between ;; point and bol. - ((save-match-data (looking-back "^[\\t ]*" (line-beginning-position))) - (let ((movement (% (current-column) tab-width)) - (p (point))) + ((and (not indent-tabs-mode) + (not (bolp)) + (not (sp-point-in-string)) + (save-excursion (>= (- (skip-chars-backward " \t")) tab-width))) + (let ((movement (% (current-column) tab-width))) (when (= movement 0) (setq movement tab-width)) - (save-match-data - (if (string-match "\\w*\\(\\s-+\\)$" - (buffer-substring-no-properties (max (point-min) (- p movement)) p)) - (sp-delete-char - (- 0 (- (match-end 1) - (match-beginning 1)))) - (call-interactively delete-backward-char))))) + (delete-char (- movement))) + (unless (memq (char-before) (list ?\n ?\ )) + (insert " "))) ;; Otherwise do a regular delete - (t (call-interactively delete-backward-char))))) + (t (delete-char -1))))) ;;;###autoload -(defun doom/inflate-space-maybe () - "Checks if point is surrounded by {} [] () delimiters and adds a -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))))) +(defun doom/delete-backward-char (n &optional killflag) + "Same as `delete-backward-char', but preforms these additional checks: -;;;###autoload -(defun doom/deflate-space-maybe () - "Checks if point is surrounded by {} [] () delimiters, and deletes -spaces on either side of the point if so. Resorts to -`doom/backward-delete-whitespace-to-column' otherwise." - (interactive) - (save-match-data - (if (doom--surrounded-p) - (let ((whitespace-match (match-string 1))) - (cond ((not whitespace-match) - (call-interactively #'delete-backward-char)) - ((string-match "\n" whitespace-match) - (funcall (if (featurep 'evil) - #'evil-delete - #'delete-region) - (point-at-bol) (point)) - (call-interactively #'delete-backward-char) - (save-excursion (call-interactively #'delete-char))) - (t (just-one-space 0)))) - (doom/backward-delete-whitespace-to-column)))) - -;;;###autoload -(defun doom/newline-and-indent () - "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)) - ((sp-point-in-comment) - (pcase major-mode - ((or 'js2-mode 'rjsx-mode) - (call-interactively #'js2-line-break)) - ((or 'java-mode 'php-mode) - (c-indent-new-comment-line)) - ((or 'c-mode 'c++-mode 'objc-mode 'css-mode 'scss-mode 'js2-mode) - (newline-and-indent) - (insert "* ") - (indent-according-to-mode)) - (_ - ;; Fix an off-by-one cursor-positioning issue - ;; with `indent-new-comment-line' - (let ((col (save-excursion (comment-beginning) (current-column)))) - (indent-new-comment-line) - (unless (= col (current-column)) - (insert " ")))))) - (t - (newline nil t) - (indent-according-to-mode)))) ++ If point is surrounded by (balanced) whitespace and a brace delimiter ({} [] + ()), delete a space on either side of the cursor. ++ If point is at BOL and surrounded by braces on adjacent lines, collapse + newlines: + { + | + } => {|} ++ Otherwise, resort to `doom/backward-delete-whitespace-to-column'. ++ Resorts to `delete-char' if n > 1" + (interactive "p\nP") + (unless (integerp n) + (signal 'wrong-type-argument (list 'integerp n))) + (cond ((and (use-region-p) + delete-active-region + (= n 1)) + ;; If a region is active, kill or delete it. + (if (eq delete-active-region 'kill) + (kill-region (region-beginning) (region-end) 'region) + (funcall region-extract-function 'delete-only))) + ;; In Overwrite mode, maybe untabify while deleting + ((null (or (null overwrite-mode) + (<= n 0) + (memq (char-before) '(?\t ?\n)) + (eobp) + (eq (char-after) ?\n))) + (let ((ocol (current-column))) + (delete-char (- n) killflag) + (save-excursion + (insert-char ?\s (- ocol (current-column)) nil)))) + ;; + ((and (= n 1) (not (minibufferp))) + (let* ((pair (sp-get-thing)) + (op (plist-get pair :op)) + (cl (plist-get pair :cl)) + (beg (plist-get pair :beg)) + (end (plist-get pair :end))) + (cond ((and end beg (= end (+ beg (length op) (length cl)))) + (sp-backward-delete-char 0)) + ((doom-surrounded-p pair :inline :balanced) + (delete-char -1 killflag) + (delete-char 1) + (when (= (point) (+ (length cl) beg)) + (sp-backward-delete-char 1) + (sp-insert-pair op))) + ((and (bolp) (doom-surrounded-p pair)) + (delete-region beg end) + (sp-insert-pair op)) + (t + (doom/backward-delete-whitespace-to-column))))) + ;; Otherwise, do simple deletion. + (t (delete-char (- n) killflag)))) ;;;###autoload (defun doom/retab (&optional beg end) - "Changes all tabs to spaces or spaces to tabs, so that indentation is -consistent throughout a selected region, depending on `indent-tab-mode'." + "Converts tabs-to-spaces or spaces-to-tabs within BEG and END (defaults to +buffer start and end, to make indentation consistent. Which it does depends on +the value of `indent-tab-mode'." (interactive "r") (unless (and beg end) (setq beg (point-min) @@ -228,7 +238,33 @@ Inspired from http://demonastery.org/2013/04/emacs-evil-narrow-region/" (t (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 (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)) diff --git a/modules/private/default/+bindings.el b/modules/private/default/+bindings.el index 317453f44..69e0b32fe 100644 --- a/modules/private/default/+bindings.el +++ b/modules/private/default/+bindings.el @@ -9,6 +9,7 @@ (map! [remap evil-jump-to-tag] #'projectile-find-tag [remap find-tag] #'projectile-find-tag + [remap newline] #'newline-and-indent ;; Ensure there are no conflicts :nmvo doom-leader-key nil @@ -698,27 +699,13 @@ :i "C-u" #'doom/backward-kill-to-bol-and-indent ;; textmate-esque newline insertion - :i [M-return] #'evil-open-below - :i [S-M-return] #'evil-open-above + :i [M-return] #'evil-open-below + :i [S-M-return] #'evil-open-above ;; textmate-esque deletion - [M-backspace] #'doom/backward-kill-to-bol-and-indent - :i [backspace] #'delete-backward-char - :i [M-backspace] #'doom/backward-kill-to-bol-and-indent + :ig [M-backspace] #'doom/backward-kill-to-bol-and-indent ;; Emacsien motions for insert mode - :i "C-b" #'backward-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)) + :i "C-b" #'backward-word + :i "C-f" #'forward-word ;; Restore common editing keys (and ESC) in minibuffer (:map (minibuffer-local-map diff --git a/modules/private/default/config.el b/modules/private/default/config.el index b6d59293f..ce7b25609 100644 --- a/modules/private/default/config.el +++ b/modules/private/default/config.el @@ -23,6 +23,51 @@ 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-commands) (load! +evil-commands))