;;; js2r-formatting.el --- Private helper functions for formatting -*- lexical-binding: t; -*- ;; Copyright (C) 2012-2014 Magnar Sveen ;; Copyright (C) 2015-2016 Magnar Sveen and Nicolas Petton ;; Author: Magnar Sveen , ;; Nicolas Petton ;; 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 . ;;; Code: (defun js2r--ensure-newline () (if (and (not (looking-at "\s*\n")) (not (looking-back "\n\s*"))) (newline-and-indent))) (defun js2r--ensure-just-one-space () (interactive) (while (or (looking-at "\s*\n") (looking-back "\n\s*")) (when (looking-at "\n") (delete-char 1)) (when (looking-back "\n\s") (backward-char) (delete-char -1)) (just-one-space)) (just-one-space)) (defmacro js2r--create-bracketed-whitespace-traverser (name ws-fix-func looking-at-start-func goto-closest-start-func subexpr-str) "Build a function to expand or contract a given type of bracketed expression, i.e., function body, object literal, or array (any of which may be nested). Parameters: name: name of the function to be built ws-fix-func: function to adjust whitespace at point looking-at-start-func: returns t if point is at the start of the bracketed thing we want to act on goto-closest-start-func: moves point if necessary until looking-at-start-func is true subexpr-str: literal delimiter of parts of the thing to be expanded or contracted" `(defun ,name () (interactive) (save-excursion (if (not ,looking-at-start-func) ,goto-closest-start-func) (let ((end (make-marker))) (set-marker end (save-excursion (forward-list) (point))) (forward-char) ,ws-fix-func (while (< (point) end) (while (js2r--point-inside-string-p) (forward-char)) (when (looking-at ,subexpr-str) (forward-char) (unless (js2-comment-node-p (js2-node-at-point)) ,ws-fix-func)) (if (looking-at "\\s(") (forward-list) (forward-char))) (backward-char) ,ws-fix-func)))) (defun js2r--looking-at-object-start () (and (looking-at "{") (not (looking-back ")[\s\n]*")))) (defun js2r--goto-closest-object-start () (while (not (js2r--looking-at-object-start)) (if (eq (car (syntax-ppss)) 0) (error "Cursor is not on an object") (goto-char (nth 1 (syntax-ppss)))))) (js2r--create-bracketed-whitespace-traverser js2r-expand-object (js2r--ensure-newline) (js2r--looking-at-object-start) (js2r--goto-closest-object-start) ",") (js2r--create-bracketed-whitespace-traverser js2r-contract-object (js2r--ensure-just-one-space) (js2r--looking-at-object-start) (js2r--goto-closest-object-start) ",") (defun js2r--looking-at-array-start () (looking-at "\\[")) (defun js2r--goto-closest-array-start () (while (not (js2r--looking-at-array-start)) (if (eq (car (syntax-ppss)) 0) (error "Cursor is not on an array") (goto-char (nth 1 (syntax-ppss)))))) (js2r--create-bracketed-whitespace-traverser js2r-expand-array (js2r--ensure-newline) (js2r--looking-at-array-start) (js2r--goto-closest-array-start) ",") (js2r--create-bracketed-whitespace-traverser js2r-contract-array (js2r--ensure-just-one-space) (js2r--looking-at-array-start) (js2r--goto-closest-array-start) ",") ;; (defun js2r--looking-at-function-start () ;; (or ;; (and (looking-at "{") ;; (looking-back ;; ;; This horrible-looking regexp is actually pretty simple. It ;; ;; matches "function ()" ;; ;; allowing for whitespace. TODO: support Unicode in function and ;; ;; parameter names. ;; (concat "function[\s\n]*" ;; "\\\([a-zA-Z_$][a-zA-Z_$0-9]*[\s\n]*\\\)?" ;; "\(\\\([a-zA-Z_$][a-zA-Z_$0-9]*" ;; "[\s\n]*,[\s\n]*\\\)*[\s\n]*" ;; "\\\([a-zA-Z_$][a-zA-Z_$0-9]*[\s\n]*\\\)*" ;; "[\s\n]*\)[\s\n]*"))) ;; ;; arrow functions ;; (and (looking-at "{") ;; (looking-back "=>[\s\n]*") ;; (not (js2r--point-inside-string-p))))) (defun js2r--looking-at-function-start () "Return non-nil if the point is at the start of a function body." (let* ((node (js2-node-at-point)) (parent (js2-node-parent node))) (and (looking-at "{") (js2-block-node-p node) (js2-function-node-p parent)))) (defun js2r--goto-closest-function-start () (while (not (js2r--looking-at-function-start)) (if (eq (car (syntax-ppss)) 0) (error "Cursor is not on a function body") (goto-char (nth 1 (syntax-ppss)))))) (js2r--create-bracketed-whitespace-traverser js2r-expand-function (js2r--ensure-newline) (js2r--looking-at-function-start) (js2r--goto-closest-function-start) ";") ;; TODO: It'd be great if js2r-contract-function could recognize ;; newlines that are implied statement terminators and insert ;; semicolons correctly, but that would probably mean not using the ;; same macro as the other "contract" function definitions. (js2r--create-bracketed-whitespace-traverser js2r-contract-function (js2r--ensure-just-one-space) (js2r--looking-at-function-start) (js2r--goto-closest-function-start) ";") (defun js2r--looking-at-call-start () (looking-at "(")) (defun js2r--goto-closest-call-start () (while (not (js2r--looking-at-call-start)) (if (eq (car (syntax-ppss)) 0) (error "Cursor is not on a call") (goto-char (nth 1 (syntax-ppss)))))) (js2r--create-bracketed-whitespace-traverser js2r-expand-call-args (js2r--ensure-newline) (js2r--looking-at-call-start) (js2r--goto-closest-call-start) ",") (js2r--create-bracketed-whitespace-traverser js2r-contract-call-args (js2r--ensure-just-one-space) (js2r--looking-at-call-start) (js2r--goto-closest-call-start) ",") (defun js2r--expand-contract-node-at-point (&optional is-contract) "Expand or contract bracketed list according to node type in point. Currently working on array, object, function and call args node types. With argument, contract closest expression, otherwise expand." (let ((pos (point)) (array-start (point-max)) (object-start (point-max)) (function-start (point-max)) (call-start (point-max))) (save-excursion (ignore-errors (js2r--goto-closest-array-start) (setq array-start (- pos (point))))) (save-excursion (ignore-errors (js2r--goto-closest-object-start) (setq object-start (- pos (point))))) (save-excursion (ignore-errors (js2r--goto-closest-function-start) (setq function-start (- pos (point))))) (save-excursion (ignore-errors (js2r--goto-closest-call-start) (setq call-start (- pos (point))))) (setq pos (-min (list array-start object-start function-start call-start))) (when (= pos array-start) (if is-contract (js2r-contract-array) (js2r-expand-array))) (when (= pos object-start) (if is-contract (js2r-contract-object) (js2r-expand-object))) (when (= pos function-start) (if is-contract (js2r-contract-function) (js2r-expand-function))) (when (= pos call-start) (if is-contract (js2r-contract-call-args) (js2r-expand-call-args))))) (defun js2r-expand-node-at-point () "Expand bracketed list according to node type at point." (interactive) (js2r--expand-contract-node-at-point)) (defun js2r-contract-node-at-point () "Contract bracketed list according to node type at point." (interactive) (js2r--expand-contract-node-at-point t)) (provide 'js2r-formatting) ;;; js2-formatting.el ends here