@@ -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)
@@ -1870,10 +1876,119 @@ With universal argument \\[universal-argument], fully unwinds thread."
18701876 (interactive )
18711877 (clojure-ts-unwind '(4 )))
18721878
1879+ (defun clojure-ts--remove-superfluous-parens ()
1880+ " Remove extra parens from a form."
1881+ (when-let* ((node (treesit-thing-at-point 'sexp 'nested ))
1882+ ((clojure-ts--list-node-p node))
1883+ ((= 1 (treesit-node-child-count node t ))))
1884+ (let ((delete-pair-blink-delay 0 ))
1885+ (delete-pair ))))
1886+
1887+ (defun clojure-ts--thread-first ()
1888+ " Thread a sexp using ->."
1889+ (save-excursion
1890+ (clojure-ts--skip-first-child (clojure-ts--threading-sexp-node))
1891+ (down-list )
1892+ (treesit-beginning-of-thing 'sexp -1 )
1893+ (let ((contents (clojure-ts--delete-and-extract-sexp)))
1894+ (delete-char -1 )
1895+ (when (looking-at-p " *\n " )
1896+ (join-line 'following ))
1897+ (backward-up-list )
1898+ (insert contents " \n " )
1899+ (clojure-ts--remove-superfluous-parens))))
1900+
1901+ (defun clojure-ts--thread-last ()
1902+ " Thread a sexp using ->>."
1903+ (save-excursion
1904+ (clojure-ts--skip-first-child (clojure-ts--threading-sexp-node))
1905+ (treesit-end-of-thing 'sexp )
1906+ (down-list -1 )
1907+ (treesit-beginning-of-thing 'sexp )
1908+ (let ((contents (clojure-ts--delete-and-extract-sexp)))
1909+ (delete-char -1 )
1910+ (treesit-end-of-thing 'sexp -1 'restricted )
1911+ (when (looking-at-p " *\n " )
1912+ (join-line 'following ))
1913+ (backward-up-list )
1914+ (insert contents " \n " )
1915+ (clojure-ts--remove-superfluous-parens))))
1916+
1917+ (defun clojure-ts--threadable-p (node )
1918+ " Return non-nil if expression NODE can be threaded.
1919+
1920+ First argument after threading symbol itself should be a list and it
1921+ should have more than one named child."
1922+ (let ((second-child (treesit-node-child node 1 t )))
1923+ (and (clojure-ts--list-node-p second-child)
1924+ (> (treesit-node-child-count second-child t ) 1 ))))
1925+
1926+ (defun clojure-ts-thread ()
1927+ " Thread by one more level an existing threading macro."
1928+ (interactive )
1929+ (when-let* ((threading-sexp (clojure-ts--threading-sexp-node))
1930+ ((clojure-ts--threadable-p threading-sexp))
1931+ (sym (thread-first threading-sexp
1932+ (treesit-node-child 0 t )
1933+ (clojure-ts--named-node-text))))
1934+ (let ((beg (thread-first threading-sexp
1935+ (treesit-node-start)
1936+ (copy-marker )))
1937+ (end (thread-first threading-sexp
1938+ (treesit-node-end)
1939+ (copy-marker ))))
1940+ (cond
1941+ ((string-match-p (rx bol (* " some" ) " ->" eol) sym)
1942+ (clojure-ts--thread-first))
1943+ ((string-match-p (rx bol (* " some" ) " ->>" eol) sym)
1944+ (clojure-ts--thread-last)))
1945+ (indent-region beg end)
1946+ (delete-trailing-whitespace beg end))
1947+ t ))
1948+
1949+ (defun clojure-ts--thread-all (first-or-last-thread but-last )
1950+ " Fully thread the form at point.
1951+
1952+ FIRST-OR-LAST-THREAD is either \" ->\" or \" ->>\" .
1953+
1954+ When BUT-LAST is non-nil, the last expression is not threaded. Default
1955+ value is `clojure-ts-thread-all-but-last.' "
1956+ (if-let* ((list-at-point (treesit-thing-at-point 'list 'nested )))
1957+ (save-excursion
1958+ (goto-char (treesit-node-start list-at-point))
1959+ (insert-parentheses 1 )
1960+ (insert first-or-last-thread)
1961+ (while (clojure-ts-thread))
1962+ (when (or but-last clojure-ts-thread-all-but-last)
1963+ (clojure-ts-unwind)))
1964+ (user-error " No list to thread at point" )))
1965+
1966+ (defun clojure-ts-thread-first-all (but-last )
1967+ " Fully thread the form at point using ->.
1968+
1969+ When BUT-LAST is non-nil, the last expression is not threaded. Default
1970+ value is `clojure-ts-thread-all-but-last' ."
1971+ (interactive " P" )
1972+ (clojure-ts--thread-all " -> " but-last))
1973+
1974+ (defun clojure-ts-thread-last-all (but-last )
1975+ " Fully thread the form at point using ->>.
1976+
1977+ When BUT-LAST is non-nil, the last expression is not threaded. Default
1978+ value is `clojure-ts-thread-all-but-last' ."
1979+ (interactive " P" )
1980+ (clojure-ts--thread-all " ->> " but-last))
1981+
18731982(defvar clojure-ts-refactor-map
18741983 (let ((map (make-sparse-keymap )))
1984+ (keymap-set map " C-t" #'clojure-ts-thread )
1985+ (keymap-set map " t" #'clojure-ts-thread )
18751986 (keymap-set map " C-u" #'clojure-ts-unwind )
18761987 (keymap-set map " u" #'clojure-ts-unwind )
1988+ (keymap-set map " C-f" #'clojure-ts-thread-first-all )
1989+ (keymap-set map " f" #'clojure-ts-thread-first-all )
1990+ (keymap-set map " C-l" #'clojure-ts-thread-last-all )
1991+ (keymap-set map " l" #'clojure-ts-thread-last-all )
18771992 map)
18781993 " Keymap for `clojure-ts-mode' refactoring commands." )
18791994
@@ -1886,6 +2001,10 @@ With universal argument \\[universal-argument], fully unwinds thread."
18862001 '(" Clojure"
18872002 [" Align expression" clojure-ts-align]
18882003 (" Refactor -> and ->>"
2004+ [" Thread once more" clojure-ts-thread]
2005+ [" Fully thread a form with ->" clojure-ts-thread-first-all]
2006+ [" Fully thread a form with ->>" clojure-ts-thread-last-all]
2007+ " --"
18892008 [" Unwind once" clojure-ts-unwind]
18902009 [" Fully unwind a threading macro" clojure-ts-unwind-all])))
18912010 map)
0 commit comments