@@ -150,6 +150,16 @@ three or more semicolons will be treated as outline headings. If set to
150150 :type 'string
151151 :package-version '(clojure-ts-mode . " 0.4" ))
152152
153+ (defcustom clojure-ts-thread-all-but-last nil
154+ " Non-nil means do not thread the last expression.
155+
156+ This means that `clojure-ts-thread-first-all' and
157+ `clojure-ts-thread-last-all' not thread the deepest sexp inside the
158+ current sexp."
159+ :package-version '(clojure-ts-mode . " 0.4.0" )
160+ :safe #'booleanp
161+ :type 'boolean )
162+
153163(defcustom clojure-ts-align-reader-conditionals nil
154164 " Whether to align reader conditionals, as if they were maps."
155165 :package-version '(clojure-ts-mode . " 0.4" )
@@ -1291,9 +1301,9 @@ according to the rule. If NODE is nil, use next node after BOL."
12911301 (clojure-ts--anon-fn-node-p parent))
12921302 ; ; Can the following two clauses be replaced by checking indexes?
12931303 ; ; Does the second child exist, and is it not equal to the current node?
1294- (treesit- node-child parent 1 t )
1295- (not (treesit-node-eq (treesit- node-child parent 1 t ) node))
1296- (let ((first-child (treesit- node-child parent 0 t )))
1304+ (clojure-ts-- node-child-skip-metadata parent 1 )
1305+ (not (treesit-node-eq (clojure-ts-- node-child-skip-metadata parent 1 ) node))
1306+ (let ((first-child (clojure-ts-- node-child-skip-metadata parent 0 )))
12971307 (or (clojure-ts--symbol-node-p first-child)
12981308 (clojure-ts--keyword-node-p first-child)
12991309 (clojure-ts--var-node-p first-child)))))
@@ -1381,17 +1391,11 @@ if NODE has metadata and its parent has type NODE-TYPE."
13811391 (treesit-node-type
13821392 (clojure-ts--node-with-metadata-parent node)))))
13831393
1384- (defun clojure-ts--anchor-nth-sibling (n &optional named )
1385- " Return the start of the Nth child of PARENT.
1386-
1387- NAMED non-nil means count only named nodes.
1388-
1389- NOTE: This is a replacement for built-in `nth-sibling' anchor preset,
1390- which doesn't work properly for named nodes (see the bug
1391- https://debbugs.gnu.org/cgi/bugreport.cgi?bug=78065)"
1394+ (defun clojure-ts--anchor-nth-sibling (n )
1395+ " Return the start of the Nth child of PARENT skipping metadata."
13921396 (lambda (_n parent &rest _ )
13931397 (treesit-node-start
1394- (treesit- node-child parent n named ))))
1398+ (clojure-ts-- node-child-skip-metadata parent n))))
13951399
13961400(defun clojure-ts--semantic-indent-rules ()
13971401 " Return a list of indentation rules for `treesit-simple-indent-rules' ."
@@ -1423,7 +1427,7 @@ https://debbugs.gnu.org/cgi/bugreport.cgi?bug=78065)"
14231427 ; ; https://guide.clojure.style/#threading-macros-alignment
14241428 (clojure-ts--match-threading-macro-arg prev-sibling 0 )
14251429 ; ; https://guide.clojure.style/#vertically-align-fn-args
1426- (clojure-ts--match-function-call-arg ,(clojure-ts--anchor-nth-sibling 1 t ) 0 )
1430+ (clojure-ts--match-function-call-arg ,(clojure-ts--anchor-nth-sibling 1 ) 0 )
14271431 ; ; https://guide.clojure.style/#one-space-indent
14281432 ((parent-is " list_lit" ) parent 1 ))))
14291433
@@ -1539,8 +1543,8 @@ BOUND bounds the whitespace search."
15391543 (and (not (treesit-node-child-by-field-name cur-sexp " value" ))
15401544 (string-empty-p (clojure-ts--named-node-text cur-sexp))))
15411545 (treesit-end-of-thing 'sexp 2 'restricted )
1542- (treesit-end-of-thing 'sexp 1 'restrict ))
1543- (when (looking-at " ," )
1546+ (treesit-end-of-thing 'sexp 1 'restricted ))
1547+ (when (looking-at-p " ," )
15441548 (forward-char ))
15451549 ; ; Move past any whitespace or comment.
15461550 (search-forward-regexp regex bound)
@@ -1744,7 +1748,7 @@ Forms between BEG and END are aligned according to
17441748 (goto-char first-child-start)
17451749 (treesit-beginning-of-thing 'sexp -1 )
17461750 (let ((contents (clojure-ts--delete-and-extract-sexp)))
1747- (when (looking-at " *\n " )
1751+ (when (looking-at-p " *\n " )
17481752 (join-line 'following ))
17491753 (just-one-space )
17501754 (goto-char first-child-start)
@@ -1753,9 +1757,11 @@ Forms between BEG and END are aligned according to
17531757 (clojure-ts--ensure-parens-around-function-name)
17541758 (down-list )
17551759 (forward-sexp )
1756- (insert " " contents)
1757- (when multiline-p
1758- (insert " \n " )))))))
1760+ (cond
1761+ ((and multiline-p (looking-at-p " *\n " ))
1762+ (insert " \n " contents))
1763+ (multiline-p (insert " " contents " \n " ))
1764+ (t (insert " " contents))))))))
17591765
17601766(defun clojure-ts--unwind-thread-last ()
17611767 " Unwind a thread last macro once."
@@ -1768,7 +1774,7 @@ Forms between BEG and END are aligned according to
17681774 (goto-char first-child-start)
17691775 (treesit-beginning-of-thing 'sexp -1 )
17701776 (let ((contents (clojure-ts--delete-and-extract-sexp)))
1771- (when (looking-at " *\n " )
1777+ (when (looking-at-p " *\n " )
17721778 (join-line 'following ))
17731779 (just-one-space )
17741780 (goto-char first-child-start)
@@ -1804,10 +1810,16 @@ Forms between BEG and END are aligned according to
18041810
18051811(defun clojure-ts--pop-out-of-threading ()
18061812 " Raise a sexp up a level to unwind a threading form."
1807- (let ((threading-sexp (clojure-ts--threading-sexp-node)))
1813+ (let* ((threading-sexp (clojure-ts--threading-sexp-node))
1814+ (beg (thread-first threading-sexp
1815+ (treesit-node-child 0 t )
1816+ (treesit-node-start))))
18081817 (save-excursion
18091818 (clojure-ts--skip-first-child threading-sexp)
1810- (raise-sexp ))))
1819+ (delete-region beg (point ))
1820+ ; ; `raise-sexp' doesn't work properly for function literals (it loses one
1821+ ; ; of the parenthesis). Seems like an Emacs' bug.
1822+ (delete-pair ))))
18111823
18121824(defun clojure-ts--fix-sexp-whitespace ()
18131825 " Fix whitespace after unwinding a threading form."
@@ -1870,10 +1882,125 @@ With universal argument \\[universal-argument], fully unwinds thread."
18701882 (interactive )
18711883 (clojure-ts-unwind '(4 )))
18721884
1885+ (defun clojure-ts--remove-superfluous-parens ()
1886+ " Remove extra parens from a form."
1887+ (when-let* ((node (treesit-thing-at-point 'sexp 'nested ))
1888+ ((clojure-ts--list-node-p node))
1889+ ((= 1 (treesit-node-child-count node t ))))
1890+ (let ((delete-pair-blink-delay 0 ))
1891+ (delete-pair ))))
1892+
1893+ (defun clojure-ts--thread-first ()
1894+ " Thread a sexp using ->."
1895+ (save-excursion
1896+ (clojure-ts--skip-first-child (clojure-ts--threading-sexp-node))
1897+ (down-list )
1898+ (treesit-beginning-of-thing 'sexp -1 )
1899+ (let ((contents (clojure-ts--delete-and-extract-sexp)))
1900+ (delete-char -1 )
1901+ (when (looking-at-p " *\n " )
1902+ (join-line 'following ))
1903+ (backward-up-list )
1904+ (insert contents " \n " )
1905+ (clojure-ts--remove-superfluous-parens))))
1906+
1907+ (defun clojure-ts--thread-last ()
1908+ " Thread a sexp using ->>."
1909+ (save-excursion
1910+ (clojure-ts--skip-first-child (clojure-ts--threading-sexp-node))
1911+ (treesit-end-of-thing 'sexp )
1912+ (down-list -1 )
1913+ (treesit-beginning-of-thing 'sexp )
1914+ (let ((contents (clojure-ts--delete-and-extract-sexp)))
1915+ (delete-char -1 )
1916+ (treesit-end-of-thing 'sexp -1 'restricted )
1917+ (when (looking-at-p " *\n " )
1918+ (join-line 'following ))
1919+ (backward-up-list )
1920+ (insert contents " \n " )
1921+ (clojure-ts--remove-superfluous-parens))))
1922+
1923+ (defun clojure-ts--threadable-p (node )
1924+ " Return non-nil if expression NODE can be threaded.
1925+
1926+ First argument after threading symbol itself should be a list and it
1927+ should have more than one named child."
1928+ (let ((second-child (treesit-node-child node 1 t )))
1929+ (and (clojure-ts--list-node-p second-child)
1930+ (> (treesit-node-child-count second-child t ) 1 ))))
1931+
1932+ (defun clojure-ts-thread (&optional called-by-user-p )
1933+ " Thread by one more level an existing threading macro.
1934+
1935+ If CALLED-BY-USER-P is non-nil (which is always TRUE when called
1936+ interactively), the function signals a `user-error' if threading form
1937+ cannot be found."
1938+ (interactive " p" )
1939+ (if-let* ((threading-sexp (clojure-ts--threading-sexp-node))
1940+ ((clojure-ts--threadable-p threading-sexp))
1941+ (sym (thread-first threading-sexp
1942+ (treesit-node-child 0 t )
1943+ (clojure-ts--named-node-text))))
1944+ (let ((beg (thread-first threading-sexp
1945+ (treesit-node-start)
1946+ (copy-marker )))
1947+ (end (thread-first threading-sexp
1948+ (treesit-node-end)
1949+ (copy-marker ))))
1950+ (cond
1951+ ((string-match-p (rx bol (* " some" ) " ->" eol) sym)
1952+ (clojure-ts--thread-first))
1953+ ((string-match-p (rx bol (* " some" ) " ->>" eol) sym)
1954+ (clojure-ts--thread-last)))
1955+ (indent-region beg end)
1956+ (delete-trailing-whitespace beg end)
1957+ t )
1958+ (when called-by-user-p
1959+ (user-error " No threading form at point" ))))
1960+
1961+ (defun clojure-ts--thread-all (first-or-last-thread but-last )
1962+ " Fully thread the form at point.
1963+
1964+ FIRST-OR-LAST-THREAD is either \" ->\" or \" ->>\" .
1965+
1966+ When BUT-LAST is non-nil, the last expression is not threaded. Default
1967+ value is `clojure-ts-thread-all-but-last.' "
1968+ (if-let* ((list-at-point (treesit-thing-at-point 'list 'nested )))
1969+ (save-excursion
1970+ (goto-char (treesit-node-start list-at-point))
1971+ (insert-parentheses 1 )
1972+ (insert first-or-last-thread)
1973+ (while (clojure-ts-thread))
1974+ (when (or but-last clojure-ts-thread-all-but-last)
1975+ (clojure-ts-unwind)))
1976+ (user-error " No list to thread at point" )))
1977+
1978+ (defun clojure-ts-thread-first-all (but-last )
1979+ " Fully thread the form at point using ->.
1980+
1981+ When BUT-LAST is non-nil, the last expression is not threaded. Default
1982+ value is `clojure-ts-thread-all-but-last' ."
1983+ (interactive " P" )
1984+ (clojure-ts--thread-all " -> " but-last))
1985+
1986+ (defun clojure-ts-thread-last-all (but-last )
1987+ " Fully thread the form at point using ->>.
1988+
1989+ When BUT-LAST is non-nil, the last expression is not threaded. Default
1990+ value is `clojure-ts-thread-all-but-last' ."
1991+ (interactive " P" )
1992+ (clojure-ts--thread-all " ->> " but-last))
1993+
18731994(defvar clojure-ts-refactor-map
18741995 (let ((map (make-sparse-keymap )))
1996+ (keymap-set map " C-t" #'clojure-ts-thread )
1997+ (keymap-set map " t" #'clojure-ts-thread )
18751998 (keymap-set map " C-u" #'clojure-ts-unwind )
18761999 (keymap-set map " u" #'clojure-ts-unwind )
2000+ (keymap-set map " C-f" #'clojure-ts-thread-first-all )
2001+ (keymap-set map " f" #'clojure-ts-thread-first-all )
2002+ (keymap-set map " C-l" #'clojure-ts-thread-last-all )
2003+ (keymap-set map " l" #'clojure-ts-thread-last-all )
18772004 map)
18782005 " Keymap for `clojure-ts-mode' refactoring commands." )
18792006
@@ -1886,6 +2013,10 @@ With universal argument \\[universal-argument], fully unwinds thread."
18862013 '(" Clojure"
18872014 [" Align expression" clojure-ts-align]
18882015 (" Refactor -> and ->>"
2016+ [" Thread once more" clojure-ts-thread]
2017+ [" Fully thread a form with ->" clojure-ts-thread-first-all]
2018+ [" Fully thread a form with ->>" clojure-ts-thread-last-all]
2019+ " --"
18892020 [" Unwind once" clojure-ts-unwind]
18902021 [" Fully unwind a threading macro" clojure-ts-unwind-all])))
18912022 map)
0 commit comments