227 lines
7.6 KiB
EmacsLisp
227 lines
7.6 KiB
EmacsLisp
;;; js2r-paredit.el --- Paredit-like extensions for js2-refactor -*- lexical-binding: t; -*-
|
|
|
|
;; Copyright (C) 2012-2014 Magnar Sveen
|
|
;; Copyright (C) 2015-2016 Magnar Sveen and Nicolas Petton
|
|
|
|
;; Author: Magnar Sveen <magnars@gmail.com>,
|
|
;; Nicolas Petton <nicolas@petton.fr>
|
|
;; Keywords: conveniences
|
|
|
|
;; This program is free software; you can redistribute it and/or modify
|
|
;; it under the terms of the GNU General Public License as published by
|
|
;; the Free Software Foundation, either version 3 of the License, or
|
|
;; (at your option) any later version.
|
|
|
|
;; This program is distributed in the hope that it will be useful,
|
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
;; GNU General Public License for more details.
|
|
|
|
;; You should have received a copy of the GNU General Public License
|
|
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
;;; Code:
|
|
|
|
(require 'dash)
|
|
|
|
(require 'js2r-helpers)
|
|
|
|
(defun js2r--nesting-node-p (node)
|
|
(or (js2-function-node-p node)
|
|
(js2-if-node-p node)
|
|
(js2-for-node-p node)
|
|
(js2-while-node-p node)))
|
|
|
|
(defun js2r--standalone-node-p (node)
|
|
(or (js2-stmt-node-p node)
|
|
(and (js2-function-node-p node)
|
|
(eq 'FUNCTION_STATEMENT (js2-function-node-form node)))))
|
|
|
|
(defun js2r-kill ()
|
|
"Kill a line like `kill-line' but tries to respect node boundaries.
|
|
Falls back to `kill-line' if the buffer has parse errors.
|
|
|
|
if(|foo) {bar();} -> if() {bar();}
|
|
|
|
function foo() {|2 + 3} -> function foo() {}
|
|
|
|
// some |comment -> // some
|
|
|
|
'this is a| string' -> 'this is a'
|
|
"
|
|
(interactive)
|
|
(if js2-parsed-errors
|
|
(progn
|
|
(message "Buffer has parse errors. Killing the line")
|
|
(kill-line))
|
|
(condition-case error
|
|
(js2r--kill-line)
|
|
(progn
|
|
(message "Error occured while trying to kill AST node. Killing the line.")
|
|
(kill-line)))))
|
|
|
|
(defun js2r--kill-line ()
|
|
"Kill a line, but respecting node boundaries."
|
|
(let ((node (js2r--next-node)))
|
|
(cond
|
|
((js2-comment-node-p node) (kill-line))
|
|
((js2-string-node-p node) (js2r--kill-line-in-string))
|
|
(t (js2r--kill-line-in-sexp)))))
|
|
|
|
(defun js2r--next-node ()
|
|
"Return the node at point, or the node after the point if the
|
|
point is at the exact end of a node."
|
|
(save-excursion
|
|
(when (= (js2-node-abs-end (js2-node-at-point))
|
|
(point))
|
|
(forward-char 1))
|
|
(js2-node-at-point)))
|
|
|
|
(defun js2r--kill-line-in-sexp ()
|
|
"Kill a line, but only kills until the closest outer sexp on
|
|
the current line, delimited with \")}]\". If no sexp is found
|
|
on the current line, falls back to
|
|
`js2r--kill-line-with-inner-sexp'."
|
|
(condition-case error
|
|
(let* ((beg (point))
|
|
(end (save-excursion
|
|
(up-list)
|
|
(forward-char -1)
|
|
(point))))
|
|
(if (js2-same-line end)
|
|
(kill-region beg end)
|
|
(js2r--kill-line-with-inner-sexp)))
|
|
(scan-error
|
|
(js2r--kill-line-with-inner-sexp))))
|
|
|
|
(defun js2r--kill-line-with-inner-sexp ()
|
|
"Kill a line, but respecting inner killed sexps, ensuring that
|
|
we kill up to the end to the next inner sexp if it starts in
|
|
the current line.
|
|
|
|
If the parentheses are unbalanced, fallback to `kill-line' and
|
|
warn the user."
|
|
(condition-case error
|
|
(let* ((beg (point))
|
|
(end (save-excursion
|
|
(forward-visible-line 1)
|
|
(point)))
|
|
(beg-of-sexp (save-excursion
|
|
(js2r--goto-last-sexp-on-line)
|
|
(point)))
|
|
(end-of-sexp (save-excursion
|
|
(goto-char beg-of-sexp)
|
|
(forward-list)
|
|
;; Kill all remaining semi-colons as well
|
|
(while (looking-at ";")
|
|
(forward-char))
|
|
(point))))
|
|
(if (js2-same-line beg-of-sexp)
|
|
(kill-region beg (max end end-of-sexp))
|
|
(kill-line)))
|
|
(scan-error
|
|
(message "Unbalanced parentheses. Killing the line.")
|
|
(kill-line))))
|
|
|
|
(defun js2r--goto-last-sexp-on-line ()
|
|
"Move the cursor to the opening of the last sexp on the current
|
|
line, or to the end of the line if no sexp is found."
|
|
(let ((pos (point)))
|
|
(down-list)
|
|
(backward-char 1)
|
|
(forward-list)
|
|
(if (js2-same-line pos)
|
|
(js2r--goto-last-sexp-on-line)
|
|
(backward-list))))
|
|
|
|
(defun js2r--kill-line-in-string ()
|
|
"Kill a line in a string node, respecting the node boundaries.
|
|
When at the beginning of the node, kill from outside of it."
|
|
(let* ((node (js2-node-at-point))
|
|
(beg (point))
|
|
(node-start (js2-node-abs-pos node))
|
|
(node-end (js2-node-abs-end node)))
|
|
(if (= beg node-start)
|
|
(js2r--kill-line-in-sexp)
|
|
(kill-region beg (1- node-end)))))
|
|
|
|
(defun js2r-forward-slurp (&optional arg)
|
|
"Add the expression following the current function into it.
|
|
|
|
The addition is performed by moving the closing brace of the
|
|
function down.
|
|
|
|
When called with a prefix argument ARG, slurp ARG expressions
|
|
following the current function."
|
|
(interactive "p")
|
|
(js2r--guard)
|
|
(js2r--wait-for-parse
|
|
(let* ((nesting (js2r--closest 'js2r--nesting-node-p))
|
|
(standalone (if (js2r--standalone-node-p nesting)
|
|
nesting
|
|
(js2-node-parent-stmt nesting)))
|
|
(next-sibling (js2-node-next-sibling standalone))
|
|
(beg (js2-node-abs-pos next-sibling))
|
|
(last-sibling (if (wholenump arg)
|
|
(let ((num arg)
|
|
(iter-sibling next-sibling))
|
|
(while (> num 1) ;; Do next-sibling arg nbr of times
|
|
(setq iter-sibling (js2-node-next-sibling iter-sibling))
|
|
(setq num (1- num)))
|
|
iter-sibling)
|
|
next-sibling)) ;; No optional arg. Just use next-sibling
|
|
(end (js2-node-abs-end last-sibling))
|
|
(text (buffer-substring beg end)))
|
|
(save-excursion
|
|
(delete-region beg end)
|
|
;; Delete newline character if the deleted AST node was at the end of the line
|
|
(goto-char beg)
|
|
(when (and (eolp)
|
|
(not (eobp)))
|
|
(delete-char 1))
|
|
(goto-char (js2-node-abs-end nesting))
|
|
(forward-char -1)
|
|
(when (looking-back "{ *")
|
|
(newline))
|
|
(setq beg (point))
|
|
(insert text)
|
|
(when (looking-at " *}")
|
|
(newline))
|
|
(setq end (point))
|
|
(indent-region beg end)))))
|
|
|
|
(defun js2r-forward-barf (&optional arg)
|
|
(interactive "p")
|
|
(js2r--guard)
|
|
(js2r--wait-for-parse
|
|
(let* ((nesting (js2r--closest 'js2r--nesting-node-p))
|
|
(standalone (if (js2r--standalone-node-p nesting)
|
|
nesting
|
|
(js2-node-parent-stmt nesting)))
|
|
(standalone-end (js2-node-abs-end standalone))
|
|
(last-child (car (last (if (js2-if-node-p nesting)
|
|
(js2-scope-kids (js2r--closest 'js2-scope-p))
|
|
(js2r--node-kids nesting)))))
|
|
(first-barf-child (if (wholenump arg)
|
|
(let ((num arg)
|
|
(iter-child last-child))
|
|
(while (> num 1) ;; Do prev-sibling arg nbr of times
|
|
(setq iter-child (js2-node-prev-sibling iter-child))
|
|
(setq num (1- num)))
|
|
iter-child)
|
|
last-child)) ; No optional arg. Just use last-child
|
|
(last-child-beg (save-excursion
|
|
(goto-char (js2-node-abs-pos first-barf-child))
|
|
(skip-syntax-backward " ")
|
|
(while (looking-back "\n") (backward-char))
|
|
(point)))
|
|
(last-child-end (js2-node-abs-end last-child))
|
|
(text (buffer-substring last-child-beg last-child-end)))
|
|
(save-excursion
|
|
(js2r--execute-changes
|
|
(list
|
|
(list :beg last-child-beg :end last-child-end :contents "")
|
|
(list :beg standalone-end :end standalone-end :contents text)))))))
|
|
|
|
(provide 'js2r-paredit)
|
|
;;; js2r-paredit.el ends here
|