├── README.md └── grammatical-edit.el /README.md: -------------------------------------------------------------------------------- 1 | # Thank you 2 | grammatical-edit is design for tree-sitter, and Emacs 29 built-in treesit, so please use [fingertip](https://github.com/manateelazycat/fingertip) instead this project, fingertip is same plugin that base on treesit. 3 | 4 | # What is grammatical-edit.el ? 5 | grammatical-edit.el is a plugin that provides grammatical edit base on tree-sitter. 6 | 7 | ## Installation 8 | 1. Install tree-sitter first: https://emacs-tree-sitter.github.io/installation/ 9 | 2. Clone or download this repository (path of the folder is the `` used below). 10 | 11 | In your `~/.emacs`, add the following two lines: 12 | ```Elisp 13 | (add-to-list 'load-path "") ; add grammatical-edit to your load-path 14 | (require 'grammatical-edit) 15 | ``` 16 | 17 | ## Enabled in the specified programming language 18 | Not all programming languages ​​are suitable for parenthesis auto-completion. 19 | You can add grammatical-edit.el to the programming language mode like below: 20 | 21 | ```Elisp 22 | (dolist (hook (list 23 | 'c-mode-common-hook 24 | 'c-mode-hook 25 | 'c++-mode-hook 26 | 'java-mode-hook 27 | 'haskell-mode-hook 28 | 'emacs-lisp-mode-hook 29 | 'lisp-interaction-mode-hook 30 | 'lisp-mode-hook 31 | 'maxima-mode-hook 32 | 'ielm-mode-hook 33 | 'sh-mode-hook 34 | 'makefile-gmake-mode-hook 35 | 'php-mode-hook 36 | 'python-mode-hook 37 | 'js-mode-hook 38 | 'go-mode-hook 39 | 'qml-mode-hook 40 | 'jade-mode-hook 41 | 'css-mode-hook 42 | 'ruby-mode-hook 43 | 'coffee-mode-hook 44 | 'rust-mode-hook 45 | 'qmake-mode-hook 46 | 'lua-mode-hook 47 | 'swift-mode-hook 48 | 'minibuffer-inactive-mode-hook 49 | 'typescript-mode-hook 50 | )) 51 | (add-hook hook '(lambda () (grammatical-edit-mode 1)))) 52 | ``` 53 | 54 | Then binding below grammatical-edit.el commands with below keystrokes: 55 | 56 | ```Elisp 57 | (define-key grammatical-edit-mode-map (kbd "(") 'grammatical-edit-open-round) 58 | (define-key grammatical-edit-mode-map (kbd "[") 'grammatical-edit-open-bracket) 59 | (define-key grammatical-edit-mode-map (kbd "{") 'grammatical-edit-open-curly) 60 | (define-key grammatical-edit-mode-map (kbd ")") 'grammatical-edit-close-round) 61 | (define-key grammatical-edit-mode-map (kbd "]") 'grammatical-edit-close-bracket) 62 | (define-key grammatical-edit-mode-map (kbd "}") 'grammatical-edit-close-curly) 63 | (define-key grammatical-edit-mode-map (kbd "=") 'grammatical-edit-equal) 64 | 65 | (define-key grammatical-edit-mode-map (kbd "%") 'grammatical-edit-match-paren) 66 | (define-key grammatical-edit-mode-map (kbd "\"") 'grammatical-edit-double-quote) 67 | (define-key grammatical-edit-mode-map (kbd "'") 'grammatical-edit-single-quote) 68 | 69 | (define-key grammatical-edit-mode-map (kbd "SPC") 'grammatical-edit-space) 70 | (define-key grammatical-edit-mode-map (kbd "RET") 'grammatical-edit-newline) 71 | 72 | (define-key grammatical-edit-mode-map (kbd "M-o") 'grammatical-edit-backward-delete) 73 | (define-key grammatical-edit-mode-map (kbd "C-d") 'grammatical-edit-forward-delete) 74 | (define-key grammatical-edit-mode-map (kbd "C-k") 'grammatical-edit-kill) 75 | 76 | (define-key grammatical-edit-mode-map (kbd "M-\"") 'grammatical-edit-wrap-double-quote) 77 | (define-key grammatical-edit-mode-map (kbd "M-'") 'grammatical-edit-wrap-single-quote) 78 | (define-key grammatical-edit-mode-map (kbd "M-[") 'grammatical-edit-wrap-bracket) 79 | (define-key grammatical-edit-mode-map (kbd "M-{") 'grammatical-edit-wrap-curly) 80 | (define-key grammatical-edit-mode-map (kbd "M-(") 'grammatical-edit-wrap-round) 81 | (define-key grammatical-edit-mode-map (kbd "M-)") 'grammatical-edit-unwrap) 82 | 83 | (define-key grammatical-edit-mode-map (kbd "M-p") 'grammatical-edit-jump-right) 84 | (define-key grammatical-edit-mode-map (kbd "M-n") 'grammatical-edit-jump-left) 85 | (define-key grammatical-edit-mode-map (kbd "M-:") 'grammatical-edit-jump-out-pair-and-newline) 86 | 87 | (define-key grammatical-edit-mode-map (kbd "C-j") 'grammatical-edit-jump-up) 88 | ``` 89 | 90 | ### Make tree-sitter to support elisp 91 | 92 | ``` 93 | 1. git clone https://github.com/Wilfred/tree-sitter-elisp 94 | 2. gcc ./src/parser.c -fPIC -I./ --shared -o elisp.so 95 | 3. cp ./elisp.so ~/.tree-sitter/langs/bin (~/.tree-sitter/langs/bin is path of your tree-sitter repo) 96 | (tree-sitter-load 'elisp "elisp") 97 | (add-to-list 'tree-sitter-major-mode-language-alist '(emacs-lisp-mode . elisp)) 98 | ``` 99 | ### Make tree-sitter to support vue 100 | 101 | ``` 102 | 1. git clone https://github.com/ikatyang/tree-sitter-vue.git 103 | 2. gcc ./src/parser.c ./src/scanner.cc -fPIC -I./ --shared -o vue.so 104 | 3. cp ./vue.so ~/.tree-sitter/langs/bin (~/.tree-sitter/langs/bin is path of your tree-sitter repo) 105 | (tree-sitter-load 'vue "vue") 106 | (add-to-list 'tree-sitter-major-mode-language-alist '(web-mode . vue)) 107 | ``` 108 | 109 | ### Make tree-sitter to support Typescript 110 | 111 | ``` 112 | 1. git clone https://github.com/tree-sitter/tree-sitter-typescript.git 113 | 2. gcc ./tsx/src/parser.c ./tsx/src/scanner.c -fPIC -I./ --shared -o typescript.so 114 | 3. cp ./typescript.so ~/.tree-sitter/langs/bin (~/.tree-sitter/langs/bin is path of your tree-sitter repo) 115 | (tree-sitter-load 'typescript "typescript") 116 | (add-to-list 'tree-sitter-major-mode-language-alist '(typescript-mode . typescript)) 117 | ``` 118 | 119 | More config about tree-sitter can refer to [my profile](https://github.com/manateelazycat/lazycat-emacs/blob/master/site-lisp/config/init-tree-sitter.el) 120 | -------------------------------------------------------------------------------- /grammatical-edit.el: -------------------------------------------------------------------------------- 1 | ;;; grammatical-edit.el --- Grammatical edit base on tree-sitter 2 | 3 | ;; Filename: grammatical-edit.el 4 | ;; Description: Grammatical edit base on tree-sitter 5 | ;; Author: Andy Stewart 6 | ;; Maintainer: Andy Stewart 7 | ;; Copyright (C) 2021, Andy Stewart, all rights reserved. 8 | ;; Created: 2021-11-25 21:24:03 9 | ;; Version: 0.1 10 | ;; Last-Updated: 2021-11-25 21:24:03 11 | ;; By: Andy Stewart 12 | ;; URL: https://www.github.org/manateelazycat/grammatical-edit 13 | ;; Keywords: 14 | ;; Compatibility: GNU Emacs 29.0.50 15 | ;; 16 | ;; Features that might be required by this library: 17 | ;; 18 | ;; 19 | ;; 20 | 21 | ;;; This file is NOT part of GNU Emacs 22 | 23 | ;;; License 24 | ;; 25 | ;; This program is free software; you can redistribute it and/or modify 26 | ;; it under the terms of the GNU General Public License as published by 27 | ;; the Free Software Foundation; either version 3, or (at your option) 28 | ;; any later version. 29 | 30 | ;; This program is distributed in the hope that it will be useful, 31 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 32 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 33 | ;; GNU General Public License for more details. 34 | 35 | ;; You should have received a copy of the GNU General Public License 36 | ;; along with this program; see the file COPYING. If not, write to 37 | ;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth 38 | ;; Floor, Boston, MA 02110-1301, USA. 39 | 40 | ;;; Commentary: 41 | ;; 42 | ;; Grammatical edit base on tree-sitter 43 | ;; 44 | 45 | ;;; Installation: 46 | ;; 47 | ;; Put grammatical-edit.el to your load-path. 48 | ;; The load-path is usually ~/elisp/. 49 | ;; It's set in your ~/.emacs like this: 50 | ;; (add-to-list 'load-path (expand-file-name "~/elisp")) 51 | ;; 52 | ;; And the following to your ~/.emacs startup file. 53 | ;; 54 | ;; (require 'grammatical-edit) 55 | ;; 56 | ;; No need more. 57 | 58 | ;;; Customize: 59 | ;; 60 | ;; 61 | ;; 62 | ;; All of the above can customize by: 63 | ;; M-x customize-group RET grammatical-edit RET 64 | ;; 65 | 66 | ;;; Change log: 67 | ;; 68 | ;; 2021/11/25 69 | ;; * First released. 70 | ;; 71 | 72 | ;;; Acknowledgements: 73 | ;; 74 | ;; 75 | ;; 76 | 77 | ;;; TODO 78 | ;; 79 | ;; 80 | ;; 81 | 82 | ;;; Require 83 | (require 'subr-x) 84 | (require 'thingatpt) 85 | (require 'tree-sitter) 86 | 87 | ;;; Code: 88 | 89 | (defgroup grammatical-edit nil 90 | "Edit grammatically." 91 | :group 'grammatical-edit) 92 | 93 | (defvar grammatical-edit-mode-map (make-sparse-keymap) 94 | "Keymap for the grammatical-edit minor mode.") 95 | 96 | ;;;###autoload 97 | (define-minor-mode grammatical-edit-mode 98 | "Minor mode for auto parenthesis pairing with syntax table. 99 | \\" 100 | :group 'grammatical-edit) 101 | 102 | (defmacro grammatical-edit-ignore-errors (body) 103 | `(ignore-errors 104 | ,body 105 | t)) 106 | 107 | (defcustom grammatical-edit-save-in-kill-ring t 108 | "Whether save kill thing into kill-ring." 109 | :type 'boolean 110 | :group 'grammatical-edit) 111 | 112 | ;;;;;;;;;;;;;;;;; Interactive functions ;;;;;;;;;;;;;;;;;;;;;; 113 | 114 | (defun grammatical-edit-delete-region (beg end) 115 | (if grammatical-edit-save-in-kill-ring 116 | (kill-region beg end) 117 | (delete-region beg end))) 118 | 119 | (defun grammatical-edit-open-object (object-start object-end) 120 | (interactive) 121 | (cond 122 | ((region-active-p) 123 | (grammatical-edit-wrap-round)) 124 | ((and (grammatical-edit-in-string-p) 125 | (derived-mode-p 'js-mode)) 126 | (insert (format "%s%s" object-start object-end)) 127 | (backward-char)) 128 | ((or (grammatical-edit-in-string-p) 129 | (grammatical-edit-in-comment-p)) 130 | (insert object-start)) 131 | (t 132 | (insert (format "%s%s" object-start object-end)) 133 | (backward-char)))) 134 | 135 | (defun grammatical-edit-open-round () 136 | (interactive) 137 | (grammatical-edit-open-object "(" ")")) 138 | 139 | (defun grammatical-edit-open-curly () 140 | (interactive) 141 | (grammatical-edit-open-object "{" "}")) 142 | 143 | (defun grammatical-edit-open-bracket () 144 | (interactive) 145 | (grammatical-edit-open-object "[" "]")) 146 | 147 | (defun grammatical-edit-fix-unbalanced-parentheses () 148 | (interactive) 149 | (let ((close (grammatical-edit-missing-close))) 150 | (if close 151 | (cond ((eq ?\) (matching-paren close)) 152 | (insert ")")) 153 | ((eq ?\} (matching-paren close)) 154 | (insert "}")) 155 | ((eq ?\] (matching-paren close)) 156 | (insert "]"))) 157 | (up-list)))) 158 | 159 | (defun grammatical-edit-close-round () 160 | (interactive) 161 | (cond ((or (grammatical-edit-in-string-p) 162 | (grammatical-edit-in-comment-p)) 163 | (insert ")")) 164 | ;; Insert ) directly in sh-mode for case ... in syntax. 165 | ((or 166 | (derived-mode-p 'sh-mode) 167 | (derived-mode-p 'markdown-mode)) 168 | (insert ")")) 169 | (t 170 | (grammatical-edit-fix-unbalanced-parentheses)))) 171 | 172 | (defun grammatical-edit-close-curly () 173 | (interactive) 174 | (cond ((or (grammatical-edit-in-string-p) 175 | (grammatical-edit-in-comment-p)) 176 | (insert "}")) 177 | (t 178 | (grammatical-edit-fix-unbalanced-parentheses)))) 179 | 180 | (defun grammatical-edit-close-bracket () 181 | (interactive) 182 | (cond ((or (grammatical-edit-in-string-p) 183 | (grammatical-edit-in-comment-p)) 184 | (insert "]")) 185 | (t 186 | (grammatical-edit-fix-unbalanced-parentheses)))) 187 | 188 | (defun grammatical-edit-single-quote () 189 | (interactive) 190 | (cond ((or (grammatical-edit-is-lisp-mode-p) 191 | (derived-mode-p 'markdown-mode) 192 | (derived-mode-p 'rust-mode)) 193 | (insert "'")) 194 | ((region-active-p) 195 | (grammatical-edit-wrap-single-quote)) 196 | ((grammatical-edit-in-string-p) 197 | (cond 198 | ((and (derived-mode-p 'python-mode) 199 | (and (eq (char-before) ?\') (eq (char-after) ?\'))) 200 | (insert "''") 201 | (backward-char)) 202 | (t 203 | (insert "'")))) 204 | ((grammatical-edit-in-comment-p) 205 | (insert "'")) 206 | (t 207 | (insert "''") 208 | (backward-char)))) 209 | 210 | (defun grammatical-edit-double-quote () 211 | (interactive) 212 | (cond ((region-active-p) 213 | (grammatical-edit-wrap-double-quote)) 214 | ((grammatical-edit-in-single-quote-string-p) 215 | (insert "\"")) 216 | ((grammatical-edit-in-string-p) 217 | (cond 218 | ((and (derived-mode-p 'python-mode) 219 | (and (eq (char-before) ?\") (eq (char-after) ?\"))) 220 | (insert "\"\"") 221 | (backward-char)) 222 | ;; When current mode is golang. 223 | ;; Don't insert \" in string that wrap by `...` 224 | ((and (derived-mode-p 'go-mode) 225 | (equal (save-excursion (nth 3 (grammatical-edit-current-parse-state))) 96)) 226 | (insert "\"")) 227 | (t 228 | (insert "\\\"")))) 229 | ((grammatical-edit-in-comment-p) 230 | (insert "\"")) 231 | (t 232 | (insert "\"\"") 233 | (backward-char)))) 234 | 235 | (defun grammatical-edit-space (arg) 236 | "Wrap space around cursor if cursor in blank parenthesis. 237 | 238 | input: {|} (press at |) 239 | output: { | } 240 | 241 | input: [|] (press at |) 242 | output: [ | ] 243 | " 244 | (interactive "p") 245 | (if (> arg 1) 246 | (self-insert-command arg) 247 | (cond ((or (grammatical-edit-in-comment-p) 248 | (grammatical-edit-in-string-p)) 249 | (insert " ")) 250 | ((or (and (equal (char-after) ?\} ) 251 | (equal (char-before) ?\{ )) 252 | (and (equal (char-after) ?\] ) 253 | (equal (char-before) ?\[ ))) 254 | (insert " ") 255 | (backward-char 1)) 256 | (t 257 | ;; Add `ignore-errors' avoid failed on ielm. 258 | (ignore-errors (insert " ")))))) 259 | 260 | (defun grammatical-edit-web-mode-match-paren () 261 | (require 'sgml-mode) 262 | (cond ((looking-at "<") 263 | (sgml-skip-tag-forward 1)) 264 | ((looking-back ">" nil) 265 | (sgml-skip-tag-backward 1)) 266 | (t (self-insert-command (or arg 1))))) 267 | 268 | (defun grammatical-edit-backward-delete () 269 | (interactive) 270 | (cond ((grammatical-edit-in-string-p) 271 | (grammatical-edit-backward-delete-in-string)) 272 | ((grammatical-edit-in-comment-p) 273 | (backward-delete-char 1)) 274 | ((grammatical-edit-after-close-pair-p) 275 | (if (and (derived-mode-p 'sh-mode) 276 | (eq ?\) (char-before))) 277 | (delete-char -1) 278 | (backward-char))) 279 | ((grammatical-edit-in-empty-pair-p) 280 | (grammatical-edit-backward-delete-in-pair)) 281 | ((not (grammatical-edit-after-open-pair-p)) 282 | (backward-delete-char 1)))) 283 | 284 | (defun grammatical-edit-forward-delete () 285 | (interactive) 286 | (cond ((region-active-p) 287 | (grammatical-edit-delete-region (region-beginning) (region-end))) 288 | ((grammatical-edit-in-empty-backquote-string-p) 289 | (grammatical-edit-delete-empty-backquote-string)) 290 | ((grammatical-edit-in-empty-string-p) 291 | (grammatical-edit-delete-empty-string)) 292 | ((grammatical-edit-in-string-p) 293 | (grammatical-edit-forward-delete-in-string)) 294 | ((grammatical-edit-in-comment-p) 295 | (delete-char 1)) 296 | ((grammatical-edit-before-string-open-quote-p) 297 | (grammatical-edit-forward-movein-string)) 298 | ((grammatical-edit-before-open-pair-p) 299 | (forward-char)) 300 | ((grammatical-edit-in-empty-pair-p) 301 | (grammatical-edit-backward-delete-in-pair)) 302 | ((and (derived-mode-p 'sh-mode) 303 | (grammatical-edit-before-close-pair-p) 304 | (eq ?\) (char-after))) 305 | (delete-char 1)) 306 | ((not (grammatical-edit-before-close-pair-p)) 307 | (delete-char 1)))) 308 | 309 | (defun grammatical-edit-kill () 310 | "Intelligent soft kill. 311 | 312 | When inside of code, kill forward S-expressions on the line, but 313 | respecting delimeters. 314 | When in a string, kill to the end of the string. 315 | When in comment, kill to the end of the line." 316 | (interactive) 317 | (cond ((region-active-p) 318 | (grammatical-edit-delete-region (region-beginning) (region-end))) 319 | ((derived-mode-p 'web-mode) 320 | (grammatical-edit-web-mode-kill)) 321 | (t 322 | (grammatical-edit-common-mode-kill)))) 323 | 324 | (defun grammatical-edit-backward-kill () 325 | "Intelligent soft kill. 326 | 327 | When inside of code, kill backward S-expressions on the line, but 328 | respecting delimiters. 329 | When in a string, kill to the beginning of the string. 330 | When in comment, kill to the beginning of the line." 331 | (interactive) 332 | (cond ((derived-mode-p 'web-mode) 333 | (grammatical-edit-web-mode-backward-kill)) 334 | (t 335 | (grammatical-edit-common-mode-backward-kill)))) 336 | 337 | (defun grammatical-edit-wrap-double-quote () 338 | (interactive) 339 | (cond ((and (region-active-p) 340 | (grammatical-edit-in-string-p)) 341 | (cond ((and (derived-mode-p 'go-mode) 342 | (equal (save-excursion (nth 3 (grammatical-edit-current-parse-state))) 96)) 343 | (grammatical-edit-wrap-region "\"" "\"")) 344 | (t 345 | (grammatical-edit-wrap-region "\\\"" "\\\"")))) 346 | ((region-active-p) 347 | (grammatical-edit-wrap-region "\"" "\"")) 348 | ((grammatical-edit-in-string-p) 349 | (goto-char (cdr (grammatical-edit-current-node-range)))) 350 | ((grammatical-edit-in-comment-p) 351 | (grammatical-edit-wrap (beginning-of-thing 'symbol) (end-of-thing 'symbol) "\"" "\"")) 352 | ((grammatical-edit-is-lisp-mode-p) 353 | (grammatical-edit-wrap (beginning-of-thing 'sexp) (end-of-thing 'sexp) "\"" "\"")) 354 | (t 355 | (grammatical-edit-wrap (beginning-of-thing 'symbol) (end-of-thing 'symbol) "\"" "\"")))) 356 | 357 | (defun grammatical-edit-wrap-single-quote () 358 | (interactive) 359 | (cond ((region-active-p) 360 | (grammatical-edit-wrap-region "'" "'")) 361 | ((grammatical-edit-in-comment-p) 362 | (grammatical-edit-wrap (beginning-of-thing 'symbol) (end-of-thing 'symbol) "'" "'")) 363 | ((grammatical-edit-is-lisp-mode-p) 364 | (grammatical-edit-wrap (beginning-of-thing 'sexp) (end-of-thing 'sexp) "'" "'")) 365 | (t 366 | (grammatical-edit-wrap (beginning-of-thing 'symbol) (end-of-thing 'symbol) "'" "'")))) 367 | 368 | (defun grammatical-edit-wrap-round () 369 | (interactive) 370 | (cond 371 | ;; If in *.Vue file 372 | ;; In template area, call `grammatical-edit-web-mode-element-wrap' 373 | ;; Otherwise, call `grammatical-edit-wrap-round-pair' 374 | ((and (buffer-file-name) (string-equal (file-name-extension (buffer-file-name)) "vue")) 375 | (if (grammatical-edit-vue-in-template-area-p) 376 | (grammatical-edit-web-mode-element-wrap) 377 | (grammatical-edit-wrap-round-pair))) 378 | ;; If is `web-mode' but not in *.Vue file, call `grammatical-edit-web-mode-element-wrap' 379 | ((derived-mode-p 'web-mode) 380 | (if (grammatical-edit-in-script-area-p) 381 | (grammatical-edit-wrap-round-pair) 382 | (grammatical-edit-web-mode-element-wrap))) 383 | ;; Otherwise call `grammatical-edit-wrap-round-pair' 384 | (t 385 | (grammatical-edit-wrap-round-pair)))) 386 | 387 | (defun grammatical-edit-wrap-round-object (object-start object-end) 388 | (let* ((not-between-in-round (and (grammatical-edit-is-lisp-mode-p) 389 | (equal (char-before) ?\() 390 | (not (equal (char-after) ?\())))) 391 | (cond ((region-active-p) 392 | (grammatical-edit-wrap-region object-start object-end)) 393 | ((grammatical-edit-in-comment-p) 394 | (grammatical-edit-wrap (beginning-of-thing 'symbol) (end-of-thing 'symbol) object-start object-end)) 395 | ((grammatical-edit-is-lisp-mode-p) 396 | (grammatical-edit-wrap (beginning-of-thing 'sexp) (end-of-thing 'sexp) object-start object-end)) 397 | ((member (grammatical-edit-node-type-at-point) (list "{" "(" "[")) 398 | (let ((match-paren-pos (save-excursion 399 | (grammatical-edit-match-paren 1) 400 | (point)))) 401 | (grammatical-edit-wrap (point) match-paren-pos object-start object-end))) 402 | (t 403 | (when (grammatical-edit-before-string-open-quote-p) 404 | (grammatical-edit-forward-movein-string)) 405 | (let ((string-bound (grammatical-edit-current-node-range))) 406 | (grammatical-edit-wrap (car string-bound) (cdr string-bound) object-start object-end)))) 407 | 408 | (unless (or (grammatical-edit-in-string-p) 409 | (grammatical-edit-in-comment-p)) 410 | ;; Indent wrap area. 411 | (grammatical-edit-indent-parent-area) 412 | 413 | ;; Backward char if cursor in nested roud, such as `( ... )|)` 414 | (when (grammatical-edit-nested-round-p) 415 | (backward-char 1)) 416 | ;; Jump to start position of parent node. 417 | (unless (grammatical-edit-is-lisp-mode-p) 418 | (goto-char (tsc-node-start-position (tsc-get-parent (tree-sitter-node-at-pos)))))) 419 | 420 | ;; Forward char if cursor not between in nested round. 421 | (when not-between-in-round 422 | (forward-char 1)))) 423 | 424 | (defun grammatical-edit-is-lisp-mode-p () 425 | (or (derived-mode-p 'lisp-mode) 426 | (derived-mode-p 'emacs-lisp-mode) 427 | (derived-mode-p 'inferior-emacs-lisp-mode))) 428 | 429 | (defun grammatical-edit-nested-round-p () 430 | (save-excursion 431 | (backward-char 1) 432 | (let ((node-type (grammatical-edit-node-type-at-point))) 433 | (or (string-equal node-type ")") 434 | (string-equal node-type "]") 435 | (string-equal node-type "}"))))) 436 | 437 | (defun grammatical-edit-wrap-round-pair () 438 | (interactive) 439 | (grammatical-edit-wrap-round-object "(" ")")) 440 | 441 | (defun grammatical-edit-wrap-bracket () 442 | (interactive) 443 | (grammatical-edit-wrap-round-object "[" "]")) 444 | 445 | (defun grammatical-edit-wrap-curly () 446 | (interactive) 447 | (grammatical-edit-wrap-round-object "{" "}")) 448 | 449 | (defun grammatical-edit-unwrap (&optional argument) 450 | (interactive "P") 451 | (cond ((derived-mode-p 'web-mode) 452 | (grammatical-edit-web-mode-element-unwrap)) 453 | ((grammatical-edit-in-string-p) 454 | (grammatical-edit-unwrap-string argument)) 455 | (t 456 | (save-excursion 457 | (grammatical-edit-kill-surrounding-sexps-for-splice argument) 458 | (backward-up-list) 459 | (save-excursion 460 | (forward-sexp) 461 | (backward-delete-char 1)) 462 | (delete-char 1) 463 | ;; Try to indent parent expression after unwrap pair. 464 | ;; This feature just enable in lisp-like language. 465 | (when (grammatical-edit-is-lisp-mode-p) 466 | (ignore-errors 467 | (backward-up-list) 468 | (indent-sexp))))))) 469 | 470 | (defun grammatical-edit-jump-out-pair-and-newline () 471 | (interactive) 472 | (cond ((grammatical-edit-in-string-p) 473 | (goto-char (cdr (grammatical-edit-current-node-range))) 474 | (newline-and-indent)) 475 | (t 476 | ;; Just do when have `up-list' in next step. 477 | (if (grammatical-edit-ignore-errors (save-excursion (up-list))) 478 | (let (up-list-point) 479 | (if (grammatical-edit-is-blank-line-p) 480 | ;; Clean current line first if current line is blank line. 481 | (grammatical-edit-kill-current-line) 482 | ;; Move out of current parentheses and newline. 483 | (up-list) 484 | (setq up-list-point (point)) 485 | (newline-and-indent) 486 | ;; Try to clean unnecessary whitespace before close parenthesis. 487 | ;; This feature just enable in lisp-like language. 488 | (when (grammatical-edit-is-lisp-mode-p) 489 | (save-excursion 490 | (goto-char up-list-point) 491 | (backward-char) 492 | (when (grammatical-edit-only-whitespaces-before-cursor-p) 493 | (grammatical-edit-delete-whitespace-around-cursor)))))) 494 | ;; Try to clean blank line if no pair can jump out. 495 | (if (grammatical-edit-is-blank-line-p) 496 | (grammatical-edit-kill-current-line)))))) 497 | 498 | (defun grammatical-edit-jump-left () 499 | (interactive) 500 | (let* ((current-node (tree-sitter-node-at-pos)) 501 | (prev-node (tsc-get-prev-sibling current-node)) 502 | (current-node-text (tsc-node-text current-node)) 503 | (current-point (point))) 504 | (cond 505 | ;; Skip blank space. 506 | ((looking-back "\\s-+" nil) 507 | (search-backward-regexp "[^ \t\n]" nil t)) 508 | 509 | ;; Jump to previous non-blank char if at line beginng. 510 | ((bolp) 511 | (forward-line -1) 512 | (end-of-line) 513 | (search-backward-regexp "[^ \t\n]" nil t)) 514 | 515 | ;; Jump to previous open char. 516 | ((and (eq major-mode 'web-mode) 517 | (eq (tsc-node-type current-node) 'raw_text)) 518 | (backward-char 1) 519 | (while (not (looking-at "\\(['\"<({]\\|[[]\\)")) (backward-char 1))) 520 | 521 | ;; Jump out string if in string. 522 | ((grammatical-edit-in-string-p) 523 | (goto-char (tsc-node-start-position current-node))) 524 | 525 | ;; Jump to node start position if current node exist. 526 | ((> (length current-node-text) 0) 527 | (goto-char (tsc-node-start-position current-node)) 528 | (if (equal (point) current-point) 529 | (backward-char 1))) 530 | 531 | ;; Otherwise, jump to start position of previous node. 532 | (prev-node 533 | (goto-char (tsc-node-start-position prev-node)))))) 534 | 535 | (defun grammatical-edit-jump-right () 536 | (interactive) 537 | (let* ((current-node (tree-sitter-node-at-pos)) 538 | (next-node (tsc-get-next-sibling current-node)) 539 | (current-node-text (tsc-node-text current-node)) 540 | (current-point (point))) 541 | (cond 542 | ;; Skip blank space. 543 | ((looking-at "\\s-+") 544 | (search-forward-regexp "\\s-+" nil t)) 545 | 546 | ;; Jump to next non-blank char if at line end. 547 | ((eolp) 548 | (forward-line 1) 549 | (beginning-of-line) 550 | (search-forward-regexp "\\s-+" nil t)) 551 | 552 | ;; Jump into string if at before string open quote char. 553 | ((eq (char-after) ?\") 554 | (forward-char)) 555 | 556 | ;; Jump to next close char. 557 | ((and (eq major-mode 'web-mode) 558 | (eq (tsc-node-type current-node) 'raw_text)) 559 | (while (not (looking-at "\\(['\">)}]\\|]\\)")) (forward-char 1)) 560 | (forward-char 1)) 561 | 562 | ;; Jump out string if in string. 563 | ((grammatical-edit-in-string-p) 564 | (goto-char (tsc-node-end-position current-node))) 565 | 566 | ;; Jump to node end position if current node exist. 567 | ((> (length current-node-text) 0) 568 | (goto-char (tsc-node-end-position current-node)) 569 | (if (equal (point) current-point) 570 | (forward-char 1))) 571 | 572 | ;; Otherwise, jump to end position of next node. 573 | (next-node 574 | (goto-char (tsc-node-end-position next-node)))))) 575 | 576 | (defun grammatical-edit-delete-whitespace-around-cursor () 577 | (grammatical-edit-delete-region (save-excursion 578 | (search-backward-regexp "[^ \t\n]" nil t) 579 | (forward-char) 580 | (point)) 581 | (save-excursion 582 | (search-forward-regexp "[^ \t\n]" nil t) 583 | (backward-char) 584 | (point)))) 585 | 586 | (defun grammatical-edit-kill-current-line () 587 | (grammatical-edit-delete-region (beginning-of-thing 'line) (end-of-thing 'line)) 588 | (back-to-indentation)) 589 | 590 | (defun grammatical-edit-missing-close () 591 | (let ((start-point (point)) 592 | open) 593 | (save-excursion 594 | ;; Get open tag. 595 | (backward-up-list) 596 | (setq open (char-after)) 597 | 598 | ;; Jump to start position and use `check-parens' check unbalance paren. 599 | (goto-char start-point) 600 | (ignore-errors 601 | (check-parens)) 602 | 603 | ;; Return missing tag if point change after `check-parens' 604 | ;; Otherwhere return nil. 605 | (if (equal start-point (point)) 606 | nil 607 | open)))) 608 | 609 | (defun grammatical-edit-backward-delete-in-pair () 610 | (backward-delete-char 1) 611 | (delete-char 1)) 612 | 613 | (defun grammatical-edit-forward-movein-string () 614 | (cond ((and (eq (grammatical-edit-node-type-at-point) 'raw_string_literal) 615 | (eq (char-after) ?`)) 616 | (forward-char 1)) 617 | (t 618 | (forward-char (length (tsc-node-text (tree-sitter-node-at-pos))))))) 619 | 620 | (defun grammatical-edit-is-string-node-p (current-node) 621 | (or (eq (tsc-node-type current-node) 'string) 622 | (eq (tsc-node-type current-node) 'string_literal) 623 | (eq (tsc-node-type current-node) 'interpreted_string_literal) 624 | (eq (tsc-node-type current-node) 'raw_string_literal) 625 | (string-equal (tsc-node-type current-node) "\""))) 626 | 627 | (defun grammatical-edit-in-empty-backquote-string-p () 628 | (let ((current-node (tree-sitter-node-at-pos))) 629 | (and (grammatical-edit-is-string-node-p current-node) 630 | (string-equal (tsc-node-text current-node) "``") 631 | (eq (char-before) ?`) 632 | (eq (char-after) ?`) 633 | ))) 634 | 635 | (defun grammatical-edit-get-parent-bound-info () 636 | (let* ((current-node (tree-sitter-node-at-pos)) 637 | (parent-node (tsc-get-parent current-node)) 638 | (parent-bound-start (tsc-node-text (save-excursion 639 | (goto-char (tsc-node-start-position parent-node)) 640 | (tree-sitter-node-at-pos)))) 641 | (parent-bound-end (tsc-node-text (save-excursion 642 | (goto-char (tsc-node-end-position parent-node)) 643 | (backward-char 1) 644 | (tree-sitter-node-at-pos))))) 645 | (list current-node parent-node parent-bound-start parent-bound-end))) 646 | 647 | (defun grammatical-edit-in-empty-string-p () 648 | (or (let* ((parent-bound-info (grammatical-edit-get-parent-bound-info)) 649 | (current-node (nth 0 parent-bound-info)) 650 | (parent-node (nth 1 parent-bound-info)) 651 | (string-bound-start (nth 2 parent-bound-info)) 652 | (string-bound-end (nth 3 parent-bound-info))) 653 | (and (grammatical-edit-is-string-node-p current-node) 654 | (= (length (tsc-node-text parent-node)) (+ (length string-bound-start) (length string-bound-end))) 655 | )) 656 | (string-equal (tsc-node-text (tree-sitter-node-at-pos)) "\"\""))) 657 | 658 | (defun grammatical-edit-backward-delete-in-string () 659 | (cond 660 | ;; Delete empty string if cursor in empty string. 661 | ((grammatical-edit-in-empty-backquote-string-p) 662 | (grammatical-edit-delete-empty-backquote-string)) 663 | ((grammatical-edit-in-empty-string-p) 664 | (grammatical-edit-delete-empty-string)) 665 | ;; Jump left to out of string quote if cursor after open quote. 666 | ((grammatical-edit-after-open-quote-p) 667 | (backward-char (length (save-excursion 668 | (backward-char 1) 669 | (tsc-node-text (tree-sitter-node-at-pos)))))) 670 | ;; Delete previous character. 671 | (t 672 | (backward-delete-char 1)))) 673 | 674 | (defun grammatical-edit-delete-empty-string () 675 | (cond ((string-equal (tsc-node-text (tree-sitter-node-at-pos)) "\"\"") 676 | (grammatical-edit-delete-region (- (point) 1) (+ (point) 1))) 677 | (t 678 | (let* ((current-node (tree-sitter-node-at-pos)) 679 | (node-bound-length (save-excursion 680 | (goto-char (tsc-node-start-position current-node)) 681 | (length (tsc-node-text (tree-sitter-node-at-pos)))))) 682 | (grammatical-edit-delete-region (- (point) node-bound-length) (+ (point) node-bound-length)))))) 683 | 684 | (defun grammatical-edit-delete-empty-backquote-string () 685 | (grammatical-edit-delete-region (save-excursion 686 | (backward-char 1) 687 | (point)) 688 | (save-excursion 689 | (forward-char 1) 690 | (point)))) 691 | 692 | (defun grammatical-edit-forward-delete-in-string () 693 | (let* ((current-node (tree-sitter-node-at-pos)) 694 | (node-bound-length (save-excursion 695 | (goto-char (tsc-node-start-position current-node)) 696 | (length (tsc-node-text (tree-sitter-node-at-pos)))))) 697 | (unless (eq (point) (- (tsc-node-end-position current-node) node-bound-length)) 698 | (delete-char 1)))) 699 | 700 | (defun grammatical-edit-unwrap-string (argument) 701 | (let ((original-point (point)) 702 | (start+end (grammatical-edit-current-node-range))) 703 | (let ((start (car start+end)) 704 | (end (1- (cdr start+end)))) 705 | (let* ((escaped-string 706 | (cond ((not (consp argument)) 707 | (buffer-substring (1+ start) end)) 708 | ((= 4 (car argument)) 709 | (buffer-substring original-point end)) 710 | (t 711 | (buffer-substring (1+ start) original-point)))) 712 | (unescaped-string 713 | (grammatical-edit-unescape-string escaped-string))) 714 | (if (not unescaped-string) 715 | (error "Unspliceable string.") 716 | (save-excursion 717 | (goto-char start) 718 | (grammatical-edit-delete-region start (1+ end)) 719 | (insert unescaped-string)) 720 | (if (not (and (consp argument) 721 | (= 4 (car argument)))) 722 | (goto-char (- original-point 1)))))))) 723 | 724 | (defun grammatical-edit-point-at-sexp-start () 725 | (save-excursion 726 | (forward-sexp) 727 | (backward-sexp) 728 | (point))) 729 | 730 | (defun grammatical-edit-point-at-sexp-end () 731 | (save-excursion 732 | (backward-sexp) 733 | (forward-sexp) 734 | (point))) 735 | 736 | (defun grammatical-edit-point-at-sexp-boundary (n) 737 | (cond ((< n 0) (grammatical-edit-point-at-sexp-start)) 738 | ((= n 0) (point)) 739 | ((> n 0) (grammatical-edit-point-at-sexp-end)))) 740 | 741 | (defun grammatical-edit-kill-surrounding-sexps-for-splice (argument) 742 | (cond ((or (grammatical-edit-in-string-p) 743 | (grammatical-edit-in-comment-p)) 744 | (error "Invalid context for splicing S-expressions.")) 745 | ((or (not argument) (eq argument 0)) nil) 746 | ((or (numberp argument) (eq argument '-)) 747 | (let* ((argument (if (eq argument '-) -1 argument)) 748 | (saved (grammatical-edit-point-at-sexp-boundary (- argument)))) 749 | (goto-char saved) 750 | (ignore-errors (backward-sexp argument)) 751 | (grammatical-edit-hack-kill-region saved (point)))) 752 | ((consp argument) 753 | (let ((v (car argument))) 754 | (if (= v 4) 755 | (let ((end (point))) 756 | (ignore-errors 757 | (while (not (bobp)) 758 | (backward-sexp))) 759 | (grammatical-edit-hack-kill-region (point) end)) 760 | (let ((beginning (point))) 761 | (ignore-errors 762 | (while (not (eobp)) 763 | (forward-sexp))) 764 | (grammatical-edit-hack-kill-region beginning (point)))))) 765 | (t (error "Bizarre prefix argument `%s'." argument)))) 766 | 767 | (defun grammatical-edit-unescape-string (string) 768 | (with-temp-buffer 769 | (insert string) 770 | (goto-char (point-min)) 771 | (while (and (not (eobp)) 772 | (search-forward "\\" nil t)) 773 | (delete-char -1) 774 | (forward-char)) 775 | (condition-case condition 776 | (progn 777 | (check-parens) 778 | (buffer-string)) 779 | (error nil)))) 780 | 781 | (defun grammatical-edit-hack-kill-region (start end) 782 | (let ((this-command nil) 783 | (last-command nil)) 784 | (grammatical-edit-delete-region start end))) 785 | 786 | (defun grammatical-edit-backward-kill-internal () 787 | (cond (current-prefix-arg 788 | (kill-line (if (integerp current-prefix-arg) 789 | current-prefix-arg 790 | 1))) 791 | ((grammatical-edit-in-string-p) 792 | (grammatical-edit-kill-before-in-string)) 793 | ((or (grammatical-edit-in-comment-p) 794 | (save-excursion 795 | (grammatical-edit-skip-whitespace nil (line-beginning-position)) 796 | (bolp))) 797 | (if (bolp) (grammatical-edit-backward-delete) 798 | (kill-line 0))) 799 | (t (grammatical-edit-kill-sexps-backward-on-line)))) 800 | 801 | (defun grammatical-edit-js-mode-kill-rest-string () 802 | (grammatical-edit-delete-region (point) 803 | (save-excursion 804 | (forward-sexp) 805 | (backward-char) 806 | (point)))) 807 | 808 | (defun grammatical-edit-at-raw-string-begin-p () 809 | (let ((current-node (tree-sitter-node-at-pos))) 810 | (and (grammatical-edit-is-string-node-p current-node) 811 | (= (point) (1+ (tsc-node-start-position current-node))) 812 | (or (eq (char-before) ?R) 813 | (eq (char-before) ?r) 814 | )))) 815 | 816 | (defun grammatical-edit-kill-after-in-string () 817 | (if (let ((current-node (tree-sitter-node-at-pos))) 818 | (and (grammatical-edit-is-string-node-p current-node) 819 | (> (point) (tsc-node-start-position current-node)))) 820 | (let* ((parent-bound-info (grammatical-edit-get-parent-bound-info)) 821 | (current-node (nth 0 parent-bound-info)) 822 | (current-node-bound-end (tsc-node-text (save-excursion 823 | (goto-char (tsc-node-end-position current-node)) 824 | (backward-char 1) 825 | (tree-sitter-node-at-pos))))) 826 | (cond ((grammatical-edit-at-raw-string-begin-p) 827 | (grammatical-edit-delete-region (tsc-node-start-position current-node) (tsc-node-end-position current-node))) 828 | ((string-equal current-node-bound-end "'''") 829 | (grammatical-edit-delete-region (point) (- (tsc-node-end-position current-node) (length current-node-bound-end)))) 830 | (t 831 | (grammatical-edit-delete-region (point) (- (tsc-node-end-position current-node) 1))))) 832 | (grammatical-edit-kill-line-in-string))) 833 | 834 | (defun grammatical-edit-kill-line-in-string () 835 | (cond ((save-excursion 836 | (grammatical-edit-skip-whitespace t (point-at-eol)) 837 | (eolp)) 838 | (kill-line)) 839 | (t 840 | (save-excursion 841 | (if (grammatical-edit-in-string-escape-p) 842 | (backward-char)) 843 | (let ((beginning (point))) 844 | (while (save-excursion 845 | (forward-char) 846 | (grammatical-edit-in-string-p)) 847 | (forward-char)) 848 | (kill-region beginning (point))) 849 | )))) 850 | 851 | (defun grammatical-edit-in-string-escape-p () 852 | (let ((oddp nil)) 853 | (save-excursion 854 | (while (eq (char-before) ?\\ ) 855 | (setq oddp (not oddp)) 856 | (backward-char))) 857 | oddp)) 858 | 859 | (defun grammatical-edit-skip-whitespace (trailing-p &optional limit) 860 | (funcall (if trailing-p 'skip-chars-forward 'skip-chars-backward) 861 | " \t\n" 862 | limit)) 863 | 864 | (defun grammatical-edit-kill-before-in-string () 865 | (grammatical-edit-delete-region (point) (1+ (tsc-node-start-position (tree-sitter-node-at-pos))))) 866 | 867 | (defun grammatical-edit-skip-whitespace (trailing-p &optional limit) 868 | (funcall (if trailing-p #'skip-chars-forward #'skip-chars-backward) 869 | " \t\n" 870 | limit)) 871 | 872 | (defun grammatical-edit-kill-sexps-on-line () 873 | "Kill forward sexp on the current line." 874 | (condition-case nil 875 | (progn 876 | (when (grammatical-edit-in-char-p) 877 | (backward-char 2)) 878 | (let* ((begin-point (point)) 879 | (eol (line-end-position)) 880 | (end-of-list-p (grammatical-edit-forward-sexps-to-kill begin-point eol))) 881 | (when end-of-list-p 882 | (up-list) 883 | (backward-char)) 884 | (goto-char (if (and (not end-of-list-p) (eq (line-end-position) eol)) 885 | eol 886 | (point))) 887 | ;; NOTE: Back to previous line if point is at the beginning of line. 888 | (when (bolp) 889 | (backward-char 1)) 890 | (grammatical-edit-delete-region begin-point (point)))) 891 | ;; Delete rest content of line when kill sexp throw `scan-error' error. 892 | (scan-error (grammatical-edit-delete-region (point) (point-at-eol))))) 893 | 894 | (defun grammatical-edit-kill-sexps-backward-on-line () 895 | "Kill backward sexp on the current line." 896 | (when (grammatical-edit-in-char-p) 897 | (forward-char 1)) 898 | (let* ((begin-point (point)) 899 | (bol (line-beginning-position)) 900 | (beg-of-list-p (grammatical-edit-backward-sexps-to-kill begin-point bol))) 901 | (when beg-of-list-p 902 | (up-list -1) 903 | (forward-char)) 904 | (grammatical-edit-delete-region (if (and (not beg-of-list-p) (eq (line-beginning-position) bol)) 905 | bol 906 | (point)) 907 | begin-point))) 908 | 909 | (defun grammatical-edit-forward-sexps-to-kill (beginning eol) 910 | (let ((end-of-list-p nil) 911 | (firstp t)) 912 | (catch 'return 913 | (while t 914 | (save-excursion 915 | (unless (grammatical-edit-ignore-errors (forward-sexp)) 916 | (when (grammatical-edit-ignore-errors (up-list)) 917 | (setq end-of-list-p (eq (line-end-position) eol)) 918 | (throw 'return nil))) 919 | (if (or (and (not firstp) 920 | (eobp)) 921 | (not (grammatical-edit-ignore-errors (backward-sexp))) 922 | (not (eq (line-end-position) eol))) 923 | (throw 'return nil))) 924 | (forward-sexp) 925 | (if (and firstp 926 | (eobp)) 927 | (throw 'return nil)) 928 | (setq firstp nil))) 929 | end-of-list-p)) 930 | 931 | (defun grammatical-edit-backward-sexps-to-kill (beginning bol) 932 | (let ((beg-of-list-p nil) 933 | (lastp t)) 934 | (catch 'return 935 | (while t 936 | (save-excursion 937 | (unless (grammatical-edit-ignore-errors (backward-sexp)) 938 | (when (grammatical-edit-ignore-errors (up-list -1)) 939 | (setq beg-of-list-p (eq (line-beginning-position) bol)) 940 | (throw 'return nil))) 941 | (if (or (and (not lastp) 942 | (bobp)) 943 | (not (grammatical-edit-ignore-errors (forward-sexp))) 944 | (not (eq (line-beginning-position) bol))) 945 | (throw 'return nil))) 946 | (backward-sexp) 947 | (if (and lastp 948 | (bobp)) 949 | (throw 'return nil)) 950 | (setq lastp nil))) 951 | beg-of-list-p)) 952 | 953 | (defun grammatical-edit-common-mode-kill () 954 | (cond ((grammatical-edit-is-blank-line-p) 955 | (grammatical-edit-kill-blank-line-and-reindent)) 956 | (current-prefix-arg 957 | (kill-line (if (integerp current-prefix-arg) 958 | current-prefix-arg 959 | 1))) 960 | ((grammatical-edit-in-string-p) 961 | (grammatical-edit-kill-after-in-string)) 962 | ((or (grammatical-edit-in-comment-p) 963 | (save-excursion 964 | (grammatical-edit-skip-whitespace t (line-end-position)) 965 | (or (eq (char-after) ?\; ) 966 | (eolp)))) 967 | (kill-line)) 968 | (t (grammatical-edit-kill-sexps-on-line)))) 969 | 970 | (defun grammatical-edit-common-mode-backward-kill () 971 | (if (grammatical-edit-is-blank-line-p) 972 | (grammatical-edit-ignore-errors 973 | (progn 974 | (grammatical-edit-kill-blank-line-and-reindent) 975 | (forward-line -1) 976 | (end-of-line))) 977 | (grammatical-edit-backward-kill-internal))) 978 | 979 | (defun grammatical-edit-kill-parent-node () 980 | (let ((range (tsc-node-position-range (tsc-get-parent (tree-sitter-node-at-pos))))) 981 | (grammatical-edit-delete-region (car range) (cdr range)))) 982 | 983 | (defun grammatical-edit-kill-grandfather-node () 984 | (let ((range (tsc-node-position-range (tsc-get-parent (tsc-get-parent (tree-sitter-node-at-pos)))))) 985 | (grammatical-edit-delete-region (car range) (cdr range)))) 986 | 987 | (defun grammatical-edit-kill-prepend-space () 988 | (grammatical-edit-delete-region (save-excursion 989 | (search-backward-regexp "[^ \t\n]" nil t) 990 | (forward-char 1) 991 | (point)) 992 | (point))) 993 | 994 | (defun grammatical-edit-at-tag-right (tag) 995 | (save-excursion 996 | (backward-char 1) 997 | (eq (grammatical-edit-node-type-at-point) tag))) 998 | 999 | (defun grammatical-edit-web-mode-kill () 1000 | "It's a smarter kill function for `web-mode'." 1001 | (if (grammatical-edit-is-blank-line-p) 1002 | (grammatical-edit-kill-blank-line-and-reindent) 1003 | (cond 1004 | ;; Kill from current point to attribute end position. 1005 | ((eq (grammatical-edit-node-type-at-point) 'attribute_value) 1006 | (grammatical-edit-delete-region (point) (tsc-node-end-position (tree-sitter-node-at-pos)))) 1007 | 1008 | ;; Kill parent node if cursor at attribute or directive node. 1009 | ((or (eq (grammatical-edit-node-type-at-point) 'attribute_name) 1010 | (eq (grammatical-edit-node-type-at-point) 'directive_name)) 1011 | (grammatical-edit-kill-parent-node)) 1012 | 1013 | ;; Jump to next non-blank char if in tag area. 1014 | ((eq (grammatical-edit-node-type-at-point) 'self_closing_tag) 1015 | (search-forward-regexp "\\s-+")) 1016 | 1017 | ;; Clean blank spaces before close tag. 1018 | ((string-equal (grammatical-edit-node-type-at-point) "/>") 1019 | (cond ((looking-back "\\s-" nil) 1020 | (grammatical-edit-kill-prepend-space)) 1021 | ;; Kill tag if nothing in tag area. 1022 | ((grammatical-edit-at-tag-right 'tag_name) 1023 | (backward-char 1) 1024 | (grammatical-edit-kill-parent-node)) 1025 | (t 1026 | (message "Nothing to kill in tag. ;)")))) 1027 | 1028 | ;; Clean blank spaces before start tag. 1029 | ((string-equal (grammatical-edit-node-type-at-point) ">") 1030 | (cond ((looking-back "\\s-" nil) 1031 | (grammatical-edit-kill-prepend-space)) 1032 | ;; Kill tag content if nothing in tag area. 1033 | ((grammatical-edit-at-tag-right 'tag_name) 1034 | (backward-char 1) 1035 | (grammatical-edit-kill-grandfather-node)) 1036 | (t 1037 | (message "Nothing to kill in tag. ;)")))) 1038 | 1039 | ;; Clean blank space before ") 1045 | (backward-char 1) 1046 | (grammatical-edit-kill-grandfather-node)) 1047 | (t 1048 | (message "Nothing to kill in tag. ;)")))) 1049 | 1050 | ;; Kill all tag content if cursor in tag start area. 1051 | ((eq (grammatical-edit-node-type-at-point) 'tag_name) 1052 | (grammatical-edit-kill-parent-node)) 1053 | 1054 | ;; Kill tag content if cursor at left of < 1055 | ((string-equal (grammatical-edit-node-type-at-point) "<") 1056 | (grammatical-edit-kill-grandfather-node)) 1057 | 1058 | ;; Kill string if cursor at start of quote. 1059 | ((string-equal (grammatical-edit-node-type-at-point) "\"") 1060 | (forward-char 1) 1061 | (grammatical-edit-kill-parent-node)) 1062 | 1063 | ;; Kill content if in start_tag area. 1064 | ((eq (grammatical-edit-node-type-at-point) 'start_tag) 1065 | (cond ((looking-at "\\s-") 1066 | (search-forward-regexp "\\s-+")) 1067 | ((save-excursion 1068 | (grammatical-edit-skip-whitespace t (line-end-position)) 1069 | (or (eq (char-after) ?\; ) 1070 | (eolp))) 1071 | (kill-line)))) 1072 | 1073 | ;; JavaScript string not identify by tree-sitter. 1074 | ;; We need use `grammatical-edit-current-parse-state' test cursor 1075 | ;; whether in string. 1076 | ((and (eq (grammatical-edit-node-type-at-point) 'raw_text) 1077 | (save-excursion (nth 3 (grammatical-edit-current-parse-state)))) 1078 | (grammatical-edit-js-mode-kill-rest-string)) 1079 | 1080 | ;; Use common kill at last. 1081 | (t 1082 | (grammatical-edit-common-mode-kill))))) 1083 | 1084 | (defun grammatical-edit-web-mode-backward-kill () 1085 | (message "Backward kill in web-mode is currently not implemented.")) 1086 | 1087 | (defun grammatical-edit-kill-blank-line-and-reindent () 1088 | (grammatical-edit-delete-region (beginning-of-thing 'line) (end-of-thing 'line)) 1089 | (back-to-indentation)) 1090 | 1091 | (defun grammatical-edit-indent-parent-area () 1092 | (let ((range (tsc-node-position-range (tsc-get-parent (tree-sitter-node-at-pos))))) 1093 | (indent-region (car range) (cdr range)))) 1094 | 1095 | (defun grammatical-edit-equal () 1096 | (interactive) 1097 | (cond 1098 | ((derived-mode-p 'web-mode) 1099 | (cond ((or (eq (grammatical-edit-node-type-at-point) 'attribute_value) 1100 | (eq (grammatical-edit-node-type-at-point) 'raw_text) 1101 | (eq (grammatical-edit-node-type-at-point) 'text)) 1102 | (insert "=")) 1103 | ;; Insert equal and double quotes if in tag attribute area. 1104 | ((and (string-equal (file-name-extension (buffer-file-name)) "vue") 1105 | (grammatical-edit-vue-in-template-area-p) 1106 | (or (eq (grammatical-edit-node-type-at-point) 'directive_name) 1107 | (eq (grammatical-edit-node-type-at-point) 'attribute_name) 1108 | (eq (grammatical-edit-node-type-at-point) 'start_tag))) 1109 | (insert "=\"\"") 1110 | (backward-char 1)) 1111 | (t 1112 | (insert "=")))) 1113 | (t 1114 | (insert "=")))) 1115 | 1116 | (defun grammatical-edit-in-script-area-p () 1117 | (and (save-excursion 1118 | (search-backward-regexp "" nil t)))) 1121 | 1122 | (defun grammatical-edit-vue-in-template-area-p () 1123 | (and (save-excursion 1124 | (search-backward-regexp "" nil t)))) 1127 | 1128 | (defun grammatical-edit-web-mode-element-wrap () 1129 | "Like `web-mode-element-wrap', but jump after tag for continue edit." 1130 | (interactive) 1131 | (let (beg end pos tag beg-sep) 1132 | ;; Insert tag pair around select area. 1133 | (save-excursion 1134 | (setq tag (read-from-minibuffer "Tag name? ")) 1135 | (setq pos (point)) 1136 | (cond 1137 | (mark-active 1138 | (setq beg (region-beginning)) 1139 | (setq end (region-end))) 1140 | ((get-text-property pos 'tag-type) 1141 | (setq beg (web-mode-element-beginning-position pos) 1142 | end (1+ (web-mode-element-end-position pos)))) 1143 | ((setq beg (web-mode-element-parent-position pos)) 1144 | (setq end (1+ (web-mode-element-end-position pos))))) 1145 | (when (and beg end (> end 0)) 1146 | (web-mode-insert-text-at-pos (concat "") end) 1147 | (web-mode-insert-text-at-pos (concat "<" tag ">") beg))) 1148 | 1149 | (when (and beg end) 1150 | ;; Insert return after start tag if have text after start tag. 1151 | (setq beg-sep "") 1152 | (goto-char (+ beg (length (concat "<" tag ">")))) 1153 | (unless (looking-at "\\s-*$") 1154 | (setq beg-sep "\n") 1155 | (insert "\n")) 1156 | 1157 | ;; Insert return before end tag if have text before end tag. 1158 | (goto-char (+ end (length (concat "<" tag ">")) (length beg-sep))) 1159 | (unless (looking-back "^\\s-*" nil) 1160 | (insert "\n")) 1161 | 1162 | ;; Insert return after end tag if have text after end tag. 1163 | (goto-char beg) 1164 | (goto-char (+ 1 (web-mode-element-end-position (point)))) 1165 | (unless (looking-at "\\s-*$") 1166 | (insert "\n")) 1167 | 1168 | ;; Indent tag area. 1169 | (let ((indent-beg beg) 1170 | (indent-end (save-excursion 1171 | (goto-char beg) 1172 | (+ 1 (web-mode-element-end-position (point))) 1173 | ))) 1174 | (indent-region indent-beg indent-end)) 1175 | 1176 | ;; Jump to start tag, ready for insert tag attributes. 1177 | (goto-char beg) 1178 | (back-to-indentation) 1179 | (forward-char (+ 1 (length tag)))))) 1180 | 1181 | (defun grammatical-edit-web-mode-element-unwrap () 1182 | "Like `web-mode-element-vanish', but you don't need jump parent tag to unwrap. 1183 | Just like `paredit-splice-sexp+' style." 1184 | (interactive) 1185 | (save-excursion 1186 | (web-mode-element-parent) 1187 | (web-mode-element-vanish 1) 1188 | (back-to-indentation))) 1189 | 1190 | (defun grammatical-edit-match-paren (arg) 1191 | "Go to the matching parenthesis if on parenthesis, otherwise insert %." 1192 | (interactive "p") 1193 | (cond ((or (grammatical-edit-in-comment-p) 1194 | (grammatical-edit-in-string-p)) 1195 | (self-insert-command (or arg 1))) 1196 | ((looking-at "\\s\(\\|\\s\{\\|\\s\[") 1197 | (forward-list)) 1198 | ((looking-back "\\s\)\\|\\s\}\\|\\s\\]" nil) 1199 | (backward-list)) 1200 | (t 1201 | (cond 1202 | ;; Enhancement the automatic jump of web-mode. 1203 | ((derived-mode-p 'web-mode) 1204 | (grammatical-edit-web-mode-match-paren)) 1205 | (t 1206 | (self-insert-command (or arg 1))))))) 1207 | 1208 | ;;;;;;;;;;;;;;;;; Utils functions ;;;;;;;;;;;;;;;;;;;;;; 1209 | 1210 | (defun grammatical-edit-wrap (beg end a b) 1211 | "Insert A at position BEG, and B after END. Save previous point position. 1212 | 1213 | A and B are strings." 1214 | (goto-char end) 1215 | (insert b) 1216 | (goto-char beg) 1217 | (insert a)) 1218 | 1219 | (defun grammatical-edit-wrap-region (a b) 1220 | "When a region is active, insert A and B around it, and jump after A. 1221 | 1222 | A and B are strings." 1223 | (when (region-active-p) 1224 | (let ((start (region-beginning)) 1225 | (end (region-end))) 1226 | (setq mark-active nil) 1227 | (goto-char end) 1228 | (insert b) 1229 | (goto-char start) 1230 | (insert a)))) 1231 | 1232 | (defun grammatical-edit-current-parse-state () 1233 | (let ((point (point))) 1234 | (beginning-of-defun) 1235 | (when (equal point (point)) 1236 | (beginning-of-line)) 1237 | (parse-partial-sexp (min (point) point) 1238 | (max (point) point)))) 1239 | 1240 | (defun grammatical-edit-current-node-range () 1241 | (tsc-node-position-range (tree-sitter-node-at-pos))) 1242 | 1243 | (defun grammatical-edit-after-open-pair-p () 1244 | (unless (bobp) 1245 | (save-excursion 1246 | (let ((syn (char-syntax (char-before)))) 1247 | (or (eq syn ?\() 1248 | (and (eq syn ?_) 1249 | (eq (char-before) ?\{))) 1250 | )))) 1251 | 1252 | (defun grammatical-edit-after-close-pair-p () 1253 | (unless (bobp) 1254 | (save-excursion 1255 | (let ((syn (char-syntax (char-before)))) 1256 | (or (eq syn ?\) ) 1257 | (eq syn ?\" ) 1258 | (and (eq syn ?_ ) 1259 | (eq (char-before) ?\}))))))) 1260 | 1261 | (defun grammatical-edit-before-open-pair-p () 1262 | (unless (eobp) 1263 | (save-excursion 1264 | (let ((syn (char-syntax (char-after)))) 1265 | (or (eq syn ?\( ) 1266 | (and (eq syn ?_) 1267 | (eq (char-after) ?\{))))))) 1268 | 1269 | (defun grammatical-edit-before-close-pair-p () 1270 | (unless (eobp) 1271 | (save-excursion 1272 | (let ((syn (char-syntax (char-after)))) 1273 | (or (eq syn ?\) ) 1274 | (and (eq syn ?_) 1275 | (eq (char-after) ?\}))))))) 1276 | 1277 | (defun grammatical-edit-in-empty-pair-p () 1278 | (ignore-errors 1279 | (save-excursion 1280 | (or (and (eq (char-syntax (char-before)) ?\() 1281 | (eq (char-after) (matching-paren (char-before)))) 1282 | (and (eq (char-syntax (char-before)) ?_) 1283 | (eq (char-before) ?\{) 1284 | (eq (char-syntax (char-after)) ?_) 1285 | (eq (char-after) ?\}) 1286 | ))))) 1287 | 1288 | (defun grammatical-edit-node-type-at-point () 1289 | (ignore-errors (tsc-node-type (tree-sitter-node-at-pos)))) 1290 | 1291 | (defun grammatical-edit-in-string-p () 1292 | (ignore-errors 1293 | (or 1294 | ;; If node type is 'string, point must at right of string open quote. 1295 | (let ((current-node (tree-sitter-node-at-pos))) 1296 | (and (grammatical-edit-is-string-node-p current-node) 1297 | (> (point) (tsc-node-start-position current-node)))) 1298 | 1299 | (nth 3 (grammatical-edit-current-parse-state)) 1300 | 1301 | (grammatical-edit-before-string-close-quote-p)))) 1302 | 1303 | (defun grammatical-edit-in-single-quote-string-p () 1304 | (ignore-errors 1305 | (let ((parent-node-text (tsc-node-text (tsc-get-parent (tree-sitter-node-at-pos))))) 1306 | (and (grammatical-edit-in-string-p) 1307 | (> (length parent-node-text) 1) 1308 | (string-equal (substring parent-node-text 0 1) "'"))))) 1309 | 1310 | (defun grammatical-edit-before-string-close-quote-p () 1311 | (let ((current-node (tree-sitter-node-at-pos))) 1312 | (and 1313 | (= (point) (tsc-node-start-position current-node)) 1314 | (string-equal (tsc-node-type current-node) "\"") 1315 | (save-excursion 1316 | (forward-char (length (tsc-node-text current-node))) 1317 | (not (grammatical-edit-is-string-node-p (tree-sitter-node-at-pos))) 1318 | )))) 1319 | 1320 | (defun grammatical-edit-after-open-quote-p () 1321 | (and (not (string-equal (grammatical-edit-node-type-at-point) "\"")) 1322 | (save-excursion 1323 | (backward-char 1) 1324 | (string-equal (grammatical-edit-node-type-at-point) "\"")))) 1325 | 1326 | (defun grammatical-edit-before-string-open-quote-p () 1327 | (and (not (grammatical-edit-in-string-p)) 1328 | (not (grammatical-edit-in-empty-string-p)) 1329 | (or (string-equal (grammatical-edit-node-type-at-point) "\"") 1330 | (eq (grammatical-edit-node-type-at-point) 'raw_string_literal)))) 1331 | 1332 | (defun grammatical-edit-in-comment-p () 1333 | (or (eq (grammatical-edit-node-type-at-point) 'comment) 1334 | (and (eolp) 1335 | (ignore-errors 1336 | (save-excursion 1337 | (backward-char 1) 1338 | (eq (grammatical-edit-node-type-at-point) 'comment)))))) 1339 | 1340 | (defun grammatical-edit-in-char-p (&optional argument) 1341 | (let ((argument (or argument (point)))) 1342 | (and (eq (char-before argument) ?\\ ) 1343 | (not (eq (char-before (1- argument)) ?\\ ))))) 1344 | 1345 | (defun grammatical-edit-is-blank-line-p () 1346 | (save-excursion 1347 | (beginning-of-line) 1348 | (looking-at "[[:space:]]*$"))) 1349 | 1350 | (defun grammatical-edit-only-whitespaces-before-cursor-p () 1351 | (let ((string-before-cursor 1352 | (buffer-substring 1353 | (save-excursion 1354 | (beginning-of-line) 1355 | (point)) 1356 | (point)))) 1357 | (equal (length (string-trim string-before-cursor)) 0))) 1358 | 1359 | (defun grammatical-edit-newline (arg) 1360 | (interactive "p") 1361 | (cond 1362 | ;; Just newline if in string or comment. 1363 | ((or (grammatical-edit-in-comment-p) 1364 | (grammatical-edit-in-string-p)) 1365 | (newline arg)) 1366 | ((derived-mode-p 'inferior-emacs-lisp-mode) 1367 | (ielm-return)) 1368 | ;; Newline and indent region if cursor in parentheses and character is not blank after cursor. 1369 | ((and (looking-back "(\s*\\|{\s*\\|\\[\s*" nil) 1370 | (looking-at-p "\s*)\\|\s*}\\|\s*\\]")) 1371 | ;; Insert blank below at parentheses. 1372 | (newline arg) 1373 | (open-line 1) 1374 | ;; Indent close parentheses line. 1375 | (save-excursion 1376 | (grammatical-edit-jump-left) 1377 | (grammatical-edit-match-paren 1) 1378 | (indent-according-to-mode)) 1379 | ;; Indent blank line. 1380 | (indent-according-to-mode)) 1381 | ;; Newline and indent. 1382 | (t 1383 | (newline arg) 1384 | (indent-according-to-mode)))) 1385 | 1386 | (defun grammatical-edit-jump-up () 1387 | (interactive) 1388 | (let* ((current-node (tree-sitter-node-at-pos)) 1389 | (parent-node (tsc-get-parent current-node))) 1390 | (if parent-node 1391 | (let ((parent-node-start-position (tsc-node-start-position parent-node))) 1392 | (if (equal parent-node-start-position (point)) 1393 | (progn 1394 | (backward-char) 1395 | (grammatical-edit-jump-up)) 1396 | (goto-char parent-node-start-position) 1397 | (scroll-down 3))) 1398 | (grammatical-edit-jump-left) 1399 | (back-to-indentation)))) 1400 | 1401 | ;; Integrate with eldoc 1402 | (with-eval-after-load 'eldoc 1403 | (eldoc-add-command-completions 1404 | "grammatical-edit-")) 1405 | 1406 | (provide 'grammatical-edit) 1407 | 1408 | ;;; grammatical-edit.el ends here 1409 | --------------------------------------------------------------------------------