From 7d6bdd7942dfc9fae02c6843bfd26b71f39b0125 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Fri, 21 Apr 2023 01:12:56 +0100 Subject: [PATCH 01/88] Begin rewrite of indentation code The objectives are: 1. Simplify the indentation code; previous implementation has become so complex it is impossible to maintain, 2. Significantly improve performance; previous indentation code was painfully slow, (see issue #6) 3. Maximum configurability; should be configured similarly to cljfmt and make previously impossible things possible (e.g. issue #21). As of this commit, objectives 1 and 2 have been met, but work on objective 3 has not yet begun. There will continue to be further improvements, particularly around performance and the "what if syntax highlighting is disabled?" scenario. These changes will unfortunately be backwards incompatible, but hopefully the improved performance and API will make up for it. --- indent/clojure.vim | 532 ++++++++++----------------------------------- 1 file changed, 119 insertions(+), 413 deletions(-) diff --git a/indent/clojure.vim b/indent/clojure.vim index dcbef55..e80da79 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -1,19 +1,19 @@ " Vim indent file -" Language: Clojure -" Maintainer: Alex Vear -" Former Maintainers: Sung Pae -" Meikel Brandmeyer -" URL: https://github.com/clojure-vim/clojure.vim -" License: Vim (see :h license) -" Last Change: %%RELEASE_DATE%% +" Language: Clojure +" Maintainer: Alex Vear +" Former Maintainers: Sung Pae +" Meikel Brandmeyer +" Last Change: %%RELEASE_DATE%% +" License: Vim (see :h license) +" Repository: https://github.com/clojure-vim/clojure.vim if exists("b:did_indent") finish endif let b:did_indent = 1 -let s:save_cpo = &cpo -set cpo&vim +let s:save_cpo = &cpoptions +set cpoptions&vim let b:undo_indent = 'setlocal autoindent< smartindent< expandtab< softtabstop< shiftwidth< indentexpr< indentkeys<' @@ -21,418 +21,124 @@ setlocal noautoindent nosmartindent setlocal softtabstop=2 shiftwidth=2 expandtab setlocal indentkeys=!,o,O -if exists("*searchpairpos") - - if !exists('g:clojure_maxlines') - let g:clojure_maxlines = 300 - endif - - if !exists('g:clojure_fuzzy_indent') - let g:clojure_fuzzy_indent = 1 - endif - - if !exists('g:clojure_fuzzy_indent_patterns') - let g:clojure_fuzzy_indent_patterns = ['^with', '^def', '^let'] - endif - - if !exists('g:clojure_fuzzy_indent_blacklist') - let g:clojure_fuzzy_indent_blacklist = ['-fn$', '\v^with-%(meta|out-str|loading-context)$'] - endif - - if !exists('g:clojure_special_indent_words') - let g:clojure_special_indent_words = 'deftype,defrecord,reify,proxy,extend-type,extend-protocol,letfn' - endif - - if !exists('g:clojure_align_multiline_strings') - let g:clojure_align_multiline_strings = 0 - endif - - if !exists('g:clojure_align_subforms') - let g:clojure_align_subforms = 0 - endif - - if !exists('g:clojure_cljfmt_compat') - let g:clojure_cljfmt_compat = 0 +" TODO: ignore 'lisp' and 'lispwords' options (actually, turn them off?) + +" TODO: Optional Vim9script implementations of hotspot/bottleneck functions. +" FIXME: fallback case when syntax highlighting is disabled. + +" Function to get the indentation of a line. +" function! s:GetIndent(lnum) +" let l = getline(a:lnum) +" return len(l) - len(trim(l, " \t", 1)) +" endfunction + +function! s:GetSynIdName(line, col) + return synIDattr(synID(a:line, a:col, 0), 'name') +endfunction + +function! s:SyntaxMatch(pattern, line, col) + return s:GetSynIdName(a:line, a:col) =~? a:pattern +endfunction + +function! s:IgnoredRegion() + return s:SyntaxMatch('\vstring|regex|comment|character', line('.'), col('.')) +endfunction + +function! s:NotAStringDelimiter() + return ! s:SyntaxMatch('stringdelimiter', line('.'), col('.')) +endfunction + +function! s:IsInString() + return s:SyntaxMatch('string', line('.'), col('.')) +endfunction + +function! s:NotARegexpDelimiter() + return ! s:SyntaxMatch('regexpdelimiter', line('.'), col('.')) +endfunction + +function! s:IsInRegex() + return s:SyntaxMatch('regex', line('.'), col('.')) +endfunction + +function! s:Conf(opt, default) + return get(b:, a:opt, get(g:, a:opt, a:default)) +endfunction + +function! s:ShouldAlignMultiLineStrings() + return s:Conf('clojure_align_multiline_strings', 0) +endfunction + +function! s:ClosestMatch(match1, match2) + let [_, coord1] = a:match1 + let [_, coord2] = a:match2 + if coord1[0] < coord2[0] + return a:match2 + elseif coord1[0] == coord2[0] && coord1[1] < coord2[1] + return a:match2 + else + return a:match1 endif - - function! s:syn_id_name() - return synIDattr(synID(line("."), col("."), 0), "name") - endfunction - - function! s:ignored_region() - return s:syn_id_name() =~? '\vstring|regex|comment|character' - endfunction - - function! s:current_char() - return getline('.')[col('.')-1] - endfunction - - function! s:current_word() - return getline('.')[col('.')-1 : searchpos('\v>', 'n', line('.'))[1]-2] - endfunction - - function! s:is_paren() - return s:current_char() =~# '\v[\(\)\[\]\{\}]' && !s:ignored_region() - endfunction - - " Returns 1 if string matches a pattern in 'patterns', which should be - " a list of patterns. - function! s:match_one(patterns, string) - for pat in a:patterns - if a:string =~# pat | return 1 | endif - endfor - endfunction - - function! s:match_pairs(open, close, stopat) - " Stop only on vector and map [ resp. {. Ignore the ones in strings and - " comments. - if a:stopat == 0 && g:clojure_maxlines > 0 - let stopat = max([line(".") - g:clojure_maxlines, 0]) - else - let stopat = a:stopat - endif - - let pos = searchpairpos(a:open, '', a:close, 'bWn', "!s:is_paren()", stopat) - return [pos[0], col(pos)] - endfunction - - function! s:clojure_check_for_string_worker() - " Check whether there is the last character of the previous line is - " highlighted as a string. If so, we check whether it's a ". In this - " case we have to check also the previous character. The " might be the - " closing one. In case the we are still in the string, we search for the - " opening ". If this is not found we take the indent of the line. - let nb = prevnonblank(v:lnum - 1) - - if nb == 0 - return -1 - endif - - call cursor(nb, 0) - call cursor(0, col("$") - 1) - if s:syn_id_name() !~? "string" - return -1 - endif - - " This will not work for a " in the first column... - if s:current_char() == '"' - call cursor(0, col("$") - 2) - if s:syn_id_name() !~? "string" - return -1 - endif - if s:current_char() != '\' - return -1 - endif - call cursor(0, col("$") - 1) - endif - - let p = searchpos('\(^\|[^\\]\)\zs"', 'bW') - - if p != [0, 0] - return p[1] - 1 - endif - - return indent(".") - endfunction - - function! s:check_for_string() - let pos = getpos('.') - try - let val = s:clojure_check_for_string_worker() - finally - call setpos('.', pos) - endtry - return val - endfunction - - function! s:strip_namespace_and_macro_chars(word) - return substitute(a:word, "\\v%(.*/|[#'`~@^,]*)(.*)", '\1', '') - endfunction - - function! s:clojure_is_method_special_case_worker(position) - " Find the next enclosing form. - call search('\S', 'Wb') - - " Special case: we are at a '(('. - if s:current_char() == '(' - return 0 - endif - call cursor(a:position) - - let next_paren = s:match_pairs('(', ')', 0) - - " Special case: we are now at toplevel. - if next_paren == [0, 0] - return 0 - endif - call cursor(next_paren) - - call search('\S', 'W') - let w = s:strip_namespace_and_macro_chars(s:current_word()) - - if g:clojure_special_indent_words =~# '\V\<' . w . '\>' - - " `letfn` is a special-special-case. - if w ==# 'letfn' - " Earlier code left the cursor at: - " (letfn [...] ...) - " ^ - - " Search and get coordinates of first `[` - " (letfn [...] ...) - " ^ - call search('\[', 'W') - let pos = getcurpos() - let letfn_bracket = [pos[1], pos[2]] - - " Move cursor to start of the form this function was - " initially called on. Grab the coordinates of the - " closest outer `[`. - call cursor(a:position) - let outer_bracket = s:match_pairs('\[', '\]', 0) - - " If the located square brackets are not the same, - " don't use special-case formatting. - if outer_bracket != letfn_bracket - return 0 - endif - endif - - return 1 - endif - +endfunction + +" Only need to search up. Never down. +function! s:GetClojureIndent() + let lnum = v:lnum + + " Move cursor to the first column of the line we want to indent. + cursor(lnum, 0) + + let matches = [ + \ ['lst', searchpairpos( '(', '', ')', 'bznW', function('IgnoredRegion'))], + \ ['vec', searchpairpos('\[', '', '\]', 'bznW', function('IgnoredRegion'))], + \ ['map', searchpairpos( '{', '', '}', 'bznW', function('IgnoredRegion'))], + \ ['reg', s:IsInRegex() ? searchpairpos('#\zs"', '', '"', 'bznW', function('NotARegexpDelimiter')) : [0, 0]], + \ ['str', s:IsInString() ? searchpairpos('"', '', '"', 'bznW', function('NotAStringDelimiter')) : [0, 0]] + \ ] + echom 'Matches' matches + + " Find closest matching higher form. + let [formtype, coord] = reduce(matches, function('ClosestMatch'), ['top', [0, 0]]) + echom 'Match' formtype coord + + if formtype == 'top' + " At the top level, no indent. + echom 'At the top level!' return 0 - endfunction - - function! s:is_method_special_case(position) - let pos = getpos('.') - try - let val = s:clojure_is_method_special_case_worker(a:position) - finally - call setpos('.', pos) - endtry - return val - endfunction - - " Check if form is a reader conditional, that is, it is prefixed by #? - " or #?@ - function! s:is_reader_conditional_special_case(position) - return getline(a:position[0])[a:position[1] - 3 : a:position[1] - 2] == "#?" - \|| getline(a:position[0])[a:position[1] - 4 : a:position[1] - 2] == "#?@" - endfunction - - " Returns 1 for opening brackets, -1 for _anything else_. - function! s:bracket_type(char) - return stridx('([{', a:char) > -1 ? 1 : -1 - endfunction - - " Returns: [opening-bracket-lnum, indent] - function! s:clojure_indent_pos() - " Get rid of special case. - if line(".") == 1 - return [0, 0] - endif - - " We have to apply some heuristics here to figure out, whether to use - " normal lisp indenting or not. - let i = s:check_for_string() - if i > -1 - return [0, i + !!g:clojure_align_multiline_strings] - endif - - call cursor(0, 1) - - " Find the next enclosing [ or {. We can limit the second search - " to the line, where the [ was found. If no [ was there this is - " zero and we search for an enclosing {. - let paren = s:match_pairs('(', ')', 0) - let bracket = s:match_pairs('\[', '\]', paren[0]) - let curly = s:match_pairs('{', '}', bracket[0]) - - " In case the curly brace is on a line later then the [ or - in - " case they are on the same line - in a higher column, we take the - " curly indent. - if curly[0] > bracket[0] || curly[1] > bracket[1] - if curly[0] > paren[0] || curly[1] > paren[1] - return curly - endif - endif - - " If the curly was not chosen, we take the bracket indent - if - " there was one. - if bracket[0] > paren[0] || bracket[1] > paren[1] - return bracket - endif - - " There are neither { nor [ nor (, ie. we are at the toplevel. - if paren == [0, 0] - return paren - endif - - " Now we have to reimplement lispindent. This is surprisingly easy, as - " soon as one has access to syntax items. - " - " - Check whether we are in a special position after a word in - " g:clojure_special_indent_words. These are special cases. - " - Get the next keyword after the (. - " - If its first character is also a (, we have another sexp and align - " one column to the right of the unmatched (. - " - In case it is in lispwords, we indent the next line to the column of - " the ( + sw. - " - If not, we check whether it is last word in the line. In that case - " we again use ( + sw for indent. - " - In any other case we use the column of the end of the word + 2. - call cursor(paren) - - if s:is_method_special_case(paren) - return [paren[0], paren[1] + &shiftwidth - 1] - endif - - if s:is_reader_conditional_special_case(paren) - return paren - endif - - " In case we are at the last character, we use the paren position. - if col("$") - 1 == paren[1] - return paren - endif - - " In case after the paren is a whitespace, we search for the next word. - call cursor(0, col('.') + 1) - if s:current_char() == ' ' - call search('\v\S', 'W') - endif - - " If we moved to another line, there is no word after the (. We - " use the ( position for indent. - if line(".") > paren[0] - return paren - endif - - " We still have to check, whether the keyword starts with a (, [ or {. - " In that case we use the ( position for indent. - let w = s:current_word() - if s:bracket_type(w[0]) == 1 - return paren - endif - - " If the keyword begins with #, check if it is an anonymous - " function or set, in which case we indent by the shiftwidth - " (minus one if g:clojure_align_subforms = 1), or if it is - " ignored, in which case we use the ( position for indent. - if w[0] == "#" - " TODO: Handle #=() and other rare reader invocations? - if w[1] == '(' || w[1] == '{' - return [paren[0], paren[1] + (g:clojure_align_subforms ? 0 : &shiftwidth - 1)] - elseif w[1] == '_' - return paren - elseif w[1] == "'" && g:clojure_cljfmt_compat - return paren - endif - endif - - " Paren indent for keywords, symbols and derefs - if g:clojure_cljfmt_compat && w[0] =~# "[:@']" - return paren - endif - - " Test words without namespace qualifiers and leading reader macro - " metacharacters. - " - " e.g. clojure.core/defn and #'defn should both indent like defn. - let ww = s:strip_namespace_and_macro_chars(w) - - if &lispwords =~# '\V\<' . ww . '\>' - return [paren[0], paren[1] + &shiftwidth - 1] - endif - - if g:clojure_fuzzy_indent - \ && !s:match_one(g:clojure_fuzzy_indent_blacklist, ww) - \ && s:match_one(g:clojure_fuzzy_indent_patterns, ww) - return [paren[0], paren[1] + &shiftwidth - 1] - endif - - call search('\v\_s', 'cW') - call search('\v\S', 'W') - if paren[0] < line(".") - return [paren[0], paren[1] + (g:clojure_align_subforms ? 0 : &shiftwidth - 1)] - endif - - call search('\v\S', 'bW') - return [line('.'), col('.') + 1] - endfunction - - function! GetClojureIndent() - let lnum = line('.') - let orig_lnum = lnum - let orig_col = col('.') - let [opening_lnum, indent] = s:clojure_indent_pos() - - " Account for multibyte characters - if opening_lnum > 0 - let indent -= indent - virtcol([opening_lnum, indent]) - endif - - " Return if there are no previous lines to inherit from - if opening_lnum < 1 || opening_lnum >= lnum - 1 - call cursor(orig_lnum, orig_col) - return indent - endif - - let bracket_count = 0 - - " Take the indent of the first previous non-white line that is - " at the same sexp level. cf. src/misc1.c:get_lisp_indent() - while 1 - let lnum = prevnonblank(lnum - 1) - let col = 1 - - if lnum <= opening_lnum - break - endif - - call cursor(lnum, col) - - " Handle bracket counting edge case - if s:is_paren() - let bracket_count += s:bracket_type(s:current_char()) - endif - - while 1 - if search('\v[(\[{}\])]', '', lnum) < 1 - break - elseif !s:ignored_region() - let bracket_count += s:bracket_type(s:current_char()) - endif - endwhile - - if bracket_count == 0 - " Check if this is part of a multiline string - call cursor(lnum, 1) - if s:syn_id_name() !~? '\vstring|regex' - call cursor(orig_lnum, orig_col) - return indent(lnum) - endif - endif - endwhile + elseif formtype == 'lst' + echom 'Special format rules!' + " TODO + " Grab text! + echom getline(coord[0], lnum - 1) + " Begin lexing! + return coord[1] + 1 + elseif formtype == 'vec' || formtype == 'map' + " Inside a vector, map or set. + return coord[1] + elseif formtype == 'reg' + " Inside a regex. + echom 'Inside a regex!' + return coord[1] - (s:ShouldAlignMultiLineStrings() ? 0 : 2) + elseif formtype == 'str' + " Inside a string. + echom 'Inside a string!' + return coord[1] - (s:ShouldAlignMultiLineStrings() ? 0 : 1) + endif - call cursor(orig_lnum, orig_col) - return indent - endfunction + return 2 +endfunction - setlocal indentexpr=GetClojureIndent() -else +setlocal indentexpr=s:GetClojureIndent() - " In case we have searchpairpos not available we fall back to - " normal lisp indenting. - setlocal indentexpr= - setlocal lisp - let b:undo_indent .= '| setlocal lisp<' -endif +" TODO: if exists("*searchpairpos") +" In case we have searchpairpos not available we fall back to normal lisp +" indenting. +"setlocal indentexpr= +"setlocal lisp +"let b:undo_indent .= '| setlocal lisp<' -let &cpo = s:save_cpo +let &cpoptions = s:save_cpo unlet! s:save_cpo " vim:sts=8:sw=8:ts=8:noet From 5706ea300adbbde0cc64e2412ba96f7d6c5aea64 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Fri, 21 Apr 2023 02:29:59 +0100 Subject: [PATCH 02/88] Double (or better) indent performance! By utilising some ugly mutable state, this change more than doubles the performance of the indentation code. You can expect even further benefits in larger code blocks. This should completely eliminate any need for the `clojure_maxlines` configuration option. --- indent/clojure.vim | 44 ++++++++++++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/indent/clojure.vim b/indent/clojure.vim index e80da79..6fde261 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -41,7 +41,7 @@ function! s:SyntaxMatch(pattern, line, col) endfunction function! s:IgnoredRegion() - return s:SyntaxMatch('\vstring|regex|comment|character', line('.'), col('.')) + return s:SyntaxMatch('\(string\|regex\|comment\|character\)', line('.'), col('.')) endfunction function! s:NotAStringDelimiter() @@ -80,6 +80,15 @@ function! s:ClosestMatch(match1, match2) endif endfunction +" Wrapper around "searchpairpos" that will automatically set "s:best_match" to +" the closest pair match and continuously optimise the "stopline" value for +" later searches. This results in a significant performance gain by reducing +" the number of syntax lookups that need to take place. +function! s:CheckPair(name, start, end, skipfn) + let pos = searchpairpos(a:start, '', a:end, 'bznW', a:skipfn, s:best_match[1][0]) + let s:best_match = s:ClosestMatch(s:best_match, [a:name, pos]) +endfunction + " Only need to search up. Never down. function! s:GetClojureIndent() let lnum = v:lnum @@ -87,28 +96,31 @@ function! s:GetClojureIndent() " Move cursor to the first column of the line we want to indent. cursor(lnum, 0) - let matches = [ - \ ['lst', searchpairpos( '(', '', ')', 'bznW', function('IgnoredRegion'))], - \ ['vec', searchpairpos('\[', '', '\]', 'bznW', function('IgnoredRegion'))], - \ ['map', searchpairpos( '{', '', '}', 'bznW', function('IgnoredRegion'))], - \ ['reg', s:IsInRegex() ? searchpairpos('#\zs"', '', '"', 'bznW', function('NotARegexpDelimiter')) : [0, 0]], - \ ['str', s:IsInString() ? searchpairpos('"', '', '"', 'bznW', function('NotAStringDelimiter')) : [0, 0]] - \ ] - echom 'Matches' matches + let s:best_match = ['top', [0, 0]] + + call s:CheckPair('lst', '(', ')', function('IgnoredRegion')) + call s:CheckPair('vec', '\[', '\]', function('IgnoredRegion')) + call s:CheckPair('map', '{', '}', function('IgnoredRegion')) + + if s:IsInString() + call s:CheckPair('str', '"', '"', function('NotAStringDelimiter')) + elseif s:IsInRegex() + call s:CheckPair('reg', '#\zs"', '"', function('NotARegexpDelimiter')) + endif " Find closest matching higher form. - let [formtype, coord] = reduce(matches, function('ClosestMatch'), ['top', [0, 0]]) - echom 'Match' formtype coord + let [formtype, coord] = s:best_match + " echom 'Match' formtype coord if formtype == 'top' " At the top level, no indent. - echom 'At the top level!' + " echom 'At the top level!' return 0 elseif formtype == 'lst' - echom 'Special format rules!' + " echom 'Special format rules!' " TODO " Grab text! - echom getline(coord[0], lnum - 1) + " echom getline(coord[0], lnum - 1) " Begin lexing! return coord[1] + 1 elseif formtype == 'vec' || formtype == 'map' @@ -116,11 +128,11 @@ function! s:GetClojureIndent() return coord[1] elseif formtype == 'reg' " Inside a regex. - echom 'Inside a regex!' + " echom 'Inside a regex!' return coord[1] - (s:ShouldAlignMultiLineStrings() ? 0 : 2) elseif formtype == 'str' " Inside a string. - echom 'Inside a string!' + " echom 'Inside a string!' return coord[1] - (s:ShouldAlignMultiLineStrings() ? 0 : 1) endif From 5f083ff1b41644047c6164527a912c5b4fa16d49 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Fri, 21 Apr 2023 02:54:51 +0100 Subject: [PATCH 03/88] A bit of code clean up and minor optimisations --- indent/clojure.vim | 56 ++++++++++++---------------------------------- 1 file changed, 14 insertions(+), 42 deletions(-) diff --git a/indent/clojure.vim b/indent/clojure.vim index 6fde261..2906788 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -23,15 +23,9 @@ setlocal indentkeys=!,o,O " TODO: ignore 'lisp' and 'lispwords' options (actually, turn them off?) -" TODO: Optional Vim9script implementations of hotspot/bottleneck functions. +" TODO: Optional Vim9script implementations of hotspot/bottleneck functions? " FIXME: fallback case when syntax highlighting is disabled. -" Function to get the indentation of a line. -" function! s:GetIndent(lnum) -" let l = getline(a:lnum) -" return len(l) - len(trim(l, " \t", 1)) -" endfunction - function! s:GetSynIdName(line, col) return synIDattr(synID(a:line, a:col, 0), 'name') endfunction @@ -48,18 +42,10 @@ function! s:NotAStringDelimiter() return ! s:SyntaxMatch('stringdelimiter', line('.'), col('.')) endfunction -function! s:IsInString() - return s:SyntaxMatch('string', line('.'), col('.')) -endfunction - function! s:NotARegexpDelimiter() return ! s:SyntaxMatch('regexpdelimiter', line('.'), col('.')) endfunction -function! s:IsInRegex() - return s:SyntaxMatch('regex', line('.'), col('.')) -endfunction - function! s:Conf(opt, default) return get(b:, a:opt, get(g:, a:opt, a:default)) endfunction @@ -68,28 +54,18 @@ function! s:ShouldAlignMultiLineStrings() return s:Conf('clojure_align_multiline_strings', 0) endfunction -function! s:ClosestMatch(match1, match2) - let [_, coord1] = a:match1 - let [_, coord2] = a:match2 - if coord1[0] < coord2[0] - return a:match2 - elseif coord1[0] == coord2[0] && coord1[1] < coord2[1] - return a:match2 - else - return a:match1 - endif -endfunction - " Wrapper around "searchpairpos" that will automatically set "s:best_match" to " the closest pair match and continuously optimise the "stopline" value for " later searches. This results in a significant performance gain by reducing " the number of syntax lookups that need to take place. function! s:CheckPair(name, start, end, skipfn) - let pos = searchpairpos(a:start, '', a:end, 'bznW', a:skipfn, s:best_match[1][0]) - let s:best_match = s:ClosestMatch(s:best_match, [a:name, pos]) + let prevln = s:best_match[1][0] + let pos = searchpairpos(a:start, '', a:end, 'bznW', a:skipfn, prevln) + if prevln < pos[0] || (prevln == pos[0] && s:best_match[1][1] < pos[1]) + let s:best_match = [a:name, pos] + endif endfunction -" Only need to search up. Never down. function! s:GetClojureIndent() let lnum = v:lnum @@ -102,9 +78,10 @@ function! s:GetClojureIndent() call s:CheckPair('vec', '\[', '\]', function('IgnoredRegion')) call s:CheckPair('map', '{', '}', function('IgnoredRegion')) - if s:IsInString() + let synname = s:GetSynIdName(lnum, col('.')) + if synname =~? 'string' call s:CheckPair('str', '"', '"', function('NotAStringDelimiter')) - elseif s:IsInRegex() + elseif synname =~? 'regex' call s:CheckPair('reg', '#\zs"', '"', function('NotARegexpDelimiter')) endif @@ -114,26 +91,21 @@ function! s:GetClojureIndent() if formtype == 'top' " At the top level, no indent. - " echom 'At the top level!' return 0 elseif formtype == 'lst' - " echom 'Special format rules!' - " TODO - " Grab text! + " Inside a list. + " TODO Begin analysis and apply rules! " echom getline(coord[0], lnum - 1) - " Begin lexing! return coord[1] + 1 elseif formtype == 'vec' || formtype == 'map' " Inside a vector, map or set. return coord[1] - elseif formtype == 'reg' - " Inside a regex. - " echom 'Inside a regex!' - return coord[1] - (s:ShouldAlignMultiLineStrings() ? 0 : 2) elseif formtype == 'str' " Inside a string. - " echom 'Inside a string!' return coord[1] - (s:ShouldAlignMultiLineStrings() ? 0 : 1) + elseif formtype == 'reg' + " Inside a regex. + return coord[1] - (s:ShouldAlignMultiLineStrings() ? 0 : 2) endif return 2 From f4e0689bf25a017b84cc269d91a92e0ec3afaaed Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Fri, 21 Apr 2023 10:08:35 +0100 Subject: [PATCH 04/88] Check for maps before vectors for further performance enhancement Since maps are far more likely to across multiple lines than vectors (and when they are they tend to be much larger, e.g. EDN files!), we can further optimise indentation by checking if the line is inside a map *before* checking it is inside a vector. (This happens because of the performance improvement made with the addition of the optimised `CheckPair` function.) --- indent/clojure.vim | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/indent/clojure.vim b/indent/clojure.vim index 2906788..e3e106b 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -22,7 +22,6 @@ setlocal softtabstop=2 shiftwidth=2 expandtab setlocal indentkeys=!,o,O " TODO: ignore 'lisp' and 'lispwords' options (actually, turn them off?) - " TODO: Optional Vim9script implementations of hotspot/bottleneck functions? " FIXME: fallback case when syntax highlighting is disabled. @@ -75,8 +74,8 @@ function! s:GetClojureIndent() let s:best_match = ['top', [0, 0]] call s:CheckPair('lst', '(', ')', function('IgnoredRegion')) - call s:CheckPair('vec', '\[', '\]', function('IgnoredRegion')) call s:CheckPair('map', '{', '}', function('IgnoredRegion')) + call s:CheckPair('vec', '\[', '\]', function('IgnoredRegion')) let synname = s:GetSynIdName(lnum, col('.')) if synname =~? 'string' From 2627e1cca4b4e34888de2fbcf5c0b30c07e9920d Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Fri, 21 Apr 2023 15:57:08 +0100 Subject: [PATCH 05/88] Add fallback for when Vim was not compiled with `searchpairpos` --- indent/clojure.vim | 61 +++++++++++++++++++++------------------------- 1 file changed, 28 insertions(+), 33 deletions(-) diff --git a/indent/clojure.vim b/indent/clojure.vim index e3e106b..9955fef 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -15,15 +15,13 @@ let b:did_indent = 1 let s:save_cpo = &cpoptions set cpoptions&vim -let b:undo_indent = 'setlocal autoindent< smartindent< expandtab< softtabstop< shiftwidth< indentexpr< indentkeys<' +let b:undo_indent = 'setlocal autoindent< smartindent< expandtab< softtabstop< shiftwidth< indentexpr< indentkeys< lisp<' -setlocal noautoindent nosmartindent +setlocal noautoindent nosmartindent nolisp setlocal softtabstop=2 shiftwidth=2 expandtab setlocal indentkeys=!,o,O -" TODO: ignore 'lisp' and 'lispwords' options (actually, turn them off?) -" TODO: Optional Vim9script implementations of hotspot/bottleneck functions? -" FIXME: fallback case when syntax highlighting is disabled. +" TODO: Write an optional Vim9 script version for better performance? function! s:GetSynIdName(line, col) return synIDattr(synID(a:line, a:col, 0), 'name') @@ -37,11 +35,11 @@ function! s:IgnoredRegion() return s:SyntaxMatch('\(string\|regex\|comment\|character\)', line('.'), col('.')) endfunction -function! s:NotAStringDelimiter() +function! s:NotStringDelimiter() return ! s:SyntaxMatch('stringdelimiter', line('.'), col('.')) endfunction -function! s:NotARegexpDelimiter() +function! s:NotRegexpDelimiter() return ! s:SyntaxMatch('regexpdelimiter', line('.'), col('.')) endfunction @@ -54,39 +52,38 @@ function! s:ShouldAlignMultiLineStrings() endfunction " Wrapper around "searchpairpos" that will automatically set "s:best_match" to -" the closest pair match and continuously optimise the "stopline" value for -" later searches. This results in a significant performance gain by reducing -" the number of syntax lookups that need to take place. -function! s:CheckPair(name, start, end, skipfn) +" the closest pair match and optimises the "stopline" value for later +" searches. This results in a significant performance gain by reducing the +" number of syntax lookups that need to take place. +function! s:CheckPair(name, start, end, SkipFn) let prevln = s:best_match[1][0] - let pos = searchpairpos(a:start, '', a:end, 'bznW', a:skipfn, prevln) + let pos = searchpairpos(a:start, '', a:end, 'bznW', a:SkipFn, prevln) if prevln < pos[0] || (prevln == pos[0] && s:best_match[1][1] < pos[1]) let s:best_match = [a:name, pos] endif endfunction function! s:GetClojureIndent() - let lnum = v:lnum - " Move cursor to the first column of the line we want to indent. - cursor(lnum, 0) + call cursor(v:lnum, 0) let s:best_match = ['top', [0, 0]] - call s:CheckPair('lst', '(', ')', function('IgnoredRegion')) - call s:CheckPair('map', '{', '}', function('IgnoredRegion')) - call s:CheckPair('vec', '\[', '\]', function('IgnoredRegion')) + let IgnoredRegionFn = function('IgnoredRegion') + + call s:CheckPair('lst', '(', ')', IgnoredRegionFn) + call s:CheckPair('map', '{', '}', IgnoredRegionFn) + call s:CheckPair('vec', '\[', '\]', IgnoredRegionFn) - let synname = s:GetSynIdName(lnum, col('.')) + let synname = s:GetSynIdName(v:lnum, col('.')) if synname =~? 'string' - call s:CheckPair('str', '"', '"', function('NotAStringDelimiter')) + call s:CheckPair('str', '"', '"', function('NotStringDelimiter')) elseif synname =~? 'regex' - call s:CheckPair('reg', '#\zs"', '"', function('NotARegexpDelimiter')) + call s:CheckPair('reg', '#\zs"', '"', function('NotRegexpDelimiter')) endif " Find closest matching higher form. let [formtype, coord] = s:best_match - " echom 'Match' formtype coord if formtype == 'top' " At the top level, no indent. @@ -94,13 +91,14 @@ function! s:GetClojureIndent() elseif formtype == 'lst' " Inside a list. " TODO Begin analysis and apply rules! - " echom getline(coord[0], lnum - 1) + " echom getline(coord[0], v:lnum - 1) return coord[1] + 1 elseif formtype == 'vec' || formtype == 'map' " Inside a vector, map or set. return coord[1] elseif formtype == 'str' " Inside a string. + " TODO: maintain string and regex indentation when `=` is pressed. return coord[1] - (s:ShouldAlignMultiLineStrings() ? 0 : 1) elseif formtype == 'reg' " Inside a regex. @@ -110,16 +108,13 @@ function! s:GetClojureIndent() return 2 endfunction - -setlocal indentexpr=s:GetClojureIndent() - - -" TODO: if exists("*searchpairpos") -" In case we have searchpairpos not available we fall back to normal lisp -" indenting. -"setlocal indentexpr= -"setlocal lisp -"let b:undo_indent .= '| setlocal lisp<' +if exists("*searchpairpos") + setlocal indentexpr=s:GetClojureIndent() +else + " If searchpairpos is not available, fallback to normal lisp + " indenting. + setlocal lisp indentexpr= +endif let &cpoptions = s:save_cpo unlet! s:save_cpo From 4a3cab72d9460bf964825ffa1347060682431e27 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Wed, 26 Apr 2023 12:43:28 +0200 Subject: [PATCH 06/88] Fix string detection when entering a newline while in insert mode --- indent/clojure.vim | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/indent/clojure.vim b/indent/clojure.vim index 9955fef..0c9e023 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -65,17 +65,23 @@ endfunction function! s:GetClojureIndent() " Move cursor to the first column of the line we want to indent. - call cursor(v:lnum, 0) + call cursor(v:lnum, 1) let s:best_match = ['top', [0, 0]] let IgnoredRegionFn = function('IgnoredRegion') - call s:CheckPair('lst', '(', ')', IgnoredRegionFn) call s:CheckPair('map', '{', '}', IgnoredRegionFn) call s:CheckPair('vec', '\[', '\]', IgnoredRegionFn) - let synname = s:GetSynIdName(v:lnum, col('.')) + " Improve accuracy of string detection when a newline is entered. + if empty(getline(v:lnum)) + let strline = v:lnum - 1 + let synname = s:GetSynIdName(strline, len(getline(strline))) + else + let synname = s:GetSynIdName(v:lnum, 1) + endif + if synname =~? 'string' call s:CheckPair('str', '"', '"', function('NotStringDelimiter')) elseif synname =~? 'regex' From d1ec02cc49847bea3f958247290bc4ec8492fa35 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Wed, 26 Apr 2023 12:47:06 +0200 Subject: [PATCH 07/88] Minor performance improvement when file contains multi-line strings --- indent/clojure.vim | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/indent/clojure.vim b/indent/clojure.vim index 0c9e023..0d571d0 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -21,8 +21,6 @@ setlocal noautoindent nosmartindent nolisp setlocal softtabstop=2 shiftwidth=2 expandtab setlocal indentkeys=!,o,O -" TODO: Write an optional Vim9 script version for better performance? - function! s:GetSynIdName(line, col) return synIDattr(synID(a:line, a:col, 0), 'name') endfunction @@ -67,13 +65,6 @@ function! s:GetClojureIndent() " Move cursor to the first column of the line we want to indent. call cursor(v:lnum, 1) - let s:best_match = ['top', [0, 0]] - - let IgnoredRegionFn = function('IgnoredRegion') - call s:CheckPair('lst', '(', ')', IgnoredRegionFn) - call s:CheckPair('map', '{', '}', IgnoredRegionFn) - call s:CheckPair('vec', '\[', '\]', IgnoredRegionFn) - " Improve accuracy of string detection when a newline is entered. if empty(getline(v:lnum)) let strline = v:lnum - 1 @@ -82,10 +73,17 @@ function! s:GetClojureIndent() let synname = s:GetSynIdName(v:lnum, 1) endif + let s:best_match = ['top', [0, 0]] + if synname =~? 'string' call s:CheckPair('str', '"', '"', function('NotStringDelimiter')) elseif synname =~? 'regex' call s:CheckPair('reg', '#\zs"', '"', function('NotRegexpDelimiter')) + else + let IgnoredRegionFn = function('IgnoredRegion') + call s:CheckPair('lst', '(', ')', IgnoredRegionFn) + call s:CheckPair('map', '{', '}', IgnoredRegionFn) + call s:CheckPair('vec', '\[', '\]', IgnoredRegionFn) endif " Find closest matching higher form. @@ -109,9 +107,9 @@ function! s:GetClojureIndent() elseif formtype == 'reg' " Inside a regex. return coord[1] - (s:ShouldAlignMultiLineStrings() ? 0 : 2) + else + return -1 endif - - return 2 endfunction if exists("*searchpairpos") From c3047beeddcf4465c64a5ab59eca875a6576ee55 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Thu, 27 Apr 2023 13:09:35 +0100 Subject: [PATCH 08/88] Mimic multi-line string/regex indent behaviour of VS Code and Emacs The `=` operator will no longer alter the indent of lines within a Clojure multi-line string or regular expression. The previous behaviour was annoying for writing detailed doc-strings, as it made reformatting the file with `gg=G` not possible as it would screw up the indentation within the doc-strings. Now the behaviour matches that of VS Code and Emacs. --- indent/clojure.vim | 41 +++++++++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/indent/clojure.vim b/indent/clojure.vim index 0d571d0..0ff9031 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -46,6 +46,8 @@ function! s:Conf(opt, default) endfunction function! s:ShouldAlignMultiLineStrings() + " TODO: third option "-1" to default to "no indent", like traditional + " lisps? return s:Conf('clojure_align_multiline_strings', 0) endfunction @@ -89,27 +91,42 @@ function! s:GetClojureIndent() " Find closest matching higher form. let [formtype, coord] = s:best_match - if formtype == 'top' + if formtype ==# 'top' " At the top level, no indent. return 0 - elseif formtype == 'lst' + elseif formtype ==# 'lst' " Inside a list. " TODO Begin analysis and apply rules! " echom getline(coord[0], v:lnum - 1) return coord[1] + 1 - elseif formtype == 'vec' || formtype == 'map' + elseif formtype ==# 'vec' || formtype ==# 'map' " Inside a vector, map or set. return coord[1] - elseif formtype == 'str' - " Inside a string. - " TODO: maintain string and regex indentation when `=` is pressed. - return coord[1] - (s:ShouldAlignMultiLineStrings() ? 0 : 1) - elseif formtype == 'reg' - " Inside a regex. - return coord[1] - (s:ShouldAlignMultiLineStrings() ? 0 : 2) - else - return -1 + elseif formtype ==# 'str' || formtype ==# 'reg' + " Mimic multi-line string indentation behaviour in VS Code and + " Emacs. + " + " Scenarios: + " - "=" operator should NOT alter indentation within + " multi-line strings. + " - Changes made while in insert mode (e.g. ""), should + " use standard string indent. + " - All other commands from normal mode (e.g. "o" and "O") + " should trigger normal string indent. + + let m = mode() + if m ==# 'i' || (m ==# 'n' && ! (v:operator ==# '=' && state() =~# 'o')) + " If in insert mode, or (in normal mode and last + " operator is not "=" and is not currently active. + let l:indent = (s:ShouldAlignMultiLineStrings() + \ ? 0 + \ : (formtype ==# 'reg' ? 2 : 1)) + return coord[1] - l:indent + endif endif + + " Keep existing indent. + return -1 endfunction if exists("*searchpairpos") From eb0463ebfc529540485776b5480d081889b7b9ac Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Thu, 27 Apr 2023 14:16:44 +0100 Subject: [PATCH 09/88] When `clojure_align_multiline_strings` is `-1`, no indentation By setting the `clojure_align_multiline_strings` option to `-1` it will switch to traditional Lisp multi-line string indentation, of no indent. --- indent/clojure.vim | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/indent/clojure.vim b/indent/clojure.vim index 0ff9031..188a955 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -46,8 +46,10 @@ function! s:Conf(opt, default) endfunction function! s:ShouldAlignMultiLineStrings() - " TODO: third option "-1" to default to "no indent", like traditional - " lisps? + " Possible Values: (default is 0) + " -1: Indent of 0, along left edge, like traditional Lisps. + " 0: Indent in alignment with string start delimiter. + " 1: Indent in alignment with end of the string start delimiter. return s:Conf('clojure_align_multiline_strings', 0) endfunction @@ -118,10 +120,17 @@ function! s:GetClojureIndent() if m ==# 'i' || (m ==# 'n' && ! (v:operator ==# '=' && state() =~# 'o')) " If in insert mode, or (in normal mode and last " operator is not "=" and is not currently active. - let l:indent = (s:ShouldAlignMultiLineStrings() - \ ? 0 - \ : (formtype ==# 'reg' ? 2 : 1)) - return coord[1] - l:indent + let rule = s:ShouldAlignMultiLineStrings() + if rule == -1 + " No indent. + return 0 + elseif rule == 1 + " Align with start of delimiter. + return coord[1] + else + " Align with end of delimiter. + return coord[1] - (formtype ==# 'reg' ? 2 : 1) + endif endif endif From d69008f08f5a2945cdc8e924b5e7a7a807462519 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Thu, 27 Apr 2023 14:40:34 +0100 Subject: [PATCH 10/88] Update indentation options sections of the README and vim help file --- README.md | 108 ++++++++++++++++-------------------------------- doc/clojure.txt | 104 +++++++++++----------------------------------- 2 files changed, 59 insertions(+), 153 deletions(-) diff --git a/README.md b/README.md index febc9cc..e33c8eb 100644 --- a/README.md +++ b/README.md @@ -71,96 +71,58 @@ disabled by default. ### Indent options -Clojure indentation differs somewhat from traditional Lisps, due in part to -the use of square and curly brackets, and otherwise by community convention. -These conventions are not universally followed, so the Clojure indent script -offers a few configuration options. +By default Clojure.vim will attempt to follow the indentation rules in the +[Clojure Style Guide](https://guide.clojure.style), but various configuration +options are provided to alter the indentation as you prefer. -(If the current Vim does not include `searchpairpos()`, the indent script falls -back to normal `'lisp'` indenting, and the following options are ignored.) +> **Warning**
+> If your installation of Vim does not include `searchpairpos()`, the indent +> script falls back to normal `'lisp'` and `'lispwords'` indenting, ignoring +> the following indentation options. -#### `g:clojure_maxlines` +#### `clojure_indent_rules` -Sets maximum scan distance of `searchpairpos()`. Larger values trade -performance for correctness when dealing with very long forms. A value of -0 will scan without limits. The default is 300. +> **Note**
+> The indentation code was recently rebuilt, which included the removal of +> several former configuration options (`clojure_fuzzy_indent`, +> `clojure_fuzzy_indent_patterns`, `clojure_fuzzy_indent_blacklist`, +> `clojure_special_indent_words`, `clojure_cljfmt_compat` and now ignores the +> value of `'lispwords'`). +> +> All of those options were rolled into this new option. -#### `g:clojure_fuzzy_indent`, `g:clojure_fuzzy_indent_patterns`, `g:clojure_fuzzy_indent_blacklist` +#### `clojure_align_multiline_strings` -The `'lispwords'` option is a list of comma-separated words that mark special -forms whose subforms should be indented with two spaces. - -For example: - -```clojure -(defn bad [] - "Incorrect indentation") - -(defn good [] - "Correct indentation") -``` - -If you would like to specify `'lispwords'` with a pattern instead, you can use -the fuzzy indent feature: - -```vim -" Default -let g:clojure_fuzzy_indent = 1 -let g:clojure_fuzzy_indent_patterns = ['^with', '^def', '^let'] -let g:clojure_fuzzy_indent_blacklist = ['-fn$', '\v^with-%(meta|out-str|loading-context)$'] -``` - -`g:clojure_fuzzy_indent_patterns` and `g:clojure_fuzzy_indent_blacklist` are -lists of patterns that will be matched against the unqualified symbol at the -head of a list. This means that a pattern like `"^foo"` will match all these -candidates: `foobar`, `my.ns/foobar`, and `#'foobar`. - -Each candidate word is tested for special treatment in this order: - -1. Return true if word is literally in `'lispwords'` -2. Return false if word matches a pattern in `g:clojure_fuzzy_indent_blacklist` -3. Return true if word matches a pattern in `g:clojure_fuzzy_indent_patterns` -4. Return false and indent normally otherwise - - -#### `g:clojure_special_indent_words` - -Some forms in Clojure are indented such that every subform is indented by only -two spaces, regardless of `'lispwords'`. If you have a custom construct that -should be indented in this idiosyncratic fashion, you can add your symbols to -the default list below. - -```vim -" Default -let g:clojure_special_indent_words = 'deftype,defrecord,reify,proxy,extend-type,extend-protocol,letfn' -``` - - -#### `g:clojure_align_multiline_strings` - -Align subsequent lines in multi-line strings to the column after the opening -quote, instead of the same column. - -For example: +Alter alignment of newly created lines within multi-line strings (and regular +expressions). ```clojure +;; let g:clojure_align_multiline_strings = 0 " Default (def default "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do - eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut - enim ad minim veniam, quis nostrud exercitation ullamco laboris - nisi ut aliquip ex ea commodo consequat.") + eiusmod tempor incididunt ut labore et dolore magna aliqua.") +;; let g:clojure_align_multiline_strings = 1 (def aligned "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do - eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut - enim ad minim veniam, quis nostrud exercitation ullamco laboris - nisi ut aliquip ex ea commodo consequat.") + eiusmod tempor incididunt ut labore et dolore magna aliqua.") + +;; let g:clojure_align_multiline_strings = -1 +(def traditional + "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do +eiusmod tempor incididunt ut labore et dolore magna aliqua.") ``` +There is also a buffer-local (`b:`) version of this option. + +> **Note**
+> Indenting the string with `=` will not alter the indentation of existing +> multi-line strings as that would break intentional formatting. + -#### `g:clojure_align_subforms` +#### `clojure_align_subforms` By default, parenthesized compound forms that look like function calls and whose head subform is on its own line have subsequent subforms indented by diff --git a/doc/clojure.txt b/doc/clojure.txt index 1bd6018..a5034ea 100644 --- a/doc/clojure.txt +++ b/doc/clojure.txt @@ -7,92 +7,44 @@ Clojure runtime files for Vim. CLOJURE *ft-clojure-indent* *clojure-indent* -Clojure indentation differs somewhat from traditional Lisps, due in part to -the use of square and curly brackets, and otherwise by community convention. -These conventions are not universally followed, so the Clojure indent script -offers a few configuration options. +By default Clojure.vim will attempt to follow the indentation rules in the +Clojure Style Guide, [1] but various configuration options are provided to +alter the indentation as you prefer. -(If the current Vim does not include |searchpairpos()|, the indent script falls -back to normal 'lisp' indenting, and the following options are ignored.) +[1]: https://guide.clojure.style +WARNING: if your installation of Vim does not include `searchpairpos()`, the +indent script falls back to normal 'lisp' and 'lispwords' indenting, +ignoring the following indentation options. - *g:clojure_maxlines* + *b:clojure_indent_rules* + *g:clojure_indent_rules* -Sets maximum scan distance of `searchpairpos()`. Larger values trade -performance for correctness when dealing with very long forms. A value of -0 will scan without limits. The default is 300. - - - *g:clojure_fuzzy_indent* - *g:clojure_fuzzy_indent_patterns* - *g:clojure_fuzzy_indent_blacklist* - -The 'lispwords' option is a list of comma-separated words that mark special -forms whose subforms should be indented with two spaces. - -For example: -> - (defn bad [] - "Incorrect indentation") - - (defn good [] - "Correct indentation") -< -If you would like to specify 'lispwords' with a |pattern| instead, you can use -the fuzzy indent feature: -> - " Default - let g:clojure_fuzzy_indent = 1 - let g:clojure_fuzzy_indent_patterns = ['^with', '^def', '^let'] - let g:clojure_fuzzy_indent_blacklist = - \ ['-fn$', '\v^with-%(meta|out-str|loading-context)$'] -< -|g:clojure_fuzzy_indent_patterns| and |g:clojure_fuzzy_indent_blacklist| are -lists of patterns that will be matched against the unqualified symbol at the -head of a list. This means that a pattern like `"^foo"` will match all these -candidates: `foobar`, `my.ns/foobar`, and `#'foobar`. - -Each candidate word is tested for special treatment in this order: - - 1. Return true if word is literally in 'lispwords' - 2. Return false if word matches a pattern in - |g:clojure_fuzzy_indent_blacklist| - 3. Return true if word matches a pattern in - |g:clojure_fuzzy_indent_patterns| - 4. Return false and indent normally otherwise - - - *g:clojure_special_indent_words* - -Some forms in Clojure are indented such that every subform is indented by only -two spaces, regardless of 'lispwords'. If you have a custom construct that -should be indented in this idiosyncratic fashion, you can add your symbols to -the default list below. -> - " Default - let g:clojure_special_indent_words = - \ 'deftype,defrecord,reify,proxy,extend-type,extend-protocol,letfn' -< +TODO: add this option and write this section. + *b:clojure_align_multiline_strings* *g:clojure_align_multiline_strings* -Align subsequent lines in multi-line strings to the column after the opening -quote, instead of the same column. - -For example: +Alter alignment of newly created lines within multi-line strings (and regular +expressions). > + ;; let g:clojure_align_multiline_strings = 0 " Default (def default "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do - eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut - enim ad minim veniam, quis nostrud exercitation ullamco laboris - nisi ut aliquip ex ea commodo consequat.") + eiusmod tempor incididunt ut labore et dolore magna aliqua.") + ;; let g:clojure_align_multiline_strings = 1 (def aligned "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do - eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut - enim ad minim veniam, quis nostrud exercitation ullamco laboris - nisi ut aliquip ex ea commodo consequat.") + eiusmod tempor incididunt ut labore et dolore magna aliqua.") + + ;; let g:clojure_align_multiline_strings = -1 + (def traditional + "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do + eiusmod tempor incididunt ut labore et dolore magna aliqua.") < +NOTE: indenting the string with |=| will not alter the indentation of existing +multi-line strings as that would break intentional formatting. *g:clojure_align_subforms* @@ -113,14 +65,6 @@ clojure-mode.el: baz) < - *g:clojure_cljfmt_compat* - -Try to be (more) compatible with `cljfmt` Clojure code formatting tool. Turns -on single space indenting for forms starting with `:keywords`, `'symbols`, -`#'variables` and `@dereferences` (it affects, for instance, `(:require ...)` -clause in Clojure `ns` form). - - CLOJURE *ft-clojure-syntax* *g:clojure_syntax_keywords* From d73e408580669688c45b87a339f5bb50e0861437 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Thu, 27 Apr 2023 14:46:58 +0100 Subject: [PATCH 11/88] Improve some of the comments in the indentation code --- indent/clojure.vim | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/indent/clojure.vim b/indent/clojure.vim index 188a955..b84940c 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -56,7 +56,7 @@ endfunction " Wrapper around "searchpairpos" that will automatically set "s:best_match" to " the closest pair match and optimises the "stopline" value for later " searches. This results in a significant performance gain by reducing the -" number of syntax lookups that need to take place. +" search distance and number of syntax lookups that need to take place. function! s:CheckPair(name, start, end, SkipFn) let prevln = s:best_match[1][0] let pos = searchpairpos(a:start, '', a:end, 'bznW', a:SkipFn, prevln) @@ -107,15 +107,6 @@ function! s:GetClojureIndent() elseif formtype ==# 'str' || formtype ==# 'reg' " Mimic multi-line string indentation behaviour in VS Code and " Emacs. - " - " Scenarios: - " - "=" operator should NOT alter indentation within - " multi-line strings. - " - Changes made while in insert mode (e.g. ""), should - " use standard string indent. - " - All other commands from normal mode (e.g. "o" and "O") - " should trigger normal string indent. - let m = mode() if m ==# 'i' || (m ==# 'n' && ! (v:operator ==# '=' && state() =~# 'o')) " If in insert mode, or (in normal mode and last @@ -141,9 +132,8 @@ endfunction if exists("*searchpairpos") setlocal indentexpr=s:GetClojureIndent() else - " If searchpairpos is not available, fallback to normal lisp - " indenting. - setlocal lisp indentexpr= + " If "searchpairpos" is not available, fallback to Lisp indenting. + setlocal lisp endif let &cpoptions = s:save_cpo From d177b3bb80d1df00786983b0f8248c03a147d8cf Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Thu, 27 Apr 2023 16:43:50 +0100 Subject: [PATCH 12/88] Move string indent picker to a separate function --- indent/clojure.vim | 46 +++++++++++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/indent/clojure.vim b/indent/clojure.vim index b84940c..c934c78 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -53,6 +53,27 @@ function! s:ShouldAlignMultiLineStrings() return s:Conf('clojure_align_multiline_strings', 0) endfunction +function! s:GetStringIndent(delim_pos, regex) + " Mimic multi-line string indentation behaviour in VS Code and Emacs. + let m = mode() + if m ==# 'i' || (m ==# 'n' && ! (v:operator ==# '=' && state('o') ==# 'o')) + " If in insert mode, or (in normal mode and last operator is + " not "=" and is not currently active. + let rule = s:ShouldAlignMultiLineStrings() + if rule == -1 + return 0 " No indent. + elseif rule == 1 + " Align with start of delimiter. + return a:delim_pos[1] + else + " Align with end of delimiter. + return a:delim_pos[1] - (a:regex ? 2 : 1) + endif + else + return -1 " Keep existing indent. + endif +endfunction + " Wrapper around "searchpairpos" that will automatically set "s:best_match" to " the closest pair match and optimises the "stopline" value for later " searches. This results in a significant performance gain by reducing the @@ -104,25 +125,12 @@ function! s:GetClojureIndent() elseif formtype ==# 'vec' || formtype ==# 'map' " Inside a vector, map or set. return coord[1] - elseif formtype ==# 'str' || formtype ==# 'reg' - " Mimic multi-line string indentation behaviour in VS Code and - " Emacs. - let m = mode() - if m ==# 'i' || (m ==# 'n' && ! (v:operator ==# '=' && state() =~# 'o')) - " If in insert mode, or (in normal mode and last - " operator is not "=" and is not currently active. - let rule = s:ShouldAlignMultiLineStrings() - if rule == -1 - " No indent. - return 0 - elseif rule == 1 - " Align with start of delimiter. - return coord[1] - else - " Align with end of delimiter. - return coord[1] - (formtype ==# 'reg' ? 2 : 1) - endif - endif + elseif formtype ==# 'str' + " Inside a string. + return s:GetStringIndent(coord, 0) + elseif formtype ==# 'reg' + " Inside a regular expression. + return s:GetStringIndent(coord, 1) endif " Keep existing indent. From 51e6a43f209745b97f23d1750dfbd95bcb5bf210 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Fri, 28 Apr 2023 14:44:19 +0100 Subject: [PATCH 13/88] Fix false positive multi-line string detection Sometimes it seems that when `=`ing over a block, Vim will sometimes not re-highlight strings correctly until we have already ran `searchpairpos`. In this case, we implement a hacky workaround which detects the false-positive and recovers from it. --- indent/clojure.vim | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/indent/clojure.vim b/indent/clojure.vim index c934c78..bcebbf6 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -90,10 +90,11 @@ function! s:GetClojureIndent() " Move cursor to the first column of the line we want to indent. call cursor(v:lnum, 1) - " Improve accuracy of string detection when a newline is entered. if empty(getline(v:lnum)) + " Improves the accuracy of string detection when a newline is + " entered while in insert mode. let strline = v:lnum - 1 - let synname = s:GetSynIdName(strline, len(getline(strline))) + let synname = s:GetSynIdName(strline, strlen(getline(strline))) else let synname = s:GetSynIdName(v:lnum, 1) endif @@ -101,7 +102,20 @@ function! s:GetClojureIndent() let s:best_match = ['top', [0, 0]] if synname =~? 'string' + " Sometimes, string highlighting does not kick in correctly, + " until after this first "s:CheckPair" call, so we have to + " detect and attempt an automatic correction. call s:CheckPair('str', '"', '"', function('NotStringDelimiter')) + let new_synname = s:GetSynIdName(v:lnum, 1) + if new_synname !=# synname + echoerr 'Misdetected string! Retrying...' + let s:best_match = ['top', [0, 0]] + let synname = new_synname + endif + endif + + if synname =~? 'string' + " We already checked this above, so pass through this block. elseif synname =~? 'regex' call s:CheckPair('reg', '#\zs"', '"', function('NotRegexpDelimiter')) else From e573da70357ad4eb73560abd825e692b02cec54f Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Fri, 28 Apr 2023 14:46:18 +0100 Subject: [PATCH 14/88] EDN files are unlikely to contain many lists, so check them last --- indent/clojure.vim | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/indent/clojure.vim b/indent/clojure.vim index bcebbf6..1294d0f 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -120,9 +120,17 @@ function! s:GetClojureIndent() call s:CheckPair('reg', '#\zs"', '"', function('NotRegexpDelimiter')) else let IgnoredRegionFn = function('IgnoredRegion') - call s:CheckPair('lst', '(', ')', IgnoredRegionFn) - call s:CheckPair('map', '{', '}', IgnoredRegionFn) - call s:CheckPair('vec', '\[', '\]', IgnoredRegionFn) + if bufname() ==? '\.edn$' + " If EDN file, check list pair last. + call s:CheckPair('map', '{', '}', IgnoredRegionFn) + call s:CheckPair('vec', '\[', '\]', IgnoredRegionFn) + call s:CheckPair('lst', '(', ')', IgnoredRegionFn) + else + " If CLJ file, check list pair first. + call s:CheckPair('lst', '(', ')', IgnoredRegionFn) + call s:CheckPair('map', '{', '}', IgnoredRegionFn) + call s:CheckPair('vec', '\[', '\]', IgnoredRegionFn) + endif endif " Find closest matching higher form. @@ -145,10 +153,10 @@ function! s:GetClojureIndent() elseif formtype ==# 'reg' " Inside a regular expression. return s:GetStringIndent(coord, 1) + else + " Keep existing indent. + return -1 endif - - " Keep existing indent. - return -1 endfunction if exists("*searchpairpos") From 242dc9dc12b11f38ee3128ff5b30d682c4faa91f Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Fri, 28 Apr 2023 15:17:12 +0100 Subject: [PATCH 15/88] Yet more string detection fixes and slight code clean up --- indent/clojure.vim | 47 +++++++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/indent/clojure.vim b/indent/clojure.vim index 1294d0f..42df1de 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -45,28 +45,26 @@ function! s:Conf(opt, default) return get(b:, a:opt, get(g:, a:opt, a:default)) endfunction -function! s:ShouldAlignMultiLineStrings() - " Possible Values: (default is 0) - " -1: Indent of 0, along left edge, like traditional Lisps. - " 0: Indent in alignment with string start delimiter. - " 1: Indent in alignment with end of the string start delimiter. - return s:Conf('clojure_align_multiline_strings', 0) +function! s:EqualsOperatorInEffect() + " Returns 1 when the previous operator used is "=" and is currently in + " effect (i.e. "state" includes "o"). + return v:operator ==# '=' && state('o') ==# 'o' endfunction function! s:GetStringIndent(delim_pos, regex) " Mimic multi-line string indentation behaviour in VS Code and Emacs. let m = mode() - if m ==# 'i' || (m ==# 'n' && ! (v:operator ==# '=' && state('o') ==# 'o')) - " If in insert mode, or (in normal mode and last operator is - " not "=" and is not currently active. - let rule = s:ShouldAlignMultiLineStrings() + if m ==# 'i' || (m ==# 'n' && ! s:EqualsOperatorInEffect()) + " If in insert mode, or normal mode but "=" is not in effect. + let rule = s:Conf('clojure_align_multiline_strings', 0) if rule == -1 - return 0 " No indent. + " Indent along left edge, like traditional Lisps. + return 0 elseif rule == 1 - " Align with start of delimiter. + " Indent in alignment with end of the string start delimiter. return a:delim_pos[1] else - " Align with end of delimiter. + " Indent in alignment with string start delimiter. return a:delim_pos[1] - (a:regex ? 2 : 1) endif else @@ -86,27 +84,30 @@ function! s:CheckPair(name, start, end, SkipFn) endif endfunction -function! s:GetClojureIndent() - " Move cursor to the first column of the line we want to indent. - call cursor(v:lnum, 1) - - if empty(getline(v:lnum)) +function! s:GetCurrentSynName(lnum) + if empty(getline(a:lnum)) " Improves the accuracy of string detection when a newline is " entered while in insert mode. - let strline = v:lnum - 1 - let synname = s:GetSynIdName(strline, strlen(getline(strline))) + let strline = a:lnum - 1 + return s:GetSynIdName(strline, strlen(getline(strline))) else - let synname = s:GetSynIdName(v:lnum, 1) + return s:GetSynIdName(a:lnum, 1) endif +endfunction + +function! s:GetClojureIndent() + " Move cursor to the first column of the line we want to indent. + call cursor(v:lnum, 1) let s:best_match = ['top', [0, 0]] + let synname = s:GetCurrentSynName(v:lnum) if synname =~? 'string' + call s:CheckPair('str', '"', '"', function('NotStringDelimiter')) " Sometimes, string highlighting does not kick in correctly, " until after this first "s:CheckPair" call, so we have to " detect and attempt an automatic correction. - call s:CheckPair('str', '"', '"', function('NotStringDelimiter')) - let new_synname = s:GetSynIdName(v:lnum, 1) + let new_synname = s:GetCurrentSynName(v:lnum) if new_synname !=# synname echoerr 'Misdetected string! Retrying...' let s:best_match = ['top', [0, 0]] From 342f35fb2ccf263f5c34eef966c63cc48477a122 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Fri, 28 Apr 2023 16:30:14 +0100 Subject: [PATCH 16/88] Initial work on list indentation Still lots to do here, this is just the initial work. --- indent/clojure.vim | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/indent/clojure.vim b/indent/clojure.vim index 42df1de..0071c7e 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -30,7 +30,7 @@ function! s:SyntaxMatch(pattern, line, col) endfunction function! s:IgnoredRegion() - return s:SyntaxMatch('\(string\|regex\|comment\|character\)', line('.'), col('.')) + return s:SyntaxMatch('\%(string\|regex\|comment\|character\)', line('.'), col('.')) endfunction function! s:NotStringDelimiter() @@ -72,6 +72,19 @@ function! s:GetStringIndent(delim_pos, regex) endif endfunction +function! s:GetListIndent(delim_pos) + " TODO Begin analysis and apply rules! + let ln1 = getline(delim_pos[0]) + let sym = get(split(ln1[delim_pos[1]:], '[[:space:],;()\[\]{}@\\"^~`]', 1), 0, -1) + if sym != -1 && ! empty(sym) && match(sym, '^[0-9:]') == -1 + " TODO: align indentation. + return delim_pos[1] + 1 " 2 space indentation + endif + + " TODO: switch between 1 vs 2 space indentation. + return delim_pos[1] " 1 space indentation +endfunction + " Wrapper around "searchpairpos" that will automatically set "s:best_match" to " the closest pair match and optimises the "stopline" value for later " searches. This results in a significant performance gain by reducing the @@ -142,9 +155,7 @@ function! s:GetClojureIndent() return 0 elseif formtype ==# 'lst' " Inside a list. - " TODO Begin analysis and apply rules! - " echom getline(coord[0], v:lnum - 1) - return coord[1] + 1 + return s:GetListIndent(coord) elseif formtype ==# 'vec' || formtype ==# 'map' " Inside a vector, map or set. return coord[1] From 51f5dcb7cbd34036b20dc62e0474cfe1ce388080 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Fri, 28 Apr 2023 17:28:58 +0100 Subject: [PATCH 17/88] More indentation code clean up and readability improvements --- indent/clojure.vim | 49 ++++++++++++++++------------------------------ 1 file changed, 17 insertions(+), 32 deletions(-) diff --git a/indent/clojure.vim b/indent/clojure.vim index 0071c7e..f7d2c4c 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -56,16 +56,13 @@ function! s:GetStringIndent(delim_pos, regex) let m = mode() if m ==# 'i' || (m ==# 'n' && ! s:EqualsOperatorInEffect()) " If in insert mode, or normal mode but "=" is not in effect. - let rule = s:Conf('clojure_align_multiline_strings', 0) - if rule == -1 - " Indent along left edge, like traditional Lisps. - return 0 - elseif rule == 1 - " Indent in alignment with end of the string start delimiter. - return a:delim_pos[1] - else - " Indent in alignment with string start delimiter. - return a:delim_pos[1] - (a:regex ? 2 : 1) + let alignment = s:Conf('clojure_align_multiline_strings', 0) + " -1: Indent along left edge, like traditional Lisps. + " 0: Indent in alignment with end of the string start delimiter. + " 1: Indent in alignment with string start delimiter. + if alignment == -1 | return 0 + elseif alignment == 1 | return a:delim_pos[1] + else | return a:delim_pos[1] - (a:regex ? 2 : 1) endif else return -1 " Keep existing indent. @@ -131,10 +128,10 @@ function! s:GetClojureIndent() if synname =~? 'string' " We already checked this above, so pass through this block. elseif synname =~? 'regex' - call s:CheckPair('reg', '#\zs"', '"', function('NotRegexpDelimiter')) + call s:CheckPair('rex', '#\zs"', '"', function('NotRegexpDelimiter')) else let IgnoredRegionFn = function('IgnoredRegion') - if bufname() ==? '\.edn$' + if bufname() =~? '\.edn$' " If EDN file, check list pair last. call s:CheckPair('map', '{', '}', IgnoredRegionFn) call s:CheckPair('vec', '\[', '\]', IgnoredRegionFn) @@ -147,27 +144,15 @@ function! s:GetClojureIndent() endif endif - " Find closest matching higher form. + " Calculate and return indent to use based on the matching form. let [formtype, coord] = s:best_match - - if formtype ==# 'top' - " At the top level, no indent. - return 0 - elseif formtype ==# 'lst' - " Inside a list. - return s:GetListIndent(coord) - elseif formtype ==# 'vec' || formtype ==# 'map' - " Inside a vector, map or set. - return coord[1] - elseif formtype ==# 'str' - " Inside a string. - return s:GetStringIndent(coord, 0) - elseif formtype ==# 'reg' - " Inside a regular expression. - return s:GetStringIndent(coord, 1) - else - " Keep existing indent. - return -1 + if formtype ==# 'top' | return 0 " At top level, no indent. + elseif formtype ==# 'lst' | return s:GetListIndent(coord) + elseif formtype ==# 'vec' | return coord[1] " Vector + elseif formtype ==# 'map' | return coord[1] " Map/set + elseif formtype ==# 'str' | return s:GetStringIndent(coord, 0) + elseif formtype ==# 'rex' | return s:GetStringIndent(coord, 1) + else | return -1 " Keep existing indent. endif endfunction From cc9cda7e5989ecfec34b8b5f79781328ba5f9783 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Sun, 30 Apr 2023 19:23:37 +0100 Subject: [PATCH 18/88] Start of a small Clojure reader for indentation w/o syntax highlighting Not complete yet, but getting there. --- indent/clojure.vim | 177 ++++++++++++++++++++++----------------------- 1 file changed, 88 insertions(+), 89 deletions(-) diff --git a/indent/clojure.vim b/indent/clojure.vim index f7d2c4c..d888ca8 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -21,24 +21,86 @@ setlocal noautoindent nosmartindent nolisp setlocal softtabstop=2 shiftwidth=2 expandtab setlocal indentkeys=!,o,O -function! s:GetSynIdName(line, col) - return synIDattr(synID(a:line, a:col, 0), 'name') +" Returns true if char_idx is preceded by an odd number of backslashes. +function! s:IsEscaped(line_str, char_idx) + let ln = a:line_str[: a:char_idx - 1] + return (strlen(ln) - strlen(trim(ln, '\', 2))) % 2 endfunction -function! s:SyntaxMatch(pattern, line, col) - return s:GetSynIdName(a:line, a:col) =~? a:pattern -endfunction +let s:pairs = {'(': ')', '[': ']', '{': '}'} + +" TODO: Maybe write a Vim9script version of this? +" Repeatedly search for tokens on the given line in reverse order building up +" a list of tokens and their positions. Ignores escaped tokens. +function! s:AnalyseLine(line_num) + let tokens = [] + let ln = getline(a:line_num) + + while 1 + " Due to legacy Vimscript being painfully slow, we literally + " have to move the cursor and perform searches which is + " ironically faster than for looping by character. + let token = searchpos('[()\[\]{};"]', 'bW', a:line_num) + + if token == [0, 0] | break | endif + let t_idx = token[1] - 1 + if s:IsEscaped(ln, t_idx) | continue | endif + let t_char = ln[t_idx] + + if t_char ==# ';' + " Comment found, reset the token list for this line. + tokens = [] + elseif t_char =~# '[()\[\]{}"]' + " Add token to the list. + call add(tokens, [t_char, token]) + endif + endwhile -function! s:IgnoredRegion() - return s:SyntaxMatch('\%(string\|regex\|comment\|character\)', line('.'), col('.')) + return tokens endfunction -function! s:NotStringDelimiter() - return ! s:SyntaxMatch('stringdelimiter', line('.'), col('.')) -endfunction +" This should also be capable of figuring out if we're in a multi-line string +" or regex. +function! s:InverseRead(lnum) + let lnum = a:lnum - 1 + let tokens = [] + + while lnum > 0 + call cursor(lnum + 1, 1) + let line_tokens = s:AnalyseLine(lnum) + + " let should_ignore = empty(a:tokens) ? 0 : (a:tokens[-1][0] ==# '"') + + " Reduce "tokens" and "line_tokens". + for t in line_tokens + " TODO: attempt early termination. + if empty(tokens) + call add(tokens, t) + elseif t[0] ==# '"' && tokens[-1][0] ==# '"' + " TODO: track original start and ignore values + " inside strings. + call remove(tokens, -1) + elseif get(s:pairs, t[0], '') ==# tokens[-1][0] + " Matching pair: drop the last item in tokens. + call remove(tokens, -1) + else + " No match: append to token list. + call add(tokens, t) + endif + endfor + + " echom 'Pass' lnum tokens + + if ! empty(tokens) && has_key(s:pairs, tokens[0][0]) + " TODO: on string match, check if string or regex. + " echom 'Match!' tokens[0] + return tokens[0] + endif + + let lnum -= 1 + endwhile -function! s:NotRegexpDelimiter() - return ! s:SyntaxMatch('regexpdelimiter', line('.'), col('.')) + return ['^', [0, 0]] " Default to top-level. endfunction function! s:Conf(opt, default) @@ -51,7 +113,7 @@ function! s:EqualsOperatorInEffect() return v:operator ==# '=' && state('o') ==# 'o' endfunction -function! s:GetStringIndent(delim_pos, regex) +function! s:GetStringIndent(delim_pos, is_regex) " Mimic multi-line string indentation behaviour in VS Code and Emacs. let m = mode() if m ==# 'i' || (m ==# 'n' && ! s:EqualsOperatorInEffect()) @@ -62,7 +124,7 @@ function! s:GetStringIndent(delim_pos, regex) " 1: Indent in alignment with string start delimiter. if alignment == -1 | return 0 elseif alignment == 1 | return a:delim_pos[1] - else | return a:delim_pos[1] - (a:regex ? 2 : 1) + else | return a:delim_pos[1] - (a:is_regex ? 2 : 1) endif else return -1 " Keep existing indent. @@ -71,10 +133,12 @@ endfunction function! s:GetListIndent(delim_pos) " TODO Begin analysis and apply rules! + " let lns = getline(delim_pos[0], v:lnum - 1) let ln1 = getline(delim_pos[0]) let sym = get(split(ln1[delim_pos[1]:], '[[:space:],;()\[\]{}@\\"^~`]', 1), 0, -1) if sym != -1 && ! empty(sym) && match(sym, '^[0-9:]') == -1 " TODO: align indentation. + " TODO: lookup rules. return delim_pos[1] + 1 " 2 space indentation endif @@ -82,86 +146,21 @@ function! s:GetListIndent(delim_pos) return delim_pos[1] " 1 space indentation endfunction -" Wrapper around "searchpairpos" that will automatically set "s:best_match" to -" the closest pair match and optimises the "stopline" value for later -" searches. This results in a significant performance gain by reducing the -" search distance and number of syntax lookups that need to take place. -function! s:CheckPair(name, start, end, SkipFn) - let prevln = s:best_match[1][0] - let pos = searchpairpos(a:start, '', a:end, 'bznW', a:SkipFn, prevln) - if prevln < pos[0] || (prevln == pos[0] && s:best_match[1][1] < pos[1]) - let s:best_match = [a:name, pos] - endif -endfunction - -function! s:GetCurrentSynName(lnum) - if empty(getline(a:lnum)) - " Improves the accuracy of string detection when a newline is - " entered while in insert mode. - let strline = a:lnum - 1 - return s:GetSynIdName(strline, strlen(getline(strline))) - else - return s:GetSynIdName(a:lnum, 1) - endif -endfunction - function! s:GetClojureIndent() - " Move cursor to the first column of the line we want to indent. - call cursor(v:lnum, 1) - - let s:best_match = ['top', [0, 0]] - - let synname = s:GetCurrentSynName(v:lnum) - if synname =~? 'string' - call s:CheckPair('str', '"', '"', function('NotStringDelimiter')) - " Sometimes, string highlighting does not kick in correctly, - " until after this first "s:CheckPair" call, so we have to - " detect and attempt an automatic correction. - let new_synname = s:GetCurrentSynName(v:lnum) - if new_synname !=# synname - echoerr 'Misdetected string! Retrying...' - let s:best_match = ['top', [0, 0]] - let synname = new_synname - endif - endif - - if synname =~? 'string' - " We already checked this above, so pass through this block. - elseif synname =~? 'regex' - call s:CheckPair('rex', '#\zs"', '"', function('NotRegexpDelimiter')) - else - let IgnoredRegionFn = function('IgnoredRegion') - if bufname() =~? '\.edn$' - " If EDN file, check list pair last. - call s:CheckPair('map', '{', '}', IgnoredRegionFn) - call s:CheckPair('vec', '\[', '\]', IgnoredRegionFn) - call s:CheckPair('lst', '(', ')', IgnoredRegionFn) - else - " If CLJ file, check list pair first. - call s:CheckPair('lst', '(', ')', IgnoredRegionFn) - call s:CheckPair('map', '{', '}', IgnoredRegionFn) - call s:CheckPair('vec', '\[', '\]', IgnoredRegionFn) - endif - endif - " Calculate and return indent to use based on the matching form. - let [formtype, coord] = s:best_match - if formtype ==# 'top' | return 0 " At top level, no indent. - elseif formtype ==# 'lst' | return s:GetListIndent(coord) - elseif formtype ==# 'vec' | return coord[1] " Vector - elseif formtype ==# 'map' | return coord[1] " Map/set - elseif formtype ==# 'str' | return s:GetStringIndent(coord, 0) - elseif formtype ==# 'rex' | return s:GetStringIndent(coord, 1) - else | return -1 " Keep existing indent. + let [formtype, coord] = s:InverseRead(v:lnum) + if formtype ==# '^' | return 0 " At top-level, no indent. + elseif formtype ==# '(' | return s:GetListIndent(coord) + elseif formtype ==# '[' | return coord[1] " Vector + elseif formtype ==# '{' | return coord[1] " Map/set + elseif formtype ==# '"' | return s:GetStringIndent(coord, 0) + elseif formtype ==# '#"' | return s:GetStringIndent(coord, 1) + else | return -1 " Keep existing indent. endif endfunction -if exists("*searchpairpos") - setlocal indentexpr=s:GetClojureIndent() -else - " If "searchpairpos" is not available, fallback to Lisp indenting. - setlocal lisp -endif +" TODO: lispoptions if exists. +setlocal indentexpr=s:GetClojureIndent() let &cpoptions = s:save_cpo unlet! s:save_cpo From ee2acc20b1b23c76a4f232e344673c61345b0704 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Mon, 1 May 2023 00:28:33 +0100 Subject: [PATCH 19/88] Completed the mini Clojure reader as the core of the indentation system Some refactoring should be possible here and further optimisations. Once all optimisations I can think of have been implemented, I'll try writing an alternate Vim9 script version. (The syntax highlight group checks used in previous implementations of the indentation code was the core bottleneck, so a Vim9 script version would not have been much faster.) --- indent/clojure.vim | 128 +++++++++++++++++++++++++++------------------ 1 file changed, 76 insertions(+), 52 deletions(-) diff --git a/indent/clojure.vim b/indent/clojure.vim index d888ca8..9d049fe 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -21,32 +21,36 @@ setlocal noautoindent nosmartindent nolisp setlocal softtabstop=2 shiftwidth=2 expandtab setlocal indentkeys=!,o,O -" Returns true if char_idx is preceded by an odd number of backslashes. -function! s:IsEscaped(line_str, char_idx) - let ln = a:line_str[: a:char_idx - 1] +" TODO: After all optimisations create Vim9script variant of the core algorithm. + +" Returns "1" if position "i_char" in "line_str" is preceded by an odd number +" of backslash characters (i.e. escaped). +function! s:IsEscaped(line_str, i_char) + let ln = a:line_str[: a:i_char - 1] return (strlen(ln) - strlen(trim(ln, '\', 2))) % 2 endfunction -let s:pairs = {'(': ')', '[': ']', '{': '}'} - -" TODO: Maybe write a Vim9script version of this? -" Repeatedly search for tokens on the given line in reverse order building up -" a list of tokens and their positions. Ignores escaped tokens. -function! s:AnalyseLine(line_num) +" Repeatedly search for tokens on a given line (in reverse order) building up +" a list of tokens and their positions. Ignores escaped tokens. Does not +" care about strings, as that is handled by "s:InsideForm". +function! s:TokeniseLine(line_num) let tokens = [] let ln = getline(a:line_num) while 1 - " Due to legacy Vimscript being painfully slow, we literally - " have to move the cursor and perform searches which is - " ironically faster than for looping by character. + " We perform searches within the buffer (and move the cusor) + " for better performance than looping char by char in a line. let token = searchpos('[()\[\]{};"]', 'bW', a:line_num) + " No more matches, exit loop. if token == [0, 0] | break | endif + let t_idx = token[1] - 1 + + " Escaped character, ignore. if s:IsEscaped(ln, t_idx) | continue | endif - let t_char = ln[t_idx] + let t_char = ln[t_idx] if t_char ==# ';' " Comment found, reset the token list for this line. tokens = [] @@ -59,61 +63,79 @@ function! s:AnalyseLine(line_num) return tokens endfunction -" This should also be capable of figuring out if we're in a multi-line string -" or regex. -function! s:InverseRead(lnum) - let lnum = a:lnum - 1 +let s:pairs = {'(': ')', '[': ']', '{': '}'} + +" TODO: refactor this procedure and optimise. +" This procedure is essentially a lightweight Clojure reader. +function! s:InsideForm(lnum) + " Reset cursor to first column of the line we wish to indent. + call cursor(a:lnum, 1) + + " Token list looks like this: "[[delim, [line, col]], ...]". let tokens = [] + let first_string_pos = [] + let in_string = 0 + let lnum = a:lnum - 1 while lnum > 0 - call cursor(lnum + 1, 1) - let line_tokens = s:AnalyseLine(lnum) + " Reduce tokens from line "lnum" into "tokens". + for tk in s:TokeniseLine(lnum) + " Keep track of the first string delimiter we see, as + " we'll need it later for multi-line strings/regexps. + if first_string_pos == [] && tk[0] ==# '"' + let first_string_pos = tk[1] + endif - " let should_ignore = empty(a:tokens) ? 0 : (a:tokens[-1][0] ==# '"') + " When in string ignore other tokens. + if in_string && tk[0] !=# '"' + continue + else + let in_string = 0 + endif - " Reduce "tokens" and "line_tokens". - for t in line_tokens - " TODO: attempt early termination. + " TODO: early termination? if empty(tokens) - call add(tokens, t) - elseif t[0] ==# '"' && tokens[-1][0] ==# '"' - " TODO: track original start and ignore values - " inside strings. + call add(tokens, tk) + elseif tk[0] ==# '"' && tokens[-1][0] ==# '"' call remove(tokens, -1) - elseif get(s:pairs, t[0], '') ==# tokens[-1][0] + elseif get(s:pairs, tk[0], '') ==# tokens[-1][0] " Matching pair: drop the last item in tokens. call remove(tokens, -1) else " No match: append to token list. - call add(tokens, t) + call add(tokens, tk) endif endfor " echom 'Pass' lnum tokens if ! empty(tokens) && has_key(s:pairs, tokens[0][0]) - " TODO: on string match, check if string or regex. - " echom 'Match!' tokens[0] return tokens[0] endif let lnum -= 1 endwhile + if ! empty(tokens) && tokens[0][0] ==# '"' + " Must have been in a multi-line string or regular expression + " as the string was never closed. + return ['"', first_string_pos] + endif + return ['^', [0, 0]] " Default to top-level. endfunction +" Get the value of a configuration option. function! s:Conf(opt, default) return get(b:, a:opt, get(g:, a:opt, a:default)) endfunction +" Returns "1" when the previous operator used was "=" and is currently active. function! s:EqualsOperatorInEffect() - " Returns 1 when the previous operator used is "=" and is currently in - " effect (i.e. "state" includes "o"). return v:operator ==# '=' && state('o') ==# 'o' endfunction -function! s:GetStringIndent(delim_pos, is_regex) +function! s:StringIndent(delim_pos) " Mimic multi-line string indentation behaviour in VS Code and Emacs. let m = mode() if m ==# 'i' || (m ==# 'n' && ! s:EqualsOperatorInEffect()) @@ -124,43 +146,45 @@ function! s:GetStringIndent(delim_pos, is_regex) " 1: Indent in alignment with string start delimiter. if alignment == -1 | return 0 elseif alignment == 1 | return a:delim_pos[1] - else | return a:delim_pos[1] - (a:is_regex ? 2 : 1) + else + let col = a:delim_pos[1] + let is_regex = col > 1 && getline(a:delim_pos[0])[col - 2] ==# '#' + return col - (is_regex ? 2 : 1) endif else return -1 " Keep existing indent. endif endfunction -function! s:GetListIndent(delim_pos) - " TODO Begin analysis and apply rules! +function! s:ListIndent(delim_pos) " let lns = getline(delim_pos[0], v:lnum - 1) - let ln1 = getline(delim_pos[0]) - let sym = get(split(ln1[delim_pos[1]:], '[[:space:],;()\[\]{}@\\"^~`]', 1), 0, -1) + let ln1 = getline(a:delim_pos[0]) + let delim_col = a:delim_pos[1] + let sym = get(split(ln1[delim_col:], '[[:space:],;()\[\]{}@\\"^~`]', 1), 0, -1) if sym != -1 && ! empty(sym) && match(sym, '^[0-9:]') == -1 " TODO: align indentation. " TODO: lookup rules. - return delim_pos[1] + 1 " 2 space indentation + return delim_col + 1 " 2 space indentation endif " TODO: switch between 1 vs 2 space indentation. - return delim_pos[1] " 1 space indentation + return delim_col " 1 space indentation endfunction -function! s:GetClojureIndent() +function! s:ClojureIndent() " Calculate and return indent to use based on the matching form. - let [formtype, coord] = s:InverseRead(v:lnum) - if formtype ==# '^' | return 0 " At top-level, no indent. - elseif formtype ==# '(' | return s:GetListIndent(coord) - elseif formtype ==# '[' | return coord[1] " Vector - elseif formtype ==# '{' | return coord[1] " Map/set - elseif formtype ==# '"' | return s:GetStringIndent(coord, 0) - elseif formtype ==# '#"' | return s:GetStringIndent(coord, 1) - else | return -1 " Keep existing indent. + let [form, pos] = s:InsideForm(v:lnum) + if form ==# '^' | return 0 " At top-level, no indent. + elseif form ==# '(' | return s:ListIndent(pos) + elseif form ==# '[' | return pos[1] + elseif form ==# '{' | return pos[1] + elseif form ==# '"' | return s:StringIndent(pos) + else | return -1 " Keep existing indent. endif endfunction -" TODO: lispoptions if exists. -setlocal indentexpr=s:GetClojureIndent() +" TODO: set lispoptions if exists. +setlocal indentexpr=s:ClojureIndent() let &cpoptions = s:save_cpo unlet! s:save_cpo From bdbc2817ac57fa58a5d7c01c0014d5485b58ad9b Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Mon, 1 May 2023 12:49:43 +0100 Subject: [PATCH 20/88] Reader indent algorithm perf has surpassed the prev syntax highlight one The performance of the new reader-like indentation algorithm has now surpassed the performance of my previous syntax highlighting approach with better accuracy and no hacky code required. --- indent/clojure.vim | 43 ++++++++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/indent/clojure.vim b/indent/clojure.vim index 9d049fe..e25abc7 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -21,6 +21,9 @@ setlocal noautoindent nosmartindent nolisp setlocal softtabstop=2 shiftwidth=2 expandtab setlocal indentkeys=!,o,O +" NOTE: To debug this code, make sure to "set debug+=msg" otherwise errors +" will occur silently. + " TODO: After all optimisations create Vim9script variant of the core algorithm. " Returns "1" if position "i_char" in "line_str" is preceded by an odd number @@ -65,8 +68,9 @@ endfunction let s:pairs = {'(': ')', '[': ']', '{': '}'} -" TODO: refactor this procedure and optimise. -" This procedure is essentially a lightweight Clojure reader. +" This procedure is kind of like a really lightweight Clojure reader. It +" looks at the lines above the current line, tokenises them (from right to +" left), and performs reductions to find the parent form and where it is. function! s:InsideForm(lnum) " Reset cursor to first column of the line we wish to indent. call cursor(a:lnum, 1) @@ -80,25 +84,29 @@ function! s:InsideForm(lnum) while lnum > 0 " Reduce tokens from line "lnum" into "tokens". for tk in s:TokeniseLine(lnum) - " Keep track of the first string delimiter we see, as - " we'll need it later for multi-line strings/regexps. - if first_string_pos == [] && tk[0] ==# '"' - let first_string_pos = tk[1] - endif + if tk[0] ==# '"' + " Keep track of the first string delimiter we + " see, as we'll need it later for multi-line + " strings/regexps. + if first_string_pos == [] + let first_string_pos = tk[1] + endif + + if ! empty(tokens) && tokens[-1][0] ==# '"' + let in_string = 0 + call remove(tokens, -1) + else + let in_string = 1 + call add(tokens, tk) + endif - " When in string ignore other tokens. - if in_string && tk[0] !=# '"' continue - else - let in_string = 0 endif - " TODO: early termination? - if empty(tokens) - call add(tokens, tk) - elseif tk[0] ==# '"' && tokens[-1][0] ==# '"' - call remove(tokens, -1) - elseif get(s:pairs, tk[0], '') ==# tokens[-1][0] + " When in string ignore other tokens. + if in_string | continue | endif + + if ! empty(tokens) && get(s:pairs, tk[0], '') ==# tokens[-1][0] " Matching pair: drop the last item in tokens. call remove(tokens, -1) else @@ -184,6 +192,7 @@ function! s:ClojureIndent() endfunction " TODO: set lispoptions if exists. +" https://github.com/vim/vim/commit/49846fb1a31de99f49d6a7e70efe685197423c84 setlocal indentexpr=s:ClojureIndent() let &cpoptions = s:save_cpo From 8ee73c55367433d55c06be5cd0342e222fc0b8f0 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Mon, 1 May 2023 13:42:01 +0100 Subject: [PATCH 21/88] Small refactor to improve code clarity in indentation code --- indent/clojure.vim | 35 ++++++++++++++--------------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/indent/clojure.vim b/indent/clojure.vim index e25abc7..a4c90ee 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -85,28 +85,23 @@ function! s:InsideForm(lnum) " Reduce tokens from line "lnum" into "tokens". for tk in s:TokeniseLine(lnum) if tk[0] ==# '"' - " Keep track of the first string delimiter we - " see, as we'll need it later for multi-line - " strings/regexps. - if first_string_pos == [] - let first_string_pos = tk[1] - endif - - if ! empty(tokens) && tokens[-1][0] ==# '"' + if in_string let in_string = 0 call remove(tokens, -1) else let in_string = 1 call add(tokens, tk) - endif - - continue - endif - - " When in string ignore other tokens. - if in_string | continue | endif - if ! empty(tokens) && get(s:pairs, tk[0], '') ==# tokens[-1][0] + " Track the first string delimiter we + " see, as we may need it later for + " multi-line strings/regexps. + if first_string_pos == [] + let first_string_pos = tk + endif + endif + elseif in_string + " In string: ignore other tokens. + elseif ! empty(tokens) && get(s:pairs, tk[0], '') ==# tokens[-1][0] " Matching pair: drop the last item in tokens. call remove(tokens, -1) else @@ -115,9 +110,8 @@ function! s:InsideForm(lnum) endif endfor - " echom 'Pass' lnum tokens - if ! empty(tokens) && has_key(s:pairs, tokens[0][0]) + " Match found! return tokens[0] endif @@ -127,7 +121,7 @@ function! s:InsideForm(lnum) if ! empty(tokens) && tokens[0][0] ==# '"' " Must have been in a multi-line string or regular expression " as the string was never closed. - return ['"', first_string_pos] + return first_string_pos endif return ['^', [0, 0]] " Default to top-level. @@ -191,8 +185,7 @@ function! s:ClojureIndent() endif endfunction -" TODO: set lispoptions if exists. -" https://github.com/vim/vim/commit/49846fb1a31de99f49d6a7e70efe685197423c84 +" TODO: setl lisp lispoptions=expr:1 if exists. "has('patch-9.0.0761')" setlocal indentexpr=s:ClojureIndent() let &cpoptions = s:save_cpo From a4beb523588ea2d6ca5168bd4e83f7e8613a13c9 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Mon, 29 May 2023 22:20:51 +0100 Subject: [PATCH 22/88] Fix indentation bug during comment detection --- indent/clojure.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indent/clojure.vim b/indent/clojure.vim index a4c90ee..14e1111 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -56,7 +56,7 @@ function! s:TokeniseLine(line_num) let t_char = ln[t_idx] if t_char ==# ';' " Comment found, reset the token list for this line. - tokens = [] + let tokens = [] elseif t_char =~# '[()\[\]{}"]' " Add token to the list. call add(tokens, [t_char, token]) From 3d8197e904f36bba341b0425de706c4bb8bd1b74 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Sat, 10 Jun 2023 21:21:58 +0100 Subject: [PATCH 23/88] Fix accidental detection of backslashes as tokens Previously backslashes were accidentally detected as tokens by the indentation tokeniser. This meant that character literals, would break indentation of everything after them. --- indent/clojure.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indent/clojure.vim b/indent/clojure.vim index 14e1111..338404b 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -43,7 +43,7 @@ function! s:TokeniseLine(line_num) while 1 " We perform searches within the buffer (and move the cusor) " for better performance than looping char by char in a line. - let token = searchpos('[()\[\]{};"]', 'bW', a:line_num) + let token = searchpos('[()[\]{};"]', 'bW', a:line_num) " No more matches, exit loop. if token == [0, 0] | break | endif From 101c9a4b83ff8ab21fc9f8c2d0659ddbacd4887c Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Mon, 26 Jun 2023 13:46:51 +0100 Subject: [PATCH 24/88] Update indent comments and move `s:Conf` function to the top --- indent/clojure.vim | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/indent/clojure.vim b/indent/clojure.vim index 338404b..3e99808 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -24,7 +24,10 @@ setlocal indentkeys=!,o,O " NOTE: To debug this code, make sure to "set debug+=msg" otherwise errors " will occur silently. -" TODO: After all optimisations create Vim9script variant of the core algorithm. +" Get the value of a configuration option. +function! s:Conf(opt, default) + return get(b:, a:opt, get(g:, a:opt, a:default)) +endfunction " Returns "1" if position "i_char" in "line_str" is preceded by an odd number " of backslash characters (i.e. escaped). @@ -33,9 +36,10 @@ function! s:IsEscaped(line_str, i_char) return (strlen(ln) - strlen(trim(ln, '\', 2))) % 2 endfunction -" Repeatedly search for tokens on a given line (in reverse order) building up -" a list of tokens and their positions. Ignores escaped tokens. Does not -" care about strings, as that is handled by "s:InsideForm". +" Repeatedly search for indentation significant Clojure tokens on a given line +" (in reverse order) building up a list of tokens and their positions. +" Ignores escaped tokens. Does not care about strings, which is handled by +" "s:InsideForm". function! s:TokeniseLine(line_num) let tokens = [] let ln = getline(a:line_num) @@ -68,9 +72,10 @@ endfunction let s:pairs = {'(': ')', '[': ']', '{': '}'} -" This procedure is kind of like a really lightweight Clojure reader. It -" looks at the lines above the current line, tokenises them (from right to -" left), and performs reductions to find the parent form and where it is. +" This procedure is kind of like a really lightweight Clojure reader that +" analyses from the inside out. It looks at the lines above the current line, +" tokenises them (from right to left), and performs reductions to find the +" parent form and where it is. function! s:InsideForm(lnum) " Reset cursor to first column of the line we wish to indent. call cursor(a:lnum, 1) @@ -127,11 +132,6 @@ function! s:InsideForm(lnum) return ['^', [0, 0]] " Default to top-level. endfunction -" Get the value of a configuration option. -function! s:Conf(opt, default) - return get(b:, a:opt, get(g:, a:opt, a:default)) -endfunction - " Returns "1" when the previous operator used was "=" and is currently active. function! s:EqualsOperatorInEffect() return v:operator ==# '=' && state('o') ==# 'o' From 35e02340c7554fb88247ded1d6475681dbf7f1aa Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Mon, 26 Jun 2023 15:31:29 +0100 Subject: [PATCH 25/88] Add basic function parameter alignment indentation This likely still needs a lot of improvements. --- indent/clojure.vim | 58 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 48 insertions(+), 10 deletions(-) diff --git a/indent/clojure.vim b/indent/clojure.vim index 3e99808..d40cf62 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -159,18 +159,56 @@ function! s:StringIndent(delim_pos) endfunction function! s:ListIndent(delim_pos) - " let lns = getline(delim_pos[0], v:lnum - 1) - let ln1 = getline(a:delim_pos[0]) - let delim_col = a:delim_pos[1] - let sym = get(split(ln1[delim_col:], '[[:space:],;()\[\]{}@\\"^~`]', 1), 0, -1) - if sym != -1 && ! empty(sym) && match(sym, '^[0-9:]') == -1 - " TODO: align indentation. - " TODO: lookup rules. - return delim_col + 1 " 2 space indentation + " TODO: attempt to extend "s:InsideForm" to provide information about + " the subforms within the form being formatted to avoid second parsing + " step. + + " TODO: The overcomplicated list indentation rules in Clojure! + " 1. If starts with a symbol or keyword: extract it. + " 1.1. Look up in rules table. + " 1.1.1. See number for forms forward to lookup. + " 1.1.2. Start parsing forwards "x" forms. (Skip metadata) + " 1.1.3. Apply indentation. + " 1.2. Not found, goto 2. + " 2. Else, format like a function. + " 2.1. Is first operand on the same line? (Treat metadata as args) + " 2.1.1. Indent subsequent lines to align with first operand. + " 2.2. Else. + " 2.2.1. Indent 1 or 2 spaces. + + call cursor(a:delim_pos) + let ln = getline(a:delim_pos[0]) + let base_indent = a:delim_pos[1] + + " 1. TODO: Macro/rule indentation + " let syms = split(ln[base_indent:], '[[:space:],;()\[\]{}@\\"^~`]', 1) + " TODO: complex indentation (e.g. letfn) + " TODO: namespaces. + + " 2. Function indentation + let cur_pos = [a:delim_pos[0], a:delim_pos[1] + 1] + call cursor(cur_pos) + + let ch = ln[cur_pos[1] - 1] + if ch =~# '[([{]' + normal! %w + elseif ch !~# '[;"]' + normal! w endif - " TODO: switch between 1 vs 2 space indentation. - return delim_col " 1 space indentation + let new_cur_pos = getcurpos()[1:2] + + " TODO: option to disable operand-alignment. + if cur_pos[0] == new_cur_pos[0] && cur_pos[1] != new_cur_pos[1] + " Align operands + return new_cur_pos[1] - 1 + endif + + " TODO: create an alternative option with a better name. + " + " Base indentation for forms. When "clojure_align_subforms" is "1", + " use 1 space indentation, otherwise 2 space indentation. + return base_indent + ! s:Conf('clojure_align_subforms', 1) endfunction function! s:ClojureIndent() From 787e1e8f74b030c2dd759d55d93988bc5a150305 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Mon, 26 Jun 2023 20:44:42 +0100 Subject: [PATCH 26/88] Replace `clojure_align_subforms` with `clojure_indent_style` --- indent/clojure.vim | 65 ++++++++++++++++++++++------------------------ 1 file changed, 31 insertions(+), 34 deletions(-) diff --git a/indent/clojure.vim b/indent/clojure.vim index d40cf62..e178f8b 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -163,52 +163,49 @@ function! s:ListIndent(delim_pos) " the subforms within the form being formatted to avoid second parsing " step. - " TODO: The overcomplicated list indentation rules in Clojure! - " 1. If starts with a symbol or keyword: extract it. - " 1.1. Look up in rules table. - " 1.1.1. See number for forms forward to lookup. - " 1.1.2. Start parsing forwards "x" forms. (Skip metadata) - " 1.1.3. Apply indentation. - " 1.2. Not found, goto 2. - " 2. Else, format like a function. - " 2.1. Is first operand on the same line? (Treat metadata as args) - " 2.1.1. Indent subsequent lines to align with first operand. - " 2.2. Else. - " 2.2.1. Indent 1 or 2 spaces. - call cursor(a:delim_pos) let ln = getline(a:delim_pos[0]) let base_indent = a:delim_pos[1] " 1. TODO: Macro/rule indentation - " let syms = split(ln[base_indent:], '[[:space:],;()\[\]{}@\\"^~`]', 1) + " if starts with a symbol or keyword: extract it. + " - Look up in rules table. + " - See number for forms forward to lookup. + " - Start parsing forwards "x" forms. (Skip metadata) + " - Apply indentation. + " else, not found, go to 2. " TODO: complex indentation (e.g. letfn) " TODO: namespaces. + " let syms = split(ln[base_indent:], '[[:space:],;()\[\]{}@\\"^~`]', 1) " 2. Function indentation - let cur_pos = [a:delim_pos[0], a:delim_pos[1] + 1] - call cursor(cur_pos) - - let ch = ln[cur_pos[1] - 1] - if ch =~# '[([{]' - normal! %w - elseif ch !~# '[;"]' - normal! w - endif - - let new_cur_pos = getcurpos()[1:2] + " if first operand is on the same line? (Treat metadata as args.) + " - Indent subsequent lines to align with first operand. + " else + " - Indent 1 or 2 spaces. + let indent_style = s:Conf('clojure_indent_style', 'always-align') + if indent_style !=# 'always-indent' + let init_col = a:delim_pos[1] + 1 + call cursor(a:delim_pos[0], init_col) + + " TODO: replace cursor translation with searching? + let ch = ln[base_indent] + if ch ==# '(' || ch ==# '[' || ch ==# '{' + normal! %w + elseif ch !=# ';' && ch !=# '"' + normal! w + endif - " TODO: option to disable operand-alignment. - if cur_pos[0] == new_cur_pos[0] && cur_pos[1] != new_cur_pos[1] - " Align operands - return new_cur_pos[1] - 1 + let cur_pos = getcurpos()[1:2] + if a:delim_pos[0] == cur_pos[0] && init_col != cur_pos[1] + " Align operands. + return cur_pos[1] - 1 + endif endif - " TODO: create an alternative option with a better name. - " - " Base indentation for forms. When "clojure_align_subforms" is "1", - " use 1 space indentation, otherwise 2 space indentation. - return base_indent + ! s:Conf('clojure_align_subforms', 1) + " Fallback indentation for operands. When "clojure_indent_style" is + " "always-align", use 1 space indentation, else 2 space indentation. + return base_indent + (indent_style !=# 'always-align') endfunction function! s:ClojureIndent() From bfce7248b352fc41aa09bdc8c3c022c8d6b35253 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Mon, 26 Jun 2023 22:22:57 +0100 Subject: [PATCH 27/88] Update and add a bunch of indentation tests --- .../indent-test-cases/basic-sexp/in.clj | 5 -- .../indent-test-cases/basic-sexp/out.clj | 5 -- .../indent-test-cases/dispach-macro/in.clj | 29 ---------- .../indent-test-cases/dispach-macro/out.clj | 29 ---------- .../inherit-indentation/config.edn | 3 - .../inherit-indentation/in.clj | 7 --- .../inherit-indentation/out.clj | 13 ----- .../in.clj | 0 .../out.clj | 0 .../reader-conditional/out.clj | 2 +- .../s-expr_align-arguments/config.edn | 1 + .../s-expr_align-arguments/in.clj | 58 +++++++++++++++++++ .../s-expr_align-arguments/out.clj | 58 +++++++++++++++++++ .../s-expr_always-align/config.edn | 1 + .../s-expr_always-align/in.clj | 58 +++++++++++++++++++ .../s-expr_always-align/out.clj | 58 +++++++++++++++++++ .../s-expr_always-indent/config.edn | 1 + .../s-expr_always-indent/in.clj | 58 +++++++++++++++++++ .../s-expr_always-indent/out.clj | 58 +++++++++++++++++++ .../side-effects-in-indentexpr/config.edn | 3 - .../side-effects-in-indentexpr/in.clj | 3 - .../side-effects-in-indentexpr/out.clj | 3 - .../indent-test-cases/strings/config.edn | 4 ++ .../indent-test-cases/strings/in.clj | 27 +++++++++ .../indent-test-cases/strings/out.clj | 33 +++++++++++ .../strings_align/config.edn | 4 ++ .../indent-test-cases/strings_align/in.clj | 27 +++++++++ .../indent-test-cases/strings_align/out.clj | 33 +++++++++++ 28 files changed, 480 insertions(+), 101 deletions(-) delete mode 100644 clj/resources/indent-test-cases/basic-sexp/in.clj delete mode 100644 clj/resources/indent-test-cases/basic-sexp/out.clj delete mode 100644 clj/resources/indent-test-cases/dispach-macro/in.clj delete mode 100644 clj/resources/indent-test-cases/dispach-macro/out.clj delete mode 100644 clj/resources/indent-test-cases/inherit-indentation/config.edn delete mode 100644 clj/resources/indent-test-cases/inherit-indentation/in.clj delete mode 100644 clj/resources/indent-test-cases/inherit-indentation/out.clj rename clj/resources/indent-test-cases/{multibyte-indentation => multibyte}/in.clj (100%) rename clj/resources/indent-test-cases/{multibyte-indentation => multibyte}/out.clj (100%) create mode 100644 clj/resources/indent-test-cases/s-expr_align-arguments/config.edn create mode 100644 clj/resources/indent-test-cases/s-expr_align-arguments/in.clj create mode 100644 clj/resources/indent-test-cases/s-expr_align-arguments/out.clj create mode 100644 clj/resources/indent-test-cases/s-expr_always-align/config.edn create mode 100644 clj/resources/indent-test-cases/s-expr_always-align/in.clj create mode 100644 clj/resources/indent-test-cases/s-expr_always-align/out.clj create mode 100644 clj/resources/indent-test-cases/s-expr_always-indent/config.edn create mode 100644 clj/resources/indent-test-cases/s-expr_always-indent/in.clj create mode 100644 clj/resources/indent-test-cases/s-expr_always-indent/out.clj delete mode 100644 clj/resources/indent-test-cases/side-effects-in-indentexpr/config.edn delete mode 100644 clj/resources/indent-test-cases/side-effects-in-indentexpr/in.clj delete mode 100644 clj/resources/indent-test-cases/side-effects-in-indentexpr/out.clj create mode 100644 clj/resources/indent-test-cases/strings/config.edn create mode 100644 clj/resources/indent-test-cases/strings/in.clj create mode 100644 clj/resources/indent-test-cases/strings/out.clj create mode 100644 clj/resources/indent-test-cases/strings_align/config.edn create mode 100644 clj/resources/indent-test-cases/strings_align/in.clj create mode 100644 clj/resources/indent-test-cases/strings_align/out.clj diff --git a/clj/resources/indent-test-cases/basic-sexp/in.clj b/clj/resources/indent-test-cases/basic-sexp/in.clj deleted file mode 100644 index 3551b97..0000000 --- a/clj/resources/indent-test-cases/basic-sexp/in.clj +++ /dev/null @@ -1,5 +0,0 @@ -(ns test-basic-sexp-indent - "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod - tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, - quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo - consequat.") diff --git a/clj/resources/indent-test-cases/basic-sexp/out.clj b/clj/resources/indent-test-cases/basic-sexp/out.clj deleted file mode 100644 index 3551b97..0000000 --- a/clj/resources/indent-test-cases/basic-sexp/out.clj +++ /dev/null @@ -1,5 +0,0 @@ -(ns test-basic-sexp-indent - "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod - tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, - quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo - consequat.") diff --git a/clj/resources/indent-test-cases/dispach-macro/in.clj b/clj/resources/indent-test-cases/dispach-macro/in.clj deleted file mode 100644 index b59b91e..0000000 --- a/clj/resources/indent-test-cases/dispach-macro/in.clj +++ /dev/null @@ -1,29 +0,0 @@ -(#(foo) -bar) - -(#(foo -bar)) - -(#(foo bar -a)) - -(#(foo bar) -a) - -(#{foo} -bar) - -(#{foo -bar}) - -(#{foo bar} -a) - -(#_(foo) -bar) - -(#_(foo -bar)) - -(#_(foo bar) -a) diff --git a/clj/resources/indent-test-cases/dispach-macro/out.clj b/clj/resources/indent-test-cases/dispach-macro/out.clj deleted file mode 100644 index 4f7356d..0000000 --- a/clj/resources/indent-test-cases/dispach-macro/out.clj +++ /dev/null @@ -1,29 +0,0 @@ -(#(foo) - bar) - -(#(foo - bar)) - -(#(foo bar - a)) - -(#(foo bar) - a) - -(#{foo} - bar) - -(#{foo - bar}) - -(#{foo bar} - a) - -(#_(foo) - bar) - -(#_(foo - bar)) - -(#_(foo bar) - a) diff --git a/clj/resources/indent-test-cases/inherit-indentation/config.edn b/clj/resources/indent-test-cases/inherit-indentation/config.edn deleted file mode 100644 index d972b2b..0000000 --- a/clj/resources/indent-test-cases/inherit-indentation/config.edn +++ /dev/null @@ -1,3 +0,0 @@ -{:indent? false - :extra-cmds ["normal! gg" - "exec \"normal! /α\\s\\Oa\\/β\\s\\\\\\\\\\\\\\\\\\b\\c\\\\d\\\""]} diff --git a/clj/resources/indent-test-cases/inherit-indentation/in.clj b/clj/resources/indent-test-cases/inherit-indentation/in.clj deleted file mode 100644 index 9d09168..0000000 --- a/clj/resources/indent-test-cases/inherit-indentation/in.clj +++ /dev/null @@ -1,7 +0,0 @@ -(foo bar - "This string has unpaired brackets [ - and is indented weirdly." -α - [β]) - -;; vim:ft=clojure: diff --git a/clj/resources/indent-test-cases/inherit-indentation/out.clj b/clj/resources/indent-test-cases/inherit-indentation/out.clj deleted file mode 100644 index e4851a1..0000000 --- a/clj/resources/indent-test-cases/inherit-indentation/out.clj +++ /dev/null @@ -1,13 +0,0 @@ -(foo bar - "This string has unpaired brackets [ - and is indented weirdly." - a - - [ - - b - c - - d]) - -;; vim:ft=clojure: diff --git a/clj/resources/indent-test-cases/multibyte-indentation/in.clj b/clj/resources/indent-test-cases/multibyte/in.clj similarity index 100% rename from clj/resources/indent-test-cases/multibyte-indentation/in.clj rename to clj/resources/indent-test-cases/multibyte/in.clj diff --git a/clj/resources/indent-test-cases/multibyte-indentation/out.clj b/clj/resources/indent-test-cases/multibyte/out.clj similarity index 100% rename from clj/resources/indent-test-cases/multibyte-indentation/out.clj rename to clj/resources/indent-test-cases/multibyte/out.clj diff --git a/clj/resources/indent-test-cases/reader-conditional/out.clj b/clj/resources/indent-test-cases/reader-conditional/out.clj index 5c5bfc2..5ad2990 100644 --- a/clj/resources/indent-test-cases/reader-conditional/out.clj +++ b/clj/resources/indent-test-cases/reader-conditional/out.clj @@ -8,4 +8,4 @@ (goog.date.UtcDateTime. (.getYear x) (.getMonth x) (.getDate x))))) #?@(:clj [5 6 7 8] - :cljs [1 2 3 4]))) + :cljs [1 2 3 4]) diff --git a/clj/resources/indent-test-cases/s-expr_align-arguments/config.edn b/clj/resources/indent-test-cases/s-expr_align-arguments/config.edn new file mode 100644 index 0000000..8142eae --- /dev/null +++ b/clj/resources/indent-test-cases/s-expr_align-arguments/config.edn @@ -0,0 +1 @@ +{:extra-cmds ["let g:clojure_indent_style = 'align-arguments'"]} diff --git a/clj/resources/indent-test-cases/s-expr_align-arguments/in.clj b/clj/resources/indent-test-cases/s-expr_align-arguments/in.clj new file mode 100644 index 0000000..34e146d --- /dev/null +++ b/clj/resources/indent-test-cases/s-expr_align-arguments/in.clj @@ -0,0 +1,58 @@ + (assoc {:foo 1} + :bar [2 + 3 + 4] + :biz 5) + + [:foo :bar +:biz :baz + "asdf" + 'a345r + 1234] + + {:hello "world" + :example "test" + 1234 'cake + [qwer + asdf + zxcv] #{1 2 + 3 4 :bar}} + + (qwer + [12 + 34 +56] + xczv) + + ((constantly +) +1 + 2) + + (filter + #(= 0 (mod % + 2)) + (range 1 10)) + +(#(foo) + bar) + + (#(foo + bar)) + + (#(foo bar +a)) + +(#(foo bar) +a) + +#_(:foo + {:foo 1}) + +(#_(foo) + bar) + + (#_(foo + bar)) + + (#_(foo bar) + a) diff --git a/clj/resources/indent-test-cases/s-expr_align-arguments/out.clj b/clj/resources/indent-test-cases/s-expr_align-arguments/out.clj new file mode 100644 index 0000000..fb393d3 --- /dev/null +++ b/clj/resources/indent-test-cases/s-expr_align-arguments/out.clj @@ -0,0 +1,58 @@ +(assoc {:foo 1} + :bar [2 + 3 + 4] + :biz 5) + +[:foo :bar + :biz :baz + "asdf" + 'a345r + 1234] + +{:hello "world" + :example "test" + 1234 'cake + [qwer + asdf + zxcv] #{1 2 + 3 4 :bar}} + +(qwer + [12 + 34 + 56] + xczv) + +((constantly +) + 1 + 2) + +(filter + #(= 0 (mod % + 2)) + (range 1 10)) + +(#(foo) + bar) + +(#(foo + bar)) + +(#(foo bar + a)) + +(#(foo bar) + a) + +#_(:foo + {:foo 1}) + +(#_(foo) + bar) + +(#_(foo + bar)) + +(#_(foo bar) + a) diff --git a/clj/resources/indent-test-cases/s-expr_always-align/config.edn b/clj/resources/indent-test-cases/s-expr_always-align/config.edn new file mode 100644 index 0000000..b0a5737 --- /dev/null +++ b/clj/resources/indent-test-cases/s-expr_always-align/config.edn @@ -0,0 +1 @@ +{:extra-cmds []} diff --git a/clj/resources/indent-test-cases/s-expr_always-align/in.clj b/clj/resources/indent-test-cases/s-expr_always-align/in.clj new file mode 100644 index 0000000..34e146d --- /dev/null +++ b/clj/resources/indent-test-cases/s-expr_always-align/in.clj @@ -0,0 +1,58 @@ + (assoc {:foo 1} + :bar [2 + 3 + 4] + :biz 5) + + [:foo :bar +:biz :baz + "asdf" + 'a345r + 1234] + + {:hello "world" + :example "test" + 1234 'cake + [qwer + asdf + zxcv] #{1 2 + 3 4 :bar}} + + (qwer + [12 + 34 +56] + xczv) + + ((constantly +) +1 + 2) + + (filter + #(= 0 (mod % + 2)) + (range 1 10)) + +(#(foo) + bar) + + (#(foo + bar)) + + (#(foo bar +a)) + +(#(foo bar) +a) + +#_(:foo + {:foo 1}) + +(#_(foo) + bar) + + (#_(foo + bar)) + + (#_(foo bar) + a) diff --git a/clj/resources/indent-test-cases/s-expr_always-align/out.clj b/clj/resources/indent-test-cases/s-expr_always-align/out.clj new file mode 100644 index 0000000..e83350c --- /dev/null +++ b/clj/resources/indent-test-cases/s-expr_always-align/out.clj @@ -0,0 +1,58 @@ +(assoc {:foo 1} + :bar [2 + 3 + 4] + :biz 5) + +[:foo :bar + :biz :baz + "asdf" + 'a345r + 1234] + +{:hello "world" + :example "test" + 1234 'cake + [qwer + asdf + zxcv] #{1 2 + 3 4 :bar}} + +(qwer + [12 + 34 + 56] + xczv) + +((constantly +) + 1 + 2) + +(filter + #(= 0 (mod % + 2)) + (range 1 10)) + +(#(foo) + bar) + +(#(foo + bar)) + +(#(foo bar + a)) + +(#(foo bar) + a) + +#_(:foo + {:foo 1}) + +(#_(foo) + bar) + +(#_(foo + bar)) + +(#_(foo bar) + a) diff --git a/clj/resources/indent-test-cases/s-expr_always-indent/config.edn b/clj/resources/indent-test-cases/s-expr_always-indent/config.edn new file mode 100644 index 0000000..6eddb66 --- /dev/null +++ b/clj/resources/indent-test-cases/s-expr_always-indent/config.edn @@ -0,0 +1 @@ +{:extra-cmds ["let g:clojure_indent_style = 'always-indent'"]} diff --git a/clj/resources/indent-test-cases/s-expr_always-indent/in.clj b/clj/resources/indent-test-cases/s-expr_always-indent/in.clj new file mode 100644 index 0000000..34e146d --- /dev/null +++ b/clj/resources/indent-test-cases/s-expr_always-indent/in.clj @@ -0,0 +1,58 @@ + (assoc {:foo 1} + :bar [2 + 3 + 4] + :biz 5) + + [:foo :bar +:biz :baz + "asdf" + 'a345r + 1234] + + {:hello "world" + :example "test" + 1234 'cake + [qwer + asdf + zxcv] #{1 2 + 3 4 :bar}} + + (qwer + [12 + 34 +56] + xczv) + + ((constantly +) +1 + 2) + + (filter + #(= 0 (mod % + 2)) + (range 1 10)) + +(#(foo) + bar) + + (#(foo + bar)) + + (#(foo bar +a)) + +(#(foo bar) +a) + +#_(:foo + {:foo 1}) + +(#_(foo) + bar) + + (#_(foo + bar)) + + (#_(foo bar) + a) diff --git a/clj/resources/indent-test-cases/s-expr_always-indent/out.clj b/clj/resources/indent-test-cases/s-expr_always-indent/out.clj new file mode 100644 index 0000000..414e2bd --- /dev/null +++ b/clj/resources/indent-test-cases/s-expr_always-indent/out.clj @@ -0,0 +1,58 @@ +(assoc {:foo 1} + :bar [2 + 3 + 4] + :biz 5) + +[:foo :bar + :biz :baz + "asdf" + 'a345r + 1234] + +{:hello "world" + :example "test" + 1234 'cake + [qwer + asdf + zxcv] #{1 2 + 3 4 :bar}} + +(qwer + [12 + 34 + 56] + xczv) + +((constantly +) + 1 + 2) + +(filter + #(= 0 (mod % + 2)) + (range 1 10)) + +(#(foo) + bar) + +(#(foo + bar)) + +(#(foo bar + a)) + +(#(foo bar) + a) + +#_(:foo + {:foo 1}) + +(#_(foo) + bar) + +(#_(foo + bar)) + +(#_(foo bar) + a) diff --git a/clj/resources/indent-test-cases/side-effects-in-indentexpr/config.edn b/clj/resources/indent-test-cases/side-effects-in-indentexpr/config.edn deleted file mode 100644 index 8bbbf0f..0000000 --- a/clj/resources/indent-test-cases/side-effects-in-indentexpr/config.edn +++ /dev/null @@ -1,3 +0,0 @@ -{:indent? false - :extra-cmds ["normal! gg" - "exec \"normal! /α\\:call GetClojureIndent()\\rxj:call GetClojureIndent()\\ry\""]} diff --git a/clj/resources/indent-test-cases/side-effects-in-indentexpr/in.clj b/clj/resources/indent-test-cases/side-effects-in-indentexpr/in.clj deleted file mode 100644 index a3dcb24..0000000 --- a/clj/resources/indent-test-cases/side-effects-in-indentexpr/in.clj +++ /dev/null @@ -1,3 +0,0 @@ -(doseq [x (range 10) y (range 10)] - (println α) - (println β)) diff --git a/clj/resources/indent-test-cases/side-effects-in-indentexpr/out.clj b/clj/resources/indent-test-cases/side-effects-in-indentexpr/out.clj deleted file mode 100644 index 154c219..0000000 --- a/clj/resources/indent-test-cases/side-effects-in-indentexpr/out.clj +++ /dev/null @@ -1,3 +0,0 @@ -(doseq [x (range 10) y (range 10)] - (println x) - (println y)) diff --git a/clj/resources/indent-test-cases/strings/config.edn b/clj/resources/indent-test-cases/strings/config.edn new file mode 100644 index 0000000..6b367d4 --- /dev/null +++ b/clj/resources/indent-test-cases/strings/config.edn @@ -0,0 +1,4 @@ +{:extra-cmds ["let g:clojure_align_multiline_strings = 0" + "normal! G" + "normal! o\u000atest \"hello\u000aworld\"" + "normal! o\u000aregex #\"asdf\u000abar\""]} diff --git a/clj/resources/indent-test-cases/strings/in.clj b/clj/resources/indent-test-cases/strings/in.clj new file mode 100644 index 0000000..0efd8a2 --- /dev/null +++ b/clj/resources/indent-test-cases/strings/in.clj @@ -0,0 +1,27 @@ +"foo + bar" + + asdf dfa sdfasdf " +asdf" + +(asdf [foo] + "hel + lo asd + fasdfa + sdf + asdf + as + as + asdf + df + df + world") + + #{:foo :bar + :biz + "ba + z"} + + #"foo + bar + biz" diff --git a/clj/resources/indent-test-cases/strings/out.clj b/clj/resources/indent-test-cases/strings/out.clj new file mode 100644 index 0000000..49cb1f0 --- /dev/null +++ b/clj/resources/indent-test-cases/strings/out.clj @@ -0,0 +1,33 @@ +"foo + bar" + +asdf dfa sdfasdf " +asdf" + +(asdf [foo] + "hel + lo asd + fasdfa + sdf + asdf + as + as + asdf + df + df + world") + +#{:foo :bar + :biz + "ba + z"} + +#"foo + bar + biz" + +test "hello + world" + +regex #"asdf + bar" diff --git a/clj/resources/indent-test-cases/strings_align/config.edn b/clj/resources/indent-test-cases/strings_align/config.edn new file mode 100644 index 0000000..b1aff05 --- /dev/null +++ b/clj/resources/indent-test-cases/strings_align/config.edn @@ -0,0 +1,4 @@ +{:extra-cmds ["let g:clojure_align_multiline_strings = 1" + "normal! G" + "normal! o\u000atest \"hello\u000aworld\"" + "normal! o\u000aregex #\"asdf\u000abar\""]} diff --git a/clj/resources/indent-test-cases/strings_align/in.clj b/clj/resources/indent-test-cases/strings_align/in.clj new file mode 100644 index 0000000..0efd8a2 --- /dev/null +++ b/clj/resources/indent-test-cases/strings_align/in.clj @@ -0,0 +1,27 @@ +"foo + bar" + + asdf dfa sdfasdf " +asdf" + +(asdf [foo] + "hel + lo asd + fasdfa + sdf + asdf + as + as + asdf + df + df + world") + + #{:foo :bar + :biz + "ba + z"} + + #"foo + bar + biz" diff --git a/clj/resources/indent-test-cases/strings_align/out.clj b/clj/resources/indent-test-cases/strings_align/out.clj new file mode 100644 index 0000000..b9f361e --- /dev/null +++ b/clj/resources/indent-test-cases/strings_align/out.clj @@ -0,0 +1,33 @@ +"foo + bar" + +asdf dfa sdfasdf " +asdf" + +(asdf [foo] + "hel + lo asd + fasdfa + sdf + asdf + as + as + asdf + df + df + world") + +#{:foo :bar + :biz + "ba + z"} + +#"foo + bar + biz" + +test "hello + world" + +regex #"asdf + bar" From a2ffcba51235e15aa8d5b7e84bb3835f61556af4 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Mon, 26 Jun 2023 22:24:08 +0100 Subject: [PATCH 28/88] Fix indentation for multibyte characters --- clj/resources/indent-test-cases/multibyte/in.clj | 4 ++-- indent/clojure.vim | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/clj/resources/indent-test-cases/multibyte/in.clj b/clj/resources/indent-test-cases/multibyte/in.clj index 4a17c24..0718780 100644 --- a/clj/resources/indent-test-cases/multibyte/in.clj +++ b/clj/resources/indent-test-cases/multibyte/in.clj @@ -1,3 +1,3 @@ -(let [Δt (if foo + (let [Δt (if foo bar - baz)]) + baz)]) diff --git a/indent/clojure.vim b/indent/clojure.vim index e178f8b..4dcb745 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -160,8 +160,7 @@ endfunction function! s:ListIndent(delim_pos) " TODO: attempt to extend "s:InsideForm" to provide information about - " the subforms within the form being formatted to avoid second parsing - " step. + " the subforms being formatted to avoid second parsing step. call cursor(a:delim_pos) let ln = getline(a:delim_pos[0]) @@ -196,7 +195,7 @@ function! s:ListIndent(delim_pos) normal! w endif - let cur_pos = getcurpos()[1:2] + let cur_pos = getcursorcharpos()[1:2] if a:delim_pos[0] == cur_pos[0] && init_col != cur_pos[1] " Align operands. return cur_pos[1] - 1 From 09720fea7bd26dc8464b09c2c79d6026ce202ce4 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Mon, 26 Jun 2023 22:29:50 +0100 Subject: [PATCH 29/88] Switch test runner to Kaocha for nicer output --- clj/project.clj | 6 +++++- clj/tests.edn | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 clj/tests.edn diff --git a/clj/project.clj b/clj/project.clj index 1aa3428..eac72ef 100644 --- a/clj/project.clj +++ b/clj/project.clj @@ -6,4 +6,8 @@ :comments ":help license"} :dependencies [[org.clojure/clojure "1.11.1"] [org.clojure/data.csv "1.0.1"] - [frak "0.1.9"]]) + [frak "0.1.9"]] + :profiles {:test {:managed-dependencies [[org.clojure/tools.cli "1.0.219"] + [org.clojure/tools.reader "1.3.6"]] + :dependencies [[lambdaisland/kaocha "1.85.1342"]]}} + :aliases {"test" ["with-profile" "+test" "run" "-m" "kaocha.runner"]}) diff --git a/clj/tests.edn b/clj/tests.edn new file mode 100644 index 0000000..9d8d845 --- /dev/null +++ b/clj/tests.edn @@ -0,0 +1 @@ +#kaocha/v1 {} From 2728db207443b16ef42b49820e45aad733d9f1ac Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Mon, 26 Jun 2023 22:35:32 +0100 Subject: [PATCH 30/88] Only run GitHub Actions workflow once for PRs --- .github/workflows/clojure.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/clojure.yml b/.github/workflows/clojure.yml index 0d443d0..3243e33 100644 --- a/.github/workflows/clojure.yml +++ b/.github/workflows/clojure.yml @@ -1,5 +1,10 @@ name: CI -on: [push, pull_request, workflow_dispatch] +on: + push: + branches: + - master + pull_request: + workflow_dispatch: jobs: lint: From d51154a7a73bcc15bd2ddff4c5179979e107bc6e Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Sat, 12 Aug 2023 13:00:45 +0100 Subject: [PATCH 31/88] Neovim does not have a `state()` function --- indent/clojure.vim | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/indent/clojure.vim b/indent/clojure.vim index 4dcb745..9709dc3 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -132,9 +132,15 @@ function! s:InsideForm(lnum) return ['^', [0, 0]] " Default to top-level. endfunction -" Returns "1" when the previous operator used was "=" and is currently active. +" Returns "1" when the "=" operator is currently active. function! s:EqualsOperatorInEffect() - return v:operator ==# '=' && state('o') ==# 'o' + if has('nvim') + " Neovim has no `state()` function so fallback to indenting + " strings. + return 0 + else + return v:operator ==# '=' && state('o') ==# 'o' + endif endfunction function! s:StringIndent(delim_pos) From f3d889f0f22f82547e796e75f187b3ccbab1f82e Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Sun, 13 Aug 2023 13:55:58 +0100 Subject: [PATCH 32/88] Initial work on building up macro indent rules --- indent/clojure.vim | 93 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 82 insertions(+), 11 deletions(-) diff --git a/indent/clojure.vim b/indent/clojure.vim index 9709dc3..b7ea034 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -24,6 +24,51 @@ setlocal indentkeys=!,o,O " NOTE: To debug this code, make sure to "set debug+=msg" otherwise errors " will occur silently. +if !exists('g:clojure_fuzzy_indent_patterns') + let g:clojure_fuzzy_indent_patterns = [ + \ "\v^with-%(meta|out-str|loading-context)\@!", + \ "^def", + \ "^let" + \ ] +endif + +if !exists('g:clojure_indent_rules') + " Defaults copied from: https://github.com/clojure-emacs/clojure-mode/blob/0e62583b5198f71856e4d7b80e1099789d47f2ed/clojure-mode.el#L1800-L1875 + let g:clojure_indent_rules = { + \ "ns": 1, + \ "fn": 1, "def": 1, "defn": 1, "bound-fn": 1, + \ "if": 1, "if-not": 1, "if-some": 1, "if-let": 1, + \ "when": 1, "when-not": 1, "when-some": 1, "when-let": 1, "when-first": 1, + \ "case": 1, "cond": 0, "cond->": 1, "cond->>": 1, "condp": 2, + \ "while": 1, "loop": 1, "for": 1, "doseq": 1, "dotimes": 1, + \ "do": 0, "doto": 1, "comment": 0, "as->": 2, + \ "delay": 0, "future": 0, "locking": 1, + \ "fdef": 1, + \ "extend": 1, + \ "try": 0, "catch": 2, "finally": 0, + \ "let": 1, "binding": 1, + \ "defmethod": 1, + \ "this-as": 1, + \ "deftest": 1, "testing": 1, "use-fixtures": 1, "are": 2, + \ "alt!": 0, "alt!!": 0, "go": 0, "go-loop": 1, "thread": 0, + \ "run": 1, "run*": 1, "fresh": 1 + \ } + +" (letfn '(1 ((:defn)) nil)) +" (proxy '(2 nil nil (:defn))) +" (reify '(:defn (1))) +" (deftype '(2 nil nil (:defn))) +" (defrecord '(2 nil nil (:defn))) +" (defprotocol '(1 (:defn))) +" (definterface '(1 (:defn))) +" (extend-protocol '(1 :defn)) +" (extend-type '(1 :defn)) +" (specify '(1 :defn)) ; ClojureScript +" (specify! '(1 :defn)) ; ClojureScript +" (this-as 1) ; ClojureScript +" clojure.test, core.async, core.logic +endif + " Get the value of a configuration option. function! s:Conf(opt, default) return get(b:, a:opt, get(g:, a:opt, a:default)) @@ -165,23 +210,49 @@ function! s:StringIndent(delim_pos) endfunction function! s:ListIndent(delim_pos) - " TODO: attempt to extend "s:InsideForm" to provide information about - " the subforms being formatted to avoid second parsing step. + " TODO: extend "s:InsideForm" to provide information about the + " subforms being formatted to avoid second parsing step. call cursor(a:delim_pos) let ln = getline(a:delim_pos[0]) let base_indent = a:delim_pos[1] - " 1. TODO: Macro/rule indentation - " if starts with a symbol or keyword: extract it. - " - Look up in rules table. - " - See number for forms forward to lookup. - " - Start parsing forwards "x" forms. (Skip metadata) - " - Apply indentation. + " 1. Macro/rule indentation + " if starts with a symbol, extract it. + " - Split namespace off symbol and #'/' syntax. + " - Check against pattern rules and apply indent on match. + " - TODO: Look up in rules table and apply indent on match. " else, not found, go to 2. - " TODO: complex indentation (e.g. letfn) - " TODO: namespaces. - " let syms = split(ln[base_indent:], '[[:space:],;()\[\]{}@\\"^~`]', 1) + " + " TODO: handle complex indentation (e.g. letfn) and introduce + " indentation config similar to Emacs' clojure-mode and cljfmt. + " This new config option `clojure_indent_rules` should replace most + " other indentation options. + " + " TODO: replace `clojure_fuzzy_indent_patterns` with `clojure_indent_patterns` + let syms = split(ln[base_indent:], '[[:space:],;()\[\]{}@\\"^~`]', 1) + if !empty(syms) + let sym = syms[0] + " TODO: strip #' and ' from front of symbol. + if sym =~# '\v^%([a-zA-Z!$&*_+=|<>?-]|[^\x00-\x7F])' + + " TODO: handle namespaced and non-namespaced variants. + if sym =~# './.' + let [_namespace, name] = split(sym, '/') + endif + + for pat in s:Conf('clojure_fuzzy_indent_patterns', []) + if sym =~# pat + return base_indent + 1 + endif + endfor + + let rules = s:Conf('clojure_indent_rules', {}) + let sym_match = get(rules, sym, -1) + " TODO: handle 2+ differently? + if sym_match >= 0 | return base_indent + 1 | endif + endif + endif " 2. Function indentation " if first operand is on the same line? (Treat metadata as args.) From bf4cf3dd0effb50749652c7b53fe7aefdbc8eeff Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Sun, 13 Aug 2023 14:06:04 +0100 Subject: [PATCH 33/88] Fix false-positive comment detection within strings --- indent/clojure.vim | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/indent/clojure.vim b/indent/clojure.vim index b7ea034..076989f 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -92,24 +92,18 @@ function! s:TokeniseLine(line_num) while 1 " We perform searches within the buffer (and move the cusor) " for better performance than looping char by char in a line. - let token = searchpos('[()[\]{};"]', 'bW', a:line_num) + let token_pos = searchpos('[()[\]{};"]', 'bW', a:line_num) " No more matches, exit loop. - if token == [0, 0] | break | endif + if token_pos == [0, 0] | break | endif - let t_idx = token[1] - 1 + let t_idx = token_pos[1] - 1 " Escaped character, ignore. if s:IsEscaped(ln, t_idx) | continue | endif - let t_char = ln[t_idx] - if t_char ==# ';' - " Comment found, reset the token list for this line. - let tokens = [] - elseif t_char =~# '[()\[\]{}"]' - " Add token to the list. - call add(tokens, [t_char, token]) - endif + " Add token to the list. + call add(tokens, [ln[t_idx], token_pos]) endwhile return tokens @@ -151,6 +145,9 @@ function! s:InsideForm(lnum) endif elseif in_string " In string: ignore other tokens. + elseif tk[0] ==# ';' + " Comment: break loop. + break elseif ! empty(tokens) && get(s:pairs, tk[0], '') ==# tokens[-1][0] " Matching pair: drop the last item in tokens. call remove(tokens, -1) From caef0c551816ff7eae2acdbfc24471c9e325294c Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Sun, 13 Aug 2023 14:24:33 +0100 Subject: [PATCH 34/88] Improve accuracy of multiline string detection for indentation --- indent/clojure.vim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/indent/clojure.vim b/indent/clojure.vim index 076989f..3d9b742 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -157,7 +157,7 @@ function! s:InsideForm(lnum) endif endfor - if ! empty(tokens) && has_key(s:pairs, tokens[0][0]) + if ! empty(tokens) && has_key(s:pairs, tokens[0][0]) && ! in_string " Match found! return tokens[0] endif @@ -165,7 +165,7 @@ function! s:InsideForm(lnum) let lnum -= 1 endwhile - if ! empty(tokens) && tokens[0][0] ==# '"' + if (in_string && first_string_pos != []) || (! empty(tokens) && tokens[0][0] ==# '"') " Must have been in a multi-line string or regular expression " as the string was never closed. return first_string_pos From 3dbc6dd2ae0be94c94fff09d860c4cac0c75bde5 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Sun, 13 Aug 2023 17:11:56 +0100 Subject: [PATCH 35/88] Fix indentation when file contains multibyte characters --- .../indent-test-cases/multibyte/in.clj | 8 ++++++ .../indent-test-cases/multibyte/out.clj | 8 ++++++ indent/clojure.vim | 25 +++++++++++-------- 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/clj/resources/indent-test-cases/multibyte/in.clj b/clj/resources/indent-test-cases/multibyte/in.clj index 0718780..c820d38 100644 --- a/clj/resources/indent-test-cases/multibyte/in.clj +++ b/clj/resources/indent-test-cases/multibyte/in.clj @@ -1,3 +1,11 @@ (let [Δt (if foo bar baz)]) + + (let [Δt {:foo 'foo + :bar + 123}]) + + (let [Δt '[if foo + bar + baz]]) diff --git a/clj/resources/indent-test-cases/multibyte/out.clj b/clj/resources/indent-test-cases/multibyte/out.clj index 4a17c24..99b1aeb 100644 --- a/clj/resources/indent-test-cases/multibyte/out.clj +++ b/clj/resources/indent-test-cases/multibyte/out.clj @@ -1,3 +1,11 @@ (let [Δt (if foo bar baz)]) + +(let [Δt {:foo 'foo + :bar + 123}]) + +(let [Δt '[if foo + bar + baz]]) diff --git a/indent/clojure.vim b/indent/clojure.vim index 3d9b742..b38575d 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -81,6 +81,11 @@ function! s:IsEscaped(line_str, i_char) return (strlen(ln) - strlen(trim(ln, '\', 2))) % 2 endfunction +function! s:PosToCharPos(pos) + call cursor(a:pos) + return getcursorcharpos()[1:2] +endfunction + " Repeatedly search for indentation significant Clojure tokens on a given line " (in reverse order) building up a list of tokens and their positions. " Ignores escaped tokens. Does not care about strings, which is handled by @@ -195,11 +200,11 @@ function! s:StringIndent(delim_pos) " 0: Indent in alignment with end of the string start delimiter. " 1: Indent in alignment with string start delimiter. if alignment == -1 | return 0 - elseif alignment == 1 | return a:delim_pos[1] + elseif alignment == 1 | return s:PosToCharPos(a:delim_pos)[1] else let col = a:delim_pos[1] let is_regex = col > 1 && getline(a:delim_pos[0])[col - 2] ==# '#' - return col - (is_regex ? 2 : 1) + return s:PosToCharPos(a:delim_pos)[1] - (is_regex ? 2 : 1) endif else return -1 " Keep existing indent. @@ -210,15 +215,15 @@ function! s:ListIndent(delim_pos) " TODO: extend "s:InsideForm" to provide information about the " subforms being formatted to avoid second parsing step. - call cursor(a:delim_pos) + let base_indent = s:PosToCharPos(a:delim_pos)[1] let ln = getline(a:delim_pos[0]) - let base_indent = a:delim_pos[1] + let delim_col = a:delim_pos[1] " 1. Macro/rule indentation " if starts with a symbol, extract it. " - Split namespace off symbol and #'/' syntax. " - Check against pattern rules and apply indent on match. - " - TODO: Look up in rules table and apply indent on match. + " - Look up in rules table and apply indent on match. " else, not found, go to 2. " " TODO: handle complex indentation (e.g. letfn) and introduce @@ -227,7 +232,7 @@ function! s:ListIndent(delim_pos) " other indentation options. " " TODO: replace `clojure_fuzzy_indent_patterns` with `clojure_indent_patterns` - let syms = split(ln[base_indent:], '[[:space:],;()\[\]{}@\\"^~`]', 1) + let syms = split(ln[delim_col:], '[[:space:],;()\[\]{}@\\"^~`]', 1) if !empty(syms) let sym = syms[0] " TODO: strip #' and ' from front of symbol. @@ -258,11 +263,11 @@ function! s:ListIndent(delim_pos) " - Indent 1 or 2 spaces. let indent_style = s:Conf('clojure_indent_style', 'always-align') if indent_style !=# 'always-indent' - let init_col = a:delim_pos[1] + 1 + let init_col = delim_col + 1 call cursor(a:delim_pos[0], init_col) " TODO: replace cursor translation with searching? - let ch = ln[base_indent] + let ch = ln[delim_col] if ch ==# '(' || ch ==# '[' || ch ==# '{' normal! %w elseif ch !=# ';' && ch !=# '"' @@ -286,8 +291,8 @@ function! s:ClojureIndent() let [form, pos] = s:InsideForm(v:lnum) if form ==# '^' | return 0 " At top-level, no indent. elseif form ==# '(' | return s:ListIndent(pos) - elseif form ==# '[' | return pos[1] - elseif form ==# '{' | return pos[1] + elseif form ==# '[' | return s:PosToCharPos(pos)[1] + elseif form ==# '{' | return s:PosToCharPos(pos)[1] elseif form ==# '"' | return s:StringIndent(pos) else | return -1 " Keep existing indent. endif From 55501118f862768e143da5fa0bc58a9b5eacdb32 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Sun, 13 Aug 2023 18:03:17 +0100 Subject: [PATCH 36/88] Add more indent test cases --- .../indent-test-cases/multibyte/in.clj | 4 +++ .../indent-test-cases/multibyte/out.clj | 4 +++ .../s-expr_align-arguments/in.clj | 27 +++++++++++++++++ .../s-expr_align-arguments/out.clj | 27 +++++++++++++++++ .../s-expr_always-align/in.clj | 27 +++++++++++++++++ .../s-expr_always-align/out.clj | 29 ++++++++++++++++++- .../s-expr_always-indent/in.clj | 27 +++++++++++++++++ .../s-expr_always-indent/out.clj | 27 +++++++++++++++++ 8 files changed, 171 insertions(+), 1 deletion(-) diff --git a/clj/resources/indent-test-cases/multibyte/in.clj b/clj/resources/indent-test-cases/multibyte/in.clj index c820d38..c05e75f 100644 --- a/clj/resources/indent-test-cases/multibyte/in.clj +++ b/clj/resources/indent-test-cases/multibyte/in.clj @@ -9,3 +9,7 @@ (let [Δt '[if foo bar baz]]) + + (let [Δt (assoc foo + :bar + 123)]) diff --git a/clj/resources/indent-test-cases/multibyte/out.clj b/clj/resources/indent-test-cases/multibyte/out.clj index 99b1aeb..a117e52 100644 --- a/clj/resources/indent-test-cases/multibyte/out.clj +++ b/clj/resources/indent-test-cases/multibyte/out.clj @@ -9,3 +9,7 @@ (let [Δt '[if foo bar baz]]) + +(let [Δt (assoc foo + :bar + 123)]) diff --git a/clj/resources/indent-test-cases/s-expr_align-arguments/in.clj b/clj/resources/indent-test-cases/s-expr_align-arguments/in.clj index 34e146d..cb9f3e8 100644 --- a/clj/resources/indent-test-cases/s-expr_align-arguments/in.clj +++ b/clj/resources/indent-test-cases/s-expr_align-arguments/in.clj @@ -28,6 +28,9 @@ 1 2) + ((constantly +) 1 + 2) + (filter #(= 0 (mod % 2)) @@ -45,6 +48,9 @@ a)) (#(foo bar) a) +(#(foo bar) a + b) + #_(:foo {:foo 1}) @@ -56,3 +62,24 @@ a) (#_(foo bar) a) + +(@foo bar + biz) + +(@foo +bar + biz) + +(#'foo bar + biz) + +(#'foo +bar + biz) + +('foo bar + biz) + +('foo +bar + biz) diff --git a/clj/resources/indent-test-cases/s-expr_align-arguments/out.clj b/clj/resources/indent-test-cases/s-expr_align-arguments/out.clj index fb393d3..35b9e00 100644 --- a/clj/resources/indent-test-cases/s-expr_align-arguments/out.clj +++ b/clj/resources/indent-test-cases/s-expr_align-arguments/out.clj @@ -28,6 +28,9 @@ 1 2) +((constantly +) 1 + 2) + (filter #(= 0 (mod % 2)) @@ -45,6 +48,9 @@ (#(foo bar) a) +(#(foo bar) a + b) + #_(:foo {:foo 1}) @@ -56,3 +62,24 @@ (#_(foo bar) a) + +(@foo bar + biz) + +(@foo + bar + biz) + +(#'foo bar + biz) + +(#'foo + bar + biz) + +('foo bar + biz) + +('foo + bar + biz) diff --git a/clj/resources/indent-test-cases/s-expr_always-align/in.clj b/clj/resources/indent-test-cases/s-expr_always-align/in.clj index 34e146d..cb9f3e8 100644 --- a/clj/resources/indent-test-cases/s-expr_always-align/in.clj +++ b/clj/resources/indent-test-cases/s-expr_always-align/in.clj @@ -28,6 +28,9 @@ 1 2) + ((constantly +) 1 + 2) + (filter #(= 0 (mod % 2)) @@ -45,6 +48,9 @@ a)) (#(foo bar) a) +(#(foo bar) a + b) + #_(:foo {:foo 1}) @@ -56,3 +62,24 @@ a) (#_(foo bar) a) + +(@foo bar + biz) + +(@foo +bar + biz) + +(#'foo bar + biz) + +(#'foo +bar + biz) + +('foo bar + biz) + +('foo +bar + biz) diff --git a/clj/resources/indent-test-cases/s-expr_always-align/out.clj b/clj/resources/indent-test-cases/s-expr_always-align/out.clj index e83350c..0af4f24 100644 --- a/clj/resources/indent-test-cases/s-expr_always-align/out.clj +++ b/clj/resources/indent-test-cases/s-expr_always-align/out.clj @@ -28,6 +28,9 @@ 1 2) +((constantly +) 1 + 2) + (filter #(= 0 (mod % 2)) @@ -43,7 +46,10 @@ a)) (#(foo bar) - a) + a) + +(#(foo bar) a + b) #_(:foo {:foo 1}) @@ -56,3 +62,24 @@ (#_(foo bar) a) + +(@foo bar + biz) + +(@foo + bar + biz) + +(#'foo bar + biz) + +(#'foo + bar + biz) + +('foo bar + biz) + +('foo + bar + biz) diff --git a/clj/resources/indent-test-cases/s-expr_always-indent/in.clj b/clj/resources/indent-test-cases/s-expr_always-indent/in.clj index 34e146d..cb9f3e8 100644 --- a/clj/resources/indent-test-cases/s-expr_always-indent/in.clj +++ b/clj/resources/indent-test-cases/s-expr_always-indent/in.clj @@ -28,6 +28,9 @@ 1 2) + ((constantly +) 1 + 2) + (filter #(= 0 (mod % 2)) @@ -45,6 +48,9 @@ a)) (#(foo bar) a) +(#(foo bar) a + b) + #_(:foo {:foo 1}) @@ -56,3 +62,24 @@ a) (#_(foo bar) a) + +(@foo bar + biz) + +(@foo +bar + biz) + +(#'foo bar + biz) + +(#'foo +bar + biz) + +('foo bar + biz) + +('foo +bar + biz) diff --git a/clj/resources/indent-test-cases/s-expr_always-indent/out.clj b/clj/resources/indent-test-cases/s-expr_always-indent/out.clj index 414e2bd..9ca0cc7 100644 --- a/clj/resources/indent-test-cases/s-expr_always-indent/out.clj +++ b/clj/resources/indent-test-cases/s-expr_always-indent/out.clj @@ -28,6 +28,9 @@ 1 2) +((constantly +) 1 + 2) + (filter #(= 0 (mod % 2)) @@ -45,6 +48,9 @@ (#(foo bar) a) +(#(foo bar) a + b) + #_(:foo {:foo 1}) @@ -56,3 +62,24 @@ (#_(foo bar) a) + +(@foo bar + biz) + +(@foo + bar + biz) + +(#'foo bar + biz) + +(#'foo + bar + biz) + +('foo bar + biz) + +('foo + bar + biz) From b8aef1b23d2405957a8840b08771fc452a320ea4 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Sat, 23 Dec 2023 14:24:31 +0000 Subject: [PATCH 37/88] Neovim has added the `state()` function; use it if available See: neovim/neovim#23576 Unintentional multiline string indentation will no longer occur in future Neovim versions. --- indent/clojure.vim | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/indent/clojure.vim b/indent/clojure.vim index b38575d..ef1ee02 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -181,13 +181,9 @@ endfunction " Returns "1" when the "=" operator is currently active. function! s:EqualsOperatorInEffect() - if has('nvim') - " Neovim has no `state()` function so fallback to indenting - " strings. - return 0 - else - return v:operator ==# '=' && state('o') ==# 'o' - endif + return exists('*state') + \ ? v:operator ==# '=' && state('o') ==# 'o' + \ : 0 endfunction function! s:StringIndent(delim_pos) From b2c392d6a3ca3ac2a6e117e6be51fc2bf822f40e Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Sat, 23 Dec 2023 19:10:55 +0000 Subject: [PATCH 38/88] Fix application of inline comments during indentation Previously indenting code containing inline comments like this: {:foo 1 ; Hello [ :bar 2} Would have resulted in this incorrect indentation: {:foo 1 ; Hello [ :bar 2} --- indent/clojure.vim | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/indent/clojure.vim b/indent/clojure.vim index ef1ee02..b763a58 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -93,6 +93,7 @@ endfunction function! s:TokeniseLine(line_num) let tokens = [] let ln = getline(a:line_num) + let possible_comment = 0 while 1 " We perform searches within the buffer (and move the cusor) @@ -108,10 +109,16 @@ function! s:TokeniseLine(line_num) if s:IsEscaped(ln, t_idx) | continue | endif " Add token to the list. - call add(tokens, [ln[t_idx], token_pos]) + let token = ln[t_idx] + call add(tokens, [token, token_pos]) + + " Early "possible comment" detection to reduce copying later. + if token ==# ';' | let possible_comment = 1 | endif endwhile - return tokens + " echom 'Tokens: ' string(tokens) + + return [tokens, possible_comment] endfunction let s:pairs = {'(': ')', '[': ']', '{': '}'} @@ -131,8 +138,15 @@ function! s:InsideForm(lnum) let lnum = a:lnum - 1 while lnum > 0 + let [line_tokens, possible_comment] = s:TokeniseLine(lnum) + + " In case of comments, copy "tokens" so we can undo alterations. + if possible_comment + let prev_tokens = copy(tokens) + endif + " Reduce tokens from line "lnum" into "tokens". - for tk in s:TokeniseLine(lnum) + for tk in line_tokens if tk[0] ==# '"' if in_string let in_string = 0 @@ -150,9 +164,10 @@ function! s:InsideForm(lnum) endif elseif in_string " In string: ignore other tokens. - elseif tk[0] ==# ';' - " Comment: break loop. - break + elseif possible_comment && tk[0] ==# ';' + " Comment: undo previous token applications on + " this line. + let tokens = copy(prev_tokens) elseif ! empty(tokens) && get(s:pairs, tk[0], '') ==# tokens[-1][0] " Matching pair: drop the last item in tokens. call remove(tokens, -1) From 4edeef035ff90b97fecba46593b28c5344740dba Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Sat, 23 Dec 2023 19:22:28 +0000 Subject: [PATCH 39/88] Add tests to check that comments don't affect indentation --- clj/resources/indent-test-cases/comments/in.clj | 11 +++++++++++ clj/resources/indent-test-cases/comments/out.clj | 11 +++++++++++ 2 files changed, 22 insertions(+) create mode 100644 clj/resources/indent-test-cases/comments/in.clj create mode 100644 clj/resources/indent-test-cases/comments/out.clj diff --git a/clj/resources/indent-test-cases/comments/in.clj b/clj/resources/indent-test-cases/comments/in.clj new file mode 100644 index 0000000..4c49d3a --- /dev/null +++ b/clj/resources/indent-test-cases/comments/in.clj @@ -0,0 +1,11 @@ +{:foo {:bar 1} ; Default. +:biz {:as 123, :asdf #{1 2 3}} +} + +;; Foo bar! { +{:foo {:bar 1} ; Default. { +:biz {:as 123, :asdf #{1 2 3}}} + +{:foo {:bar 1} ; Default. +;; Foo bar! { +:biz {:as 123, :asdf #{1 2 3}}} diff --git a/clj/resources/indent-test-cases/comments/out.clj b/clj/resources/indent-test-cases/comments/out.clj new file mode 100644 index 0000000..f6259f3 --- /dev/null +++ b/clj/resources/indent-test-cases/comments/out.clj @@ -0,0 +1,11 @@ +{:foo {:bar 1} ; Default. + :biz {:as 123, :asdf #{1 2 3}} + } + +;; Foo bar! { +{:foo {:bar 1} ; Default. { + :biz {:as 123, :asdf #{1 2 3}}} + +{:foo {:bar 1} ; Default. + ;; Foo bar! { + :biz {:as 123, :asdf #{1 2 3}}} From 0388414537fe57cc084c06e08d6cb326bd8f6852 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Sat, 23 Dec 2023 20:06:25 +0000 Subject: [PATCH 40/88] Fix indentation of `with-*` macros --- .../in.clj | 0 .../out.clj | 0 clj/resources/indent-test-cases/with/in.clj | 15 +++++++ clj/resources/indent-test-cases/with/out.clj | 15 +++++++ indent/clojure.vim | 40 +++++++++---------- 5 files changed, 50 insertions(+), 20 deletions(-) rename clj/resources/indent-test-cases/{reader-conditional => reader_conditionals}/in.clj (100%) rename clj/resources/indent-test-cases/{reader-conditional => reader_conditionals}/out.clj (100%) create mode 100644 clj/resources/indent-test-cases/with/in.clj create mode 100644 clj/resources/indent-test-cases/with/out.clj diff --git a/clj/resources/indent-test-cases/reader-conditional/in.clj b/clj/resources/indent-test-cases/reader_conditionals/in.clj similarity index 100% rename from clj/resources/indent-test-cases/reader-conditional/in.clj rename to clj/resources/indent-test-cases/reader_conditionals/in.clj diff --git a/clj/resources/indent-test-cases/reader-conditional/out.clj b/clj/resources/indent-test-cases/reader_conditionals/out.clj similarity index 100% rename from clj/resources/indent-test-cases/reader-conditional/out.clj rename to clj/resources/indent-test-cases/reader_conditionals/out.clj diff --git a/clj/resources/indent-test-cases/with/in.clj b/clj/resources/indent-test-cases/with/in.clj new file mode 100644 index 0000000..c307037 --- /dev/null +++ b/clj/resources/indent-test-cases/with/in.clj @@ -0,0 +1,15 @@ +(with-open [f (io/file)] + (slurp f)) + +(with-meta obj + {:foo 1}) + +(with-meta + obj + {:foo 1}) + +(with-out-str +()) + +(with-in-str + ()) diff --git a/clj/resources/indent-test-cases/with/out.clj b/clj/resources/indent-test-cases/with/out.clj new file mode 100644 index 0000000..48edfef --- /dev/null +++ b/clj/resources/indent-test-cases/with/out.clj @@ -0,0 +1,15 @@ +(with-open [f (io/file)] + (slurp f)) + +(with-meta obj + {:foo 1}) + +(with-meta + obj + {:foo 1}) + +(with-out-str + ()) + +(with-in-str + ()) diff --git a/indent/clojure.vim b/indent/clojure.vim index b763a58..aa6f5fd 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -26,32 +26,32 @@ setlocal indentkeys=!,o,O if !exists('g:clojure_fuzzy_indent_patterns') let g:clojure_fuzzy_indent_patterns = [ - \ "\v^with-%(meta|out-str|loading-context)\@!", - \ "^def", - \ "^let" + \ '^with-\%(meta\|out-str\|loading-context\)\@!', + \ '^def', + \ '^let' \ ] endif if !exists('g:clojure_indent_rules') " Defaults copied from: https://github.com/clojure-emacs/clojure-mode/blob/0e62583b5198f71856e4d7b80e1099789d47f2ed/clojure-mode.el#L1800-L1875 let g:clojure_indent_rules = { - \ "ns": 1, - \ "fn": 1, "def": 1, "defn": 1, "bound-fn": 1, - \ "if": 1, "if-not": 1, "if-some": 1, "if-let": 1, - \ "when": 1, "when-not": 1, "when-some": 1, "when-let": 1, "when-first": 1, - \ "case": 1, "cond": 0, "cond->": 1, "cond->>": 1, "condp": 2, - \ "while": 1, "loop": 1, "for": 1, "doseq": 1, "dotimes": 1, - \ "do": 0, "doto": 1, "comment": 0, "as->": 2, - \ "delay": 0, "future": 0, "locking": 1, - \ "fdef": 1, - \ "extend": 1, - \ "try": 0, "catch": 2, "finally": 0, - \ "let": 1, "binding": 1, - \ "defmethod": 1, - \ "this-as": 1, - \ "deftest": 1, "testing": 1, "use-fixtures": 1, "are": 2, - \ "alt!": 0, "alt!!": 0, "go": 0, "go-loop": 1, "thread": 0, - \ "run": 1, "run*": 1, "fresh": 1 + \ 'ns': 1, + \ 'fn': 1, 'def': 1, 'defn': 1, 'bound-fn': 1, + \ 'if': 1, 'if-not': 1, 'if-some': 1, 'if-let': 1, + \ 'when': 1, 'when-not': 1, 'when-some': 1, 'when-let': 1, 'when-first': 1, + \ 'case': 1, 'cond': 0, 'cond->': 1, 'cond->>': 1, 'condp': 2, + \ 'while': 1, 'loop': 1, 'for': 1, 'doseq': 1, 'dotimes': 1, + \ 'do': 0, 'doto': 1, 'comment': 0, 'as->': 2, + \ 'delay': 0, 'future': 0, 'locking': 1, + \ 'fdef': 1, + \ 'extend': 1, + \ 'try': 0, 'catch': 2, 'finally': 0, + \ 'let': 1, 'binding': 1, + \ 'defmethod': 1, + \ 'this-as': 1, + \ 'deftest': 1, 'testing': 1, 'use-fixtures': 1, 'are': 2, + \ 'alt!': 0, 'alt!!': 0, 'go': 0, 'go-loop': 1, 'thread': 0, + \ 'run': 1, 'run*': 1, 'fresh': 1 \ } " (letfn '(1 ((:defn)) nil)) From 41a45d40fc048eabeaef2997db9a20518fe20704 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Sat, 23 Dec 2023 20:38:43 +0000 Subject: [PATCH 41/88] Update comments and add `with-in-str` indent rule --- indent/clojure.vim | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/indent/clojure.vim b/indent/clojure.vim index aa6f5fd..321070a 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -26,7 +26,7 @@ setlocal indentkeys=!,o,O if !exists('g:clojure_fuzzy_indent_patterns') let g:clojure_fuzzy_indent_patterns = [ - \ '^with-\%(meta\|out-str\|loading-context\)\@!', + \ '^with-\%(meta\|in-str\|out-str\|loading-context\)\@!', \ '^def', \ '^let' \ ] @@ -54,19 +54,19 @@ if !exists('g:clojure_indent_rules') \ 'run': 1, 'run*': 1, 'fresh': 1 \ } -" (letfn '(1 ((:defn)) nil)) -" (proxy '(2 nil nil (:defn))) -" (reify '(:defn (1))) -" (deftype '(2 nil nil (:defn))) -" (defrecord '(2 nil nil (:defn))) -" (defprotocol '(1 (:defn))) -" (definterface '(1 (:defn))) -" (extend-protocol '(1 :defn)) -" (extend-type '(1 :defn)) -" (specify '(1 :defn)) ; ClojureScript -" (specify! '(1 :defn)) ; ClojureScript -" (this-as 1) ; ClojureScript -" clojure.test, core.async, core.logic + " (letfn '(1 ((:defn)) nil)) + " (proxy '(2 nil nil (:defn))) + " (reify '(:defn (1))) + " (deftype '(2 nil nil (:defn))) + " (defrecord '(2 nil nil (:defn))) + " (defprotocol '(1 (:defn))) + " (definterface '(1 (:defn))) + " (extend-protocol '(1 :defn)) + " (extend-type '(1 :defn)) + " (specify '(1 :defn)) ; ClojureScript + " (specify! '(1 :defn)) ; ClojureScript + " (this-as 1) ; ClojureScript + " clojure.test, core.async, core.logic endif " Get the value of a configuration option. From 68999cad5122e220a43da538f7d5da792c4b22b3 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Mon, 25 Dec 2023 22:21:04 +0000 Subject: [PATCH 42/88] Improve accuracy of function operand indenting In the original indent script, code like this: ((foo) 1 3) Would be indented as this: ((foo) 1 3) Now it is *correctly* indented as this: ((foo) 1 3) --- indent/clojure.vim | 45 ++++++++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/indent/clojure.vim b/indent/clojure.vim index 321070a..a739c11 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -81,6 +81,14 @@ function! s:IsEscaped(line_str, i_char) return (strlen(ln) - strlen(trim(ln, '\', 2))) % 2 endfunction +" TODO: better comment and function name. +" Used to check if in the current form for list function indentation. +let s:in_form_current_form = [0, 0] +function! s:InForm() + let pos = searchpairpos('[([{"]', '', '[)\]}"]', 'b') + return pos != [0, 0] && pos != s:in_form_current_form +endfunction + function! s:PosToCharPos(pos) call cursor(a:pos) return getcursorcharpos()[1:2] @@ -116,8 +124,6 @@ function! s:TokeniseLine(line_num) if token ==# ';' | let possible_comment = 1 | endif endwhile - " echom 'Tokens: ' string(tokens) - return [tokens, possible_comment] endfunction @@ -185,6 +191,7 @@ function! s:InsideForm(lnum) let lnum -= 1 endwhile + " TODO: can this conditional be simplified? if (in_string && first_string_pos != []) || (! empty(tokens) && tokens[0][0] ==# '"') " Must have been in a multi-line string or regular expression " as the string was never closed. @@ -236,17 +243,18 @@ function! s:ListIndent(delim_pos) " - Check against pattern rules and apply indent on match. " - Look up in rules table and apply indent on match. " else, not found, go to 2. - " + " TODO: handle complex indentation (e.g. letfn) and introduce " indentation config similar to Emacs' clojure-mode and cljfmt. " This new config option `clojure_indent_rules` should replace most " other indentation options. - " - " TODO: replace `clojure_fuzzy_indent_patterns` with `clojure_indent_patterns` + + " TODO: simplify this. let syms = split(ln[delim_col:], '[[:space:],;()\[\]{}@\\"^~`]', 1) + if !empty(syms) let sym = syms[0] - " TODO: strip #' and ' from front of symbol. + " TODO: if prefixed with "#'" or "'" fallback to func indent. if sym =~# '\v^%([a-zA-Z!$&*_+=|<>?-]|[^\x00-\x7F])' " TODO: handle namespaced and non-namespaced variants. @@ -254,6 +262,7 @@ function! s:ListIndent(delim_pos) let [_namespace, name] = split(sym, '/') endif + " TODO: replace `clojure_fuzzy_indent_patterns` with `clojure_indent_patterns` for pat in s:Conf('clojure_fuzzy_indent_patterns', []) if sym =~# pat return base_indent + 1 @@ -272,24 +281,18 @@ function! s:ListIndent(delim_pos) " - Indent subsequent lines to align with first operand. " else " - Indent 1 or 2 spaces. + let indent_style = s:Conf('clojure_indent_style', 'always-align') if indent_style !=# 'always-indent' - let init_col = delim_col + 1 - call cursor(a:delim_pos[0], init_col) - - " TODO: replace cursor translation with searching? - let ch = ln[delim_col] - if ch ==# '(' || ch ==# '[' || ch ==# '{' - normal! %w - elseif ch !=# ';' && ch !=# '"' - normal! w - endif + let lnr = a:delim_pos[0] + call cursor(lnr, delim_col + 1) - let cur_pos = getcursorcharpos()[1:2] - if a:delim_pos[0] == cur_pos[0] && init_col != cur_pos[1] - " Align operands. - return cur_pos[1] - 1 - endif + " TODO: ignore comments. + " TODO: handle escaped characters! + let s:in_form_current_form = a:delim_pos + let ln_s = searchpos('[ ,]\+\zs', 'z', lnr, 0, function('InForm')) + + if ln_s != [0, 0] | return s:PosToCharPos(ln_s)[1] - 1 | endif endif " Fallback indentation for operands. When "clojure_indent_style" is From 8f38c11bfdc04242f16639fefa2f44a1256ed10c Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Tue, 26 Dec 2023 15:15:08 +0000 Subject: [PATCH 43/88] Slight code refactor and added more comments --- indent/clojure.vim | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/indent/clojure.vim b/indent/clojure.vim index a739c11..bbdb4b3 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -81,14 +81,26 @@ function! s:IsEscaped(line_str, i_char) return (strlen(ln) - strlen(trim(ln, '\', 2))) % 2 endfunction -" TODO: better comment and function name. -" Used to check if in the current form for list function indentation. -let s:in_form_current_form = [0, 0] -function! s:InForm() +" Used during list function indentation. Returns the position of the first +" operand in the list on the first line of the form at "pos". +function! s:FirstFnArgPos(pos) + " TODO: ignore comments. + " TODO: handle escaped characters! + let lnr = a:pos[0] + let s:in_form_current_form = a:pos + call cursor(lnr, a:pos[1] + 1) + return searchpos('[ ,]\+\zs', 'z', lnr, 0, function('IsSubForm')) +endfunction + +" Used by "s:FirstFnArgPos" function to skip over subforms as the first value +" in a list form. +function! s:IsSubForm() let pos = searchpairpos('[([{"]', '', '[)\]}"]', 'b') return pos != [0, 0] && pos != s:in_form_current_form endfunction +" Converts a cursor position into a characterwise cursor position (to handle +" multibyte characters). function! s:PosToCharPos(pos) call cursor(a:pos) return getcursorcharpos()[1:2] @@ -235,7 +247,6 @@ function! s:ListIndent(delim_pos) let base_indent = s:PosToCharPos(a:delim_pos)[1] let ln = getline(a:delim_pos[0]) - let delim_col = a:delim_pos[1] " 1. Macro/rule indentation " if starts with a symbol, extract it. @@ -250,7 +261,7 @@ function! s:ListIndent(delim_pos) " other indentation options. " TODO: simplify this. - let syms = split(ln[delim_col:], '[[:space:],;()\[\]{}@\\"^~`]', 1) + let syms = split(ln[a:delim_pos[1]:], '[[:space:],;()\[\]{}@\\"^~`]', 1) if !empty(syms) let sym = syms[0] @@ -264,9 +275,7 @@ function! s:ListIndent(delim_pos) " TODO: replace `clojure_fuzzy_indent_patterns` with `clojure_indent_patterns` for pat in s:Conf('clojure_fuzzy_indent_patterns', []) - if sym =~# pat - return base_indent + 1 - endif + if sym =~# pat | return base_indent + 1 | endif endfor let rules = s:Conf('clojure_indent_rules', {}) @@ -277,22 +286,14 @@ function! s:ListIndent(delim_pos) endif " 2. Function indentation - " if first operand is on the same line? (Treat metadata as args.) + " if first operand is on the same line? " - Indent subsequent lines to align with first operand. " else " - Indent 1 or 2 spaces. - let indent_style = s:Conf('clojure_indent_style', 'always-align') if indent_style !=# 'always-indent' - let lnr = a:delim_pos[0] - call cursor(lnr, delim_col + 1) - - " TODO: ignore comments. - " TODO: handle escaped characters! - let s:in_form_current_form = a:delim_pos - let ln_s = searchpos('[ ,]\+\zs', 'z', lnr, 0, function('InForm')) - - if ln_s != [0, 0] | return s:PosToCharPos(ln_s)[1] - 1 | endif + let pos = s:FirstFnArgPos(a:delim_pos) + if pos != [0, 0] | return s:PosToCharPos(pos)[1] - 1 | endif endif " Fallback indentation for operands. When "clojure_indent_style" is From 7e90f09b779ffefb1236cb9aedbfd8cf432acff4 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Tue, 26 Dec 2023 15:22:41 +0000 Subject: [PATCH 44/88] When `lispoptions` feature is available, we can enable the `lisp` option Closes: #40 --- indent/clojure.vim | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/indent/clojure.vim b/indent/clojure.vim index bbdb4b3..01392a3 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -313,7 +313,11 @@ function! s:ClojureIndent() endif endfunction -" TODO: setl lisp lispoptions=expr:1 if exists. "has('patch-9.0.0761')" +" Connect indentation function. +if exists('&lispoptions') + setlocal lisp lispoptions=expr:1 + let b:undo_indent .= ' lispoptions<' +endif setlocal indentexpr=s:ClojureIndent() let &cpoptions = s:save_cpo From 0b388aefe7503666c7f92751288196ae7fdd1243 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Tue, 26 Dec 2023 16:05:39 +0000 Subject: [PATCH 45/88] Indent some macros a bit like functions --- clj/resources/indent-test-cases/special_forms/in.clj | 8 ++++++++ clj/resources/indent-test-cases/special_forms/out.clj | 8 ++++++++ indent/clojure.vim | 9 ++++++--- 3 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 clj/resources/indent-test-cases/special_forms/in.clj create mode 100644 clj/resources/indent-test-cases/special_forms/out.clj diff --git a/clj/resources/indent-test-cases/special_forms/in.clj b/clj/resources/indent-test-cases/special_forms/in.clj new file mode 100644 index 0000000..fda77be --- /dev/null +++ b/clj/resources/indent-test-cases/special_forms/in.clj @@ -0,0 +1,8 @@ +(try (/ 1 0) + (catch Exception e + (foo))) + +(try + (/ 1 0) + (catch Exception e + (foo))) diff --git a/clj/resources/indent-test-cases/special_forms/out.clj b/clj/resources/indent-test-cases/special_forms/out.clj new file mode 100644 index 0000000..388fb54 --- /dev/null +++ b/clj/resources/indent-test-cases/special_forms/out.clj @@ -0,0 +1,8 @@ +(try (/ 1 0) + (catch Exception e + (foo))) + +(try + (/ 1 0) + (catch Exception e + (foo))) diff --git a/indent/clojure.vim b/indent/clojure.vim index 01392a3..7f0ac8c 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -248,6 +248,8 @@ function! s:ListIndent(delim_pos) let base_indent = s:PosToCharPos(a:delim_pos)[1] let ln = getline(a:delim_pos[0]) + let sym_match = -1 + " 1. Macro/rule indentation " if starts with a symbol, extract it. " - Split namespace off symbol and #'/' syntax. @@ -265,7 +267,6 @@ function! s:ListIndent(delim_pos) if !empty(syms) let sym = syms[0] - " TODO: if prefixed with "#'" or "'" fallback to func indent. if sym =~# '\v^%([a-zA-Z!$&*_+=|<>?-]|[^\x00-\x7F])' " TODO: handle namespaced and non-namespaced variants. @@ -281,7 +282,7 @@ function! s:ListIndent(delim_pos) let rules = s:Conf('clojure_indent_rules', {}) let sym_match = get(rules, sym, -1) " TODO: handle 2+ differently? - if sym_match >= 0 | return base_indent + 1 | endif + if sym_match > 0 | return base_indent + 1 | endif endif endif @@ -298,7 +299,9 @@ function! s:ListIndent(delim_pos) " Fallback indentation for operands. When "clojure_indent_style" is " "always-align", use 1 space indentation, else 2 space indentation. - return base_indent + (indent_style !=# 'always-align') + " The "sym_match" check handles the case when "clojure_indent_rules" + " specified a value of "0". + return base_indent + (indent_style !=# 'always-align' || sym_match == 0) endfunction function! s:ClojureIndent() From 289352dd44c7f3a8792e3d0eeb1c8822aed0babd Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Sat, 9 Mar 2024 17:28:43 +0000 Subject: [PATCH 46/88] More indent test cases --- clj/resources/indent-test-cases/comments/in.clj | 6 ++++++ clj/resources/indent-test-cases/comments/out.clj | 6 ++++++ clj/resources/indent-test-cases/def/in.clj | 5 +++++ clj/resources/indent-test-cases/def/out.clj | 5 +++++ 4 files changed, 22 insertions(+) create mode 100644 clj/resources/indent-test-cases/def/in.clj create mode 100644 clj/resources/indent-test-cases/def/out.clj diff --git a/clj/resources/indent-test-cases/comments/in.clj b/clj/resources/indent-test-cases/comments/in.clj index 4c49d3a..5de184f 100644 --- a/clj/resources/indent-test-cases/comments/in.clj +++ b/clj/resources/indent-test-cases/comments/in.clj @@ -9,3 +9,9 @@ {:foo {:bar 1} ; Default. ;; Foo bar! { :biz {:as 123, :asdf #{1 2 3}}} + +(comment +{:foo {:bar 1} ; Default. + ;; Foo bar! { + :biz {:as 123, :asdf #{1 2 3}}} +) diff --git a/clj/resources/indent-test-cases/comments/out.clj b/clj/resources/indent-test-cases/comments/out.clj index f6259f3..1d5f218 100644 --- a/clj/resources/indent-test-cases/comments/out.clj +++ b/clj/resources/indent-test-cases/comments/out.clj @@ -9,3 +9,9 @@ {:foo {:bar 1} ; Default. ;; Foo bar! { :biz {:as 123, :asdf #{1 2 3}}} + +(comment + {:foo {:bar 1} ; Default. + ;; Foo bar! { + :biz {:as 123, :asdf #{1 2 3}}} + ) diff --git a/clj/resources/indent-test-cases/def/in.clj b/clj/resources/indent-test-cases/def/in.clj new file mode 100644 index 0000000..4dd213c --- /dev/null +++ b/clj/resources/indent-test-cases/def/in.clj @@ -0,0 +1,5 @@ +(defn- insert! + ^Map [^Map ^String k ^Object v] + (if (.putIfAbsent m k v) + (recur m (str \@ k) v) + m)) diff --git a/clj/resources/indent-test-cases/def/out.clj b/clj/resources/indent-test-cases/def/out.clj new file mode 100644 index 0000000..4dd213c --- /dev/null +++ b/clj/resources/indent-test-cases/def/out.clj @@ -0,0 +1,5 @@ +(defn- insert! + ^Map [^Map ^String k ^Object v] + (if (.putIfAbsent m k v) + (recur m (str \@ k) v) + m)) From cefb7bc2d5a66509e187f81e3674b84862c86040 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Sun, 10 Mar 2024 17:57:35 +0000 Subject: [PATCH 47/88] Fix escape character detection logic --- indent/clojure.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indent/clojure.vim b/indent/clojure.vim index 7f0ac8c..dbb169d 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -78,7 +78,7 @@ endfunction " of backslash characters (i.e. escaped). function! s:IsEscaped(line_str, i_char) let ln = a:line_str[: a:i_char - 1] - return (strlen(ln) - strlen(trim(ln, '\', 2))) % 2 + return ! strlen(trim(ln, '\', 2)) % 2 endfunction " Used during list function indentation. Returns the position of the first From 16026f73b5aba1005c854b6e002f7d3bc4a033a8 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Sun, 10 Mar 2024 18:39:43 +0000 Subject: [PATCH 48/88] Simplify character column resolution --- indent/clojure.vim | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/indent/clojure.vim b/indent/clojure.vim index dbb169d..2a41649 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -7,9 +7,7 @@ " License: Vim (see :h license) " Repository: https://github.com/clojure-vim/clojure.vim -if exists("b:did_indent") - finish -endif +if exists("b:did_indent") | finish | endif let b:did_indent = 1 let s:save_cpo = &cpoptions @@ -99,11 +97,11 @@ function! s:IsSubForm() return pos != [0, 0] && pos != s:in_form_current_form endfunction -" Converts a cursor position into a characterwise cursor position (to handle -" multibyte characters). -function! s:PosToCharPos(pos) +" Converts a cursor position into a characterwise cursor column position (to +" handle multibyte characters). +function! s:PosToCharCol(pos) call cursor(a:pos) - return getcursorcharpos()[1:2] + return getcursorcharpos()[2] endfunction " Repeatedly search for indentation significant Clojure tokens on a given line @@ -230,11 +228,11 @@ function! s:StringIndent(delim_pos) " 0: Indent in alignment with end of the string start delimiter. " 1: Indent in alignment with string start delimiter. if alignment == -1 | return 0 - elseif alignment == 1 | return s:PosToCharPos(a:delim_pos)[1] + elseif alignment == 1 | return s:PosToCharCol(a:delim_pos) else let col = a:delim_pos[1] let is_regex = col > 1 && getline(a:delim_pos[0])[col - 2] ==# '#' - return s:PosToCharPos(a:delim_pos)[1] - (is_regex ? 2 : 1) + return s:PosToCharCol(a:delim_pos) - (is_regex ? 2 : 1) endif else return -1 " Keep existing indent. @@ -245,7 +243,7 @@ function! s:ListIndent(delim_pos) " TODO: extend "s:InsideForm" to provide information about the " subforms being formatted to avoid second parsing step. - let base_indent = s:PosToCharPos(a:delim_pos)[1] + let base_indent = s:PosToCharCol(a:delim_pos) let ln = getline(a:delim_pos[0]) let sym_match = -1 @@ -294,7 +292,7 @@ function! s:ListIndent(delim_pos) let indent_style = s:Conf('clojure_indent_style', 'always-align') if indent_style !=# 'always-indent' let pos = s:FirstFnArgPos(a:delim_pos) - if pos != [0, 0] | return s:PosToCharPos(pos)[1] - 1 | endif + if pos != [0, 0] | return s:PosToCharCol(pos) - 1 | endif endif " Fallback indentation for operands. When "clojure_indent_style" is @@ -309,8 +307,8 @@ function! s:ClojureIndent() let [form, pos] = s:InsideForm(v:lnum) if form ==# '^' | return 0 " At top-level, no indent. elseif form ==# '(' | return s:ListIndent(pos) - elseif form ==# '[' | return s:PosToCharPos(pos)[1] - elseif form ==# '{' | return s:PosToCharPos(pos)[1] + elseif form ==# '[' | return s:PosToCharCol(pos) + elseif form ==# '{' | return s:PosToCharCol(pos) elseif form ==# '"' | return s:StringIndent(pos) else | return -1 " Keep existing indent. endif From d87f25483c753d673d22a9e75395bc6c54447a3c Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Sun, 10 Mar 2024 21:35:09 +0000 Subject: [PATCH 49/88] Add some indent rules for more built in macros --- indent/clojure.vim | 61 ++++++++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/indent/clojure.vim b/indent/clojure.vim index 2a41649..11c8e65 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -7,6 +7,9 @@ " License: Vim (see :h license) " Repository: https://github.com/clojure-vim/clojure.vim +" NOTE: To debug this code, make sure to "set debug+=msg" otherwise errors +" will occur silently. + if exists("b:did_indent") | finish | endif let b:did_indent = 1 @@ -19,9 +22,6 @@ setlocal noautoindent nosmartindent nolisp setlocal softtabstop=2 shiftwidth=2 expandtab setlocal indentkeys=!,o,O -" NOTE: To debug this code, make sure to "set debug+=msg" otherwise errors -" will occur silently. - if !exists('g:clojure_fuzzy_indent_patterns') let g:clojure_fuzzy_indent_patterns = [ \ '^with-\%(meta\|in-str\|out-str\|loading-context\)\@!', @@ -30,41 +30,48 @@ if !exists('g:clojure_fuzzy_indent_patterns') \ ] endif +" TODO: are all these options needed or relevant? Use "lispwords" instead? +" Defaults copied from: https://github.com/clojure-emacs/clojure-mode/blob/0e62583b5198f71856e4d7b80e1099789d47f2ed/clojure-mode.el#L1800-L1875 if !exists('g:clojure_indent_rules') - " Defaults copied from: https://github.com/clojure-emacs/clojure-mode/blob/0e62583b5198f71856e4d7b80e1099789d47f2ed/clojure-mode.el#L1800-L1875 let g:clojure_indent_rules = { \ 'ns': 1, - \ 'fn': 1, 'def': 1, 'defn': 1, 'bound-fn': 1, + \ 'fn': 1, 'def': 1, 'defn': 1, 'bound-fn': 1, 'fdef': 1, + \ 'let': 1, 'binding': 1, 'defmethod': 1, \ 'if': 1, 'if-not': 1, 'if-some': 1, 'if-let': 1, \ 'when': 1, 'when-not': 1, 'when-some': 1, 'when-let': 1, 'when-first': 1, \ 'case': 1, 'cond': 0, 'cond->': 1, 'cond->>': 1, 'condp': 2, \ 'while': 1, 'loop': 1, 'for': 1, 'doseq': 1, 'dotimes': 1, \ 'do': 0, 'doto': 1, 'comment': 0, 'as->': 2, \ 'delay': 0, 'future': 0, 'locking': 1, - \ 'fdef': 1, - \ 'extend': 1, \ 'try': 0, 'catch': 2, 'finally': 0, - \ 'let': 1, 'binding': 1, - \ 'defmethod': 1, - \ 'this-as': 1, - \ 'deftest': 1, 'testing': 1, 'use-fixtures': 1, 'are': 2, - \ 'alt!': 0, 'alt!!': 0, 'go': 0, 'go-loop': 1, 'thread': 0, - \ 'run': 1, 'run*': 1, 'fresh': 1 + \ 'reify': 1, 'proxy': 2, 'defrecord': 2, 'defprotocol': 1, 'definterface': 1, + \ 'extend': 1, 'extend-protocol': 1, 'extend-type': 1 \ } - - " (letfn '(1 ((:defn)) nil)) - " (proxy '(2 nil nil (:defn))) - " (reify '(:defn (1))) - " (deftype '(2 nil nil (:defn))) - " (defrecord '(2 nil nil (:defn))) - " (defprotocol '(1 (:defn))) - " (definterface '(1 (:defn))) - " (extend-protocol '(1 :defn)) - " (extend-type '(1 :defn)) - " (specify '(1 :defn)) ; ClojureScript - " (specify! '(1 :defn)) ; ClojureScript - " (this-as 1) ; ClojureScript - " clojure.test, core.async, core.logic + " (letfn) (1 ((:defn)) nil) + " (reify) (:defn (1)) + " (deftype defrecord proxy) (2 nil nil (:defn)) + " (defprotocol definterface extend-protocol extend-type) (1 (:defn)) + + " ClojureScript + call extend(g:clojure_indent_rules, { + \ 'this-as': 1, 'specify': 1, 'specify!': 1 + \ }) + " (specify specify!) (1 :defn) + + " clojure.test + call extend(g:clojure_indent_rules, { + \ 'deftest': 1, 'testing': 1, 'use-fixtures': 1, 'are': 2 + \ }) + + " core.async + call extend(g:clojure_indent_rules, { + \ 'alt!': 0, 'alt!!': 0, 'go': 0, 'go-loop': 1, 'thread': 0 + \ }) + + " core.logic + call extend(g:clojure_indent_rules, { + \ 'run': 1, 'run*': 1, 'fresh': 1 + \ }) endif " Get the value of a configuration option. From 6a3d2d345c49a96b56260b49ae5224445576ba77 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Sun, 22 Sep 2024 23:33:50 +0100 Subject: [PATCH 50/88] Don't use fn arg alignment indent when a keyword is in function position Fixes indentation of reader conditional macros. May choose to remove this if function argument indentation is generally preferred by programmers when a keyword is in function position. --- .../indent-test-cases/reader_conditionals/in.clj | 12 ++++++------ .../indent-test-cases/reader_conditionals/out.clj | 10 +++++----- indent/clojure.vim | 7 ++++--- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/clj/resources/indent-test-cases/reader_conditionals/in.clj b/clj/resources/indent-test-cases/reader_conditionals/in.clj index 9bc1c7b..f018a26 100644 --- a/clj/resources/indent-test-cases/reader_conditionals/in.clj +++ b/clj/resources/indent-test-cases/reader_conditionals/in.clj @@ -1,11 +1,11 @@ (def DateTime #?(:clj org.joda.time.DateTime, :cljs goog.date.UtcDateTime)) -#?(:cljs - (extend-protocol ToDateTime - goog.date.Date - (-to-date-time [x] - (goog.date.UtcDateTime. (.getYear x) (.getMonth x) (.getDate x))))) +#?(:clj + (defn regexp? + "Returns true if x is a Java regular expression pattern." +[x] + (instance? java.util.regex.Pattern x))) #?@(:clj [5 6 7 8] - :cljs [1 2 3 4]))) + :cljs [1 2 3 4]) diff --git a/clj/resources/indent-test-cases/reader_conditionals/out.clj b/clj/resources/indent-test-cases/reader_conditionals/out.clj index 5ad2990..1554098 100644 --- a/clj/resources/indent-test-cases/reader_conditionals/out.clj +++ b/clj/resources/indent-test-cases/reader_conditionals/out.clj @@ -1,11 +1,11 @@ (def DateTime #?(:clj org.joda.time.DateTime, :cljs goog.date.UtcDateTime)) -#?(:cljs - (extend-protocol ToDateTime - goog.date.Date - (-to-date-time [x] - (goog.date.UtcDateTime. (.getYear x) (.getMonth x) (.getDate x))))) +#?(:clj + (defn regexp? + "Returns true if x is a Java regular expression pattern." + [x] + (instance? java.util.regex.Pattern x))) #?@(:clj [5 6 7 8] :cljs [1 2 3 4]) diff --git a/indent/clojure.vim b/indent/clojure.vim index 11c8e65..47f797e 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -252,6 +252,7 @@ function! s:ListIndent(delim_pos) let base_indent = s:PosToCharCol(a:delim_pos) let ln = getline(a:delim_pos[0]) + let ln_content = ln[a:delim_pos[1]:] let sym_match = -1 @@ -268,7 +269,7 @@ function! s:ListIndent(delim_pos) " other indentation options. " TODO: simplify this. - let syms = split(ln[a:delim_pos[1]:], '[[:space:],;()\[\]{}@\\"^~`]', 1) + let syms = split(ln_content, '[[:space:],;()\[\]{}@\\"^~`]', 1) if !empty(syms) let sym = syms[0] @@ -292,12 +293,12 @@ function! s:ListIndent(delim_pos) endif " 2. Function indentation - " if first operand is on the same line? + " if first operand is on the same line? (and not a keyword) " - Indent subsequent lines to align with first operand. " else " - Indent 1 or 2 spaces. let indent_style = s:Conf('clojure_indent_style', 'always-align') - if indent_style !=# 'always-indent' + if indent_style !=# 'always-indent' && ln_content[0] !=# ':' let pos = s:FirstFnArgPos(a:delim_pos) if pos != [0, 0] | return s:PosToCharCol(pos) - 1 | endif endif From f271dca511aa6f458c0ae643f7cec07009beca36 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Sun, 22 Sep 2024 23:50:00 +0100 Subject: [PATCH 51/88] Add extra indentation test cases for records and protocols --- .../indent-test-cases/custom_types/in.clj | 20 +++++++++++++++++++ .../indent-test-cases/custom_types/out.clj | 20 +++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 clj/resources/indent-test-cases/custom_types/in.clj create mode 100644 clj/resources/indent-test-cases/custom_types/out.clj diff --git a/clj/resources/indent-test-cases/custom_types/in.clj b/clj/resources/indent-test-cases/custom_types/in.clj new file mode 100644 index 0000000..78ff69e --- /dev/null +++ b/clj/resources/indent-test-cases/custom_types/in.clj @@ -0,0 +1,20 @@ +(defrecord Thing [a] + FileNameMap + (getContentTypeFor [_ file-name] + (str a "-" file-name)) + Object + (toString [_] + "My very own thing!!")) + +(defrecord TheNameOfTheRecord + [a pretty long argument list] + SomeType + (assoc [_ x] + (.assoc pretty x 10))) + +(extend-protocol MyProtocol + goog.date.Date +(-to-date-time [x] + (goog.date.UtcDateTime. (.getYear x) + (.getMonth x) + (.getDate x)))) diff --git a/clj/resources/indent-test-cases/custom_types/out.clj b/clj/resources/indent-test-cases/custom_types/out.clj new file mode 100644 index 0000000..18f9241 --- /dev/null +++ b/clj/resources/indent-test-cases/custom_types/out.clj @@ -0,0 +1,20 @@ +(defrecord Thing [a] + FileNameMap + (getContentTypeFor [_ file-name] + (str a "-" file-name)) + Object + (toString [_] + "My very own thing!!")) + +(defrecord TheNameOfTheRecord + [a pretty long argument list] + SomeType + (assoc [_ x] + (.assoc pretty x 10))) + +(extend-protocol MyProtocol + goog.date.Date + (-to-date-time [x] + (goog.date.UtcDateTime. (.getYear x) + (.getMonth x) + (.getDate x)))) From b86a4c069e1399e0403d1b45a824af613beedb9a Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Sat, 5 Oct 2024 00:12:54 +0100 Subject: [PATCH 52/88] Begin refinements to configuration options --- .../s-expr_align-arguments/config.edn | 1 - .../s-expr_always-indent/config.edn | 1 - .../config.edn | 0 .../in.clj | 0 .../out.clj | 0 .../s-expr_traditional/config.edn | 1 + .../in.clj | 0 .../out.clj | 0 .../s-expr_uniform/config.edn | 1 + .../in.clj | 0 .../out.clj | 0 indent/clojure.vim | 52 ++++++++++++------- 12 files changed, 36 insertions(+), 20 deletions(-) delete mode 100644 clj/resources/indent-test-cases/s-expr_align-arguments/config.edn delete mode 100644 clj/resources/indent-test-cases/s-expr_always-indent/config.edn rename clj/resources/indent-test-cases/{s-expr_always-align => s-expr_standard}/config.edn (100%) rename clj/resources/indent-test-cases/{s-expr_align-arguments => s-expr_standard}/in.clj (100%) rename clj/resources/indent-test-cases/{s-expr_always-align => s-expr_standard}/out.clj (100%) create mode 100644 clj/resources/indent-test-cases/s-expr_traditional/config.edn rename clj/resources/indent-test-cases/{s-expr_always-align => s-expr_traditional}/in.clj (100%) rename clj/resources/indent-test-cases/{s-expr_align-arguments => s-expr_traditional}/out.clj (100%) create mode 100644 clj/resources/indent-test-cases/s-expr_uniform/config.edn rename clj/resources/indent-test-cases/{s-expr_always-indent => s-expr_uniform}/in.clj (100%) rename clj/resources/indent-test-cases/{s-expr_always-indent => s-expr_uniform}/out.clj (100%) diff --git a/clj/resources/indent-test-cases/s-expr_align-arguments/config.edn b/clj/resources/indent-test-cases/s-expr_align-arguments/config.edn deleted file mode 100644 index 8142eae..0000000 --- a/clj/resources/indent-test-cases/s-expr_align-arguments/config.edn +++ /dev/null @@ -1 +0,0 @@ -{:extra-cmds ["let g:clojure_indent_style = 'align-arguments'"]} diff --git a/clj/resources/indent-test-cases/s-expr_always-indent/config.edn b/clj/resources/indent-test-cases/s-expr_always-indent/config.edn deleted file mode 100644 index 6eddb66..0000000 --- a/clj/resources/indent-test-cases/s-expr_always-indent/config.edn +++ /dev/null @@ -1 +0,0 @@ -{:extra-cmds ["let g:clojure_indent_style = 'always-indent'"]} diff --git a/clj/resources/indent-test-cases/s-expr_always-align/config.edn b/clj/resources/indent-test-cases/s-expr_standard/config.edn similarity index 100% rename from clj/resources/indent-test-cases/s-expr_always-align/config.edn rename to clj/resources/indent-test-cases/s-expr_standard/config.edn diff --git a/clj/resources/indent-test-cases/s-expr_align-arguments/in.clj b/clj/resources/indent-test-cases/s-expr_standard/in.clj similarity index 100% rename from clj/resources/indent-test-cases/s-expr_align-arguments/in.clj rename to clj/resources/indent-test-cases/s-expr_standard/in.clj diff --git a/clj/resources/indent-test-cases/s-expr_always-align/out.clj b/clj/resources/indent-test-cases/s-expr_standard/out.clj similarity index 100% rename from clj/resources/indent-test-cases/s-expr_always-align/out.clj rename to clj/resources/indent-test-cases/s-expr_standard/out.clj diff --git a/clj/resources/indent-test-cases/s-expr_traditional/config.edn b/clj/resources/indent-test-cases/s-expr_traditional/config.edn new file mode 100644 index 0000000..245de34 --- /dev/null +++ b/clj/resources/indent-test-cases/s-expr_traditional/config.edn @@ -0,0 +1 @@ +{:extra-cmds ["let g:clojure_indent_style = 'traditional'"]} diff --git a/clj/resources/indent-test-cases/s-expr_always-align/in.clj b/clj/resources/indent-test-cases/s-expr_traditional/in.clj similarity index 100% rename from clj/resources/indent-test-cases/s-expr_always-align/in.clj rename to clj/resources/indent-test-cases/s-expr_traditional/in.clj diff --git a/clj/resources/indent-test-cases/s-expr_align-arguments/out.clj b/clj/resources/indent-test-cases/s-expr_traditional/out.clj similarity index 100% rename from clj/resources/indent-test-cases/s-expr_align-arguments/out.clj rename to clj/resources/indent-test-cases/s-expr_traditional/out.clj diff --git a/clj/resources/indent-test-cases/s-expr_uniform/config.edn b/clj/resources/indent-test-cases/s-expr_uniform/config.edn new file mode 100644 index 0000000..cfc5d71 --- /dev/null +++ b/clj/resources/indent-test-cases/s-expr_uniform/config.edn @@ -0,0 +1 @@ +{:extra-cmds ["let g:clojure_indent_style = 'uniform'"]} diff --git a/clj/resources/indent-test-cases/s-expr_always-indent/in.clj b/clj/resources/indent-test-cases/s-expr_uniform/in.clj similarity index 100% rename from clj/resources/indent-test-cases/s-expr_always-indent/in.clj rename to clj/resources/indent-test-cases/s-expr_uniform/in.clj diff --git a/clj/resources/indent-test-cases/s-expr_always-indent/out.clj b/clj/resources/indent-test-cases/s-expr_uniform/out.clj similarity index 100% rename from clj/resources/indent-test-cases/s-expr_always-indent/out.clj rename to clj/resources/indent-test-cases/s-expr_uniform/out.clj diff --git a/indent/clojure.vim b/indent/clojure.vim index 47f797e..6e89581 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -22,15 +22,36 @@ setlocal noautoindent nosmartindent nolisp setlocal softtabstop=2 shiftwidth=2 expandtab setlocal indentkeys=!,o,O -if !exists('g:clojure_fuzzy_indent_patterns') - let g:clojure_fuzzy_indent_patterns = [ - \ '^with-\%(meta\|in-str\|out-str\|loading-context\)\@!', - \ '^def', - \ '^let' - \ ] -endif +" Set a new configuration option with a default value. Assigns a script-local +" version too, which is the default fallback in case the global was "unlet". +function! s:SConf(name, default) abort + exec 'let' 's:' . a:name '=' string(a:default) + let n = 'g:' . a:name + if ! exists(n) | exec 'let' n '=' string(a:default) | endif +endfunction + +" Get the value of a configuration option with a possible fallback. If no +" fallback is given, uses the original config option value. +function! s:Conf(opt, fallback = v:null) abort + return a:fallback ==# v:null + \ ? get(b:, a:opt, get(g:, a:opt, get(s:, a:opt))) + \ : get(b:, a:opt, get(g:, a:opt, a:fallback)) +endfunction + +" Available options: +" - standard (Emacs equiv: always-align) +" - traditional (Emacs equiv: align-arguments) +" - uniform (Emacs equiv: always-indent) +call s:SConf('clojure_indent_style', 'standard') + +call s:SConf('clojure_align_multiline_strings', 0) + +call s:SConf('clojure_fuzzy_indent_patterns', [ +\ '^with-\%(meta\|in-str\|out-str\|loading-context\)\@!', +\ '^def', +\ '^let' +\ ]) -" TODO: are all these options needed or relevant? Use "lispwords" instead? " Defaults copied from: https://github.com/clojure-emacs/clojure-mode/blob/0e62583b5198f71856e4d7b80e1099789d47f2ed/clojure-mode.el#L1800-L1875 if !exists('g:clojure_indent_rules') let g:clojure_indent_rules = { @@ -74,11 +95,6 @@ if !exists('g:clojure_indent_rules') \ }) endif -" Get the value of a configuration option. -function! s:Conf(opt, default) - return get(b:, a:opt, get(g:, a:opt, a:default)) -endfunction - " Returns "1" if position "i_char" in "line_str" is preceded by an odd number " of backslash characters (i.e. escaped). function! s:IsEscaped(line_str, i_char) @@ -230,7 +246,7 @@ function! s:StringIndent(delim_pos) let m = mode() if m ==# 'i' || (m ==# 'n' && ! s:EqualsOperatorInEffect()) " If in insert mode, or normal mode but "=" is not in effect. - let alignment = s:Conf('clojure_align_multiline_strings', 0) + let alignment = s:Conf('clojure_align_multiline_strings') " -1: Indent along left edge, like traditional Lisps. " 0: Indent in alignment with end of the string start delimiter. " 1: Indent in alignment with string start delimiter. @@ -297,17 +313,17 @@ function! s:ListIndent(delim_pos) " - Indent subsequent lines to align with first operand. " else " - Indent 1 or 2 spaces. - let indent_style = s:Conf('clojure_indent_style', 'always-align') - if indent_style !=# 'always-indent' && ln_content[0] !=# ':' + let indent_style = s:Conf('clojure_indent_style') + if indent_style !=# 'uniform' && ln_content[0] !=# ':' let pos = s:FirstFnArgPos(a:delim_pos) if pos != [0, 0] | return s:PosToCharCol(pos) - 1 | endif endif " Fallback indentation for operands. When "clojure_indent_style" is - " "always-align", use 1 space indentation, else 2 space indentation. + " "standard", use 1 space indentation, else 2 space indentation. " The "sym_match" check handles the case when "clojure_indent_rules" " specified a value of "0". - return base_indent + (indent_style !=# 'always-align' || sym_match == 0) + return base_indent + (indent_style !=# 'standard' || sym_match == 0) endfunction function! s:ClojureIndent() From 01edfec86b35f7f1ae0bcc7dc3044323bde551af Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Sat, 5 Oct 2024 00:35:17 +0100 Subject: [PATCH 53/88] Fix config option lookup Vim script is infuriating sometimes! Turns out that the indentation tests locally passed with the previous code, but failed in CI and real life. --- indent/clojure.vim | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/indent/clojure.vim b/indent/clojure.vim index 6e89581..d68f516 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -25,17 +25,15 @@ setlocal indentkeys=!,o,O " Set a new configuration option with a default value. Assigns a script-local " version too, which is the default fallback in case the global was "unlet". function! s:SConf(name, default) abort - exec 'let' 's:' . a:name '=' string(a:default) + let s = 's:' . a:name let n = 'g:' . a:name - if ! exists(n) | exec 'let' n '=' string(a:default) | endif + exec 'let' 's:' . a:name '=' string(a:default) + if ! exists(n) | exec 'let' n '=' s | endif endfunction -" Get the value of a configuration option with a possible fallback. If no -" fallback is given, uses the original config option value. -function! s:Conf(opt, fallback = v:null) abort - return a:fallback ==# v:null - \ ? get(b:, a:opt, get(g:, a:opt, get(s:, a:opt))) - \ : get(b:, a:opt, get(g:, a:opt, a:fallback)) +" Get the value of a configuration option with a possible fallback. +function! s:Conf(opt, fallback) abort + return get(b:, a:opt, get(g:, a:opt, a:fallback)) endfunction " Available options: @@ -246,7 +244,7 @@ function! s:StringIndent(delim_pos) let m = mode() if m ==# 'i' || (m ==# 'n' && ! s:EqualsOperatorInEffect()) " If in insert mode, or normal mode but "=" is not in effect. - let alignment = s:Conf('clojure_align_multiline_strings') + let alignment = s:Conf('clojure_align_multiline_strings', s:clojure_align_multiline_strings) " -1: Indent along left edge, like traditional Lisps. " 0: Indent in alignment with end of the string start delimiter. " 1: Indent in alignment with string start delimiter. @@ -313,7 +311,7 @@ function! s:ListIndent(delim_pos) " - Indent subsequent lines to align with first operand. " else " - Indent 1 or 2 spaces. - let indent_style = s:Conf('clojure_indent_style') + let indent_style = s:Conf('clojure_indent_style', s:clojure_indent_style) if indent_style !=# 'uniform' && ln_content[0] !=# ':' let pos = s:FirstFnArgPos(a:delim_pos) if pos != [0, 0] | return s:PosToCharCol(pos) - 1 | endif From 9b5e42c14ea3ea68c94e7d2b2723f5fc5e6ca6d9 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Sat, 5 Oct 2024 00:48:20 +0100 Subject: [PATCH 54/88] Discovered a way to put comments within dict defs in Vim script --- indent/clojure.vim | 75 +++++++++++++++++++--------------------------- 1 file changed, 30 insertions(+), 45 deletions(-) diff --git a/indent/clojure.vim b/indent/clojure.vim index d68f516..9391457 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -23,12 +23,11 @@ setlocal softtabstop=2 shiftwidth=2 expandtab setlocal indentkeys=!,o,O " Set a new configuration option with a default value. Assigns a script-local -" version too, which is the default fallback in case the global was "unlet". +" version too, to be used as a default fallback if the global was "unlet". function! s:SConf(name, default) abort - let s = 's:' . a:name - let n = 'g:' . a:name + let [s, g] = ['s:' . a:name, 'g:' . a:name] exec 'let' 's:' . a:name '=' string(a:default) - if ! exists(n) | exec 'let' n '=' s | endif + if ! exists(g) | exec 'let' g '=' s | endif endfunction " Get the value of a configuration option with a possible fallback. @@ -51,47 +50,33 @@ call s:SConf('clojure_fuzzy_indent_patterns', [ \ ]) " Defaults copied from: https://github.com/clojure-emacs/clojure-mode/blob/0e62583b5198f71856e4d7b80e1099789d47f2ed/clojure-mode.el#L1800-L1875 -if !exists('g:clojure_indent_rules') - let g:clojure_indent_rules = { - \ 'ns': 1, - \ 'fn': 1, 'def': 1, 'defn': 1, 'bound-fn': 1, 'fdef': 1, - \ 'let': 1, 'binding': 1, 'defmethod': 1, - \ 'if': 1, 'if-not': 1, 'if-some': 1, 'if-let': 1, - \ 'when': 1, 'when-not': 1, 'when-some': 1, 'when-let': 1, 'when-first': 1, - \ 'case': 1, 'cond': 0, 'cond->': 1, 'cond->>': 1, 'condp': 2, - \ 'while': 1, 'loop': 1, 'for': 1, 'doseq': 1, 'dotimes': 1, - \ 'do': 0, 'doto': 1, 'comment': 0, 'as->': 2, - \ 'delay': 0, 'future': 0, 'locking': 1, - \ 'try': 0, 'catch': 2, 'finally': 0, - \ 'reify': 1, 'proxy': 2, 'defrecord': 2, 'defprotocol': 1, 'definterface': 1, - \ 'extend': 1, 'extend-protocol': 1, 'extend-type': 1 - \ } - " (letfn) (1 ((:defn)) nil) - " (reify) (:defn (1)) - " (deftype defrecord proxy) (2 nil nil (:defn)) - " (defprotocol definterface extend-protocol extend-type) (1 (:defn)) - - " ClojureScript - call extend(g:clojure_indent_rules, { - \ 'this-as': 1, 'specify': 1, 'specify!': 1 - \ }) - " (specify specify!) (1 :defn) - - " clojure.test - call extend(g:clojure_indent_rules, { - \ 'deftest': 1, 'testing': 1, 'use-fixtures': 1, 'are': 2 - \ }) - - " core.async - call extend(g:clojure_indent_rules, { - \ 'alt!': 0, 'alt!!': 0, 'go': 0, 'go-loop': 1, 'thread': 0 - \ }) - - " core.logic - call extend(g:clojure_indent_rules, { - \ 'run': 1, 'run*': 1, 'fresh': 1 - \ }) -endif +call s:SConf('clojure_indent_rules', { +\ 'ns': 1, +\ 'fn': 1, 'def': 1, 'defn': 1, 'bound-fn': 1, 'fdef': 1, +\ 'let': 1, 'binding': 1, 'defmethod': 1, +\ 'if': 1, 'if-not': 1, 'if-some': 1, 'if-let': 1, +\ 'when': 1, 'when-not': 1, 'when-some': 1, 'when-let': 1, 'when-first': 1, +\ 'case': 1, 'cond': 0, 'cond->': 1, 'cond->>': 1, 'condp': 2, +\ 'while': 1, 'loop': 1, 'for': 1, 'doseq': 1, 'dotimes': 1, +\ 'do': 0, 'doto': 1, 'comment': 0, 'as->': 2, +\ 'delay': 0, 'future': 0, 'locking': 1, +\ 'try': 0, 'catch': 2, 'finally': 0, +\ 'reify': 1, 'proxy': 2, 'defrecord': 2, 'defprotocol': 1, 'definterface': 1, +\ 'extend': 1, 'extend-protocol': 1, 'extend-type': 1, +"\ (letfn) (1 ((:defn)) nil) +"\ (reify) (:defn (1)) +"\ (deftype defrecord proxy) (2 nil nil (:defn)) +"\ (defprotocol definterface extend-protocol extend-type) (1 (:defn)) +"\ ClojureScript +\ 'this-as': 1, 'specify': 1, 'specify!': 1, +"\ (specify specify!) (1 :defn) +"\ clojure.test +\ 'deftest': 1, 'testing': 1, 'use-fixtures': 1, 'are': 2, +"\ core.async +\ 'alt!': 0, 'alt!!': 0, 'go': 0, 'go-loop': 1, 'thread': 0, +"\ core.logic +\ 'run': 1, 'run*': 1, 'fresh': 1 +\ }) " Returns "1" if position "i_char" in "line_str" is preceded by an odd number " of backslash characters (i.e. escaped). From a41fa1b997b4ca6e6b8a161cda73fe1240bf992d Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Sat, 5 Oct 2024 02:20:20 +0100 Subject: [PATCH 55/88] Improve indentation in "uniform" style and undo reader conditional work When the "uniform" indent style is selected, we no longer apply other list indentation rules/analysis. Unfortunately the reader conditional changes broke indentation for the following: (ns my-ns (:require [clojure.string :as str] [clojure.spec.alpha :as s])) Indenting it as this: (ns my-ns (:require [clojure.string :as str] [clojure.spec.alpha :as s])) Which is incorrect. --- indent/clojure.vim | 46 ++++++++++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/indent/clojure.vim b/indent/clojure.vim index 9391457..ac41847 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -40,19 +40,28 @@ endfunction " - traditional (Emacs equiv: align-arguments) " - uniform (Emacs equiv: always-indent) call s:SConf('clojure_indent_style', 'standard') - call s:SConf('clojure_align_multiline_strings', 0) - call s:SConf('clojure_fuzzy_indent_patterns', [ \ '^with-\%(meta\|in-str\|out-str\|loading-context\)\@!', \ '^def', \ '^let' \ ]) +" NOTE: When in "uniform" mode, ignores the "indent_style" and "indent_patterns" options. + +" FIXME: fix reader conditional tests. Include (:require [...]) test cases. +" Is it possible to fix reader conditional indentation? + +" TODO: make the indentation function usable from other Clojure-like languages. + +" TODO: explain the different numbers. The "indent_style" option can override "0" +" - -1 Not in dictionary, follow defaults. +" - 0: Align to first argument, else 2 space indentation. +" - 1+: 2 space indentation, no alignment. " Defaults copied from: https://github.com/clojure-emacs/clojure-mode/blob/0e62583b5198f71856e4d7b80e1099789d47f2ed/clojure-mode.el#L1800-L1875 call s:SConf('clojure_indent_rules', { \ 'ns': 1, -\ 'fn': 1, 'def': 1, 'defn': 1, 'bound-fn': 1, 'fdef': 1, +\ 'fn': 1, 'def': 1, 'defn': 1, 'bound-fn': 1, \ 'let': 1, 'binding': 1, 'defmethod': 1, \ 'if': 1, 'if-not': 1, 'if-some': 1, 'if-let': 1, \ 'when': 1, 'when-not': 1, 'when-some': 1, 'when-let': 1, 'when-first': 1, @@ -64,14 +73,14 @@ call s:SConf('clojure_indent_rules', { \ 'reify': 1, 'proxy': 2, 'defrecord': 2, 'defprotocol': 1, 'definterface': 1, \ 'extend': 1, 'extend-protocol': 1, 'extend-type': 1, "\ (letfn) (1 ((:defn)) nil) -"\ (reify) (:defn (1)) "\ (deftype defrecord proxy) (2 nil nil (:defn)) "\ (defprotocol definterface extend-protocol extend-type) (1 (:defn)) "\ ClojureScript \ 'this-as': 1, 'specify': 1, 'specify!': 1, -"\ (specify specify!) (1 :defn) "\ clojure.test \ 'deftest': 1, 'testing': 1, 'use-fixtures': 1, 'are': 2, +"\ clojure.spec.alpha +\ 'fdef': 1, "\ core.async \ 'alt!': 0, 'alt!!': 0, 'go': 0, 'go-loop': 1, 'thread': 0, "\ core.logic @@ -217,11 +226,10 @@ function! s:InsideForm(lnum) return ['^', [0, 0]] " Default to top-level. endfunction -" Returns "1" when the "=" operator is currently active. +" Returns "1" when the "=" operator is currently active, else "0". function! s:EqualsOperatorInEffect() return exists('*state') - \ ? v:operator ==# '=' && state('o') ==# 'o' - \ : 0 + \ ? v:operator ==# '=' && state('o') ==# 'o' : 0 endfunction function! s:StringIndent(delim_pos) @@ -249,7 +257,12 @@ function! s:ListIndent(delim_pos) " TODO: extend "s:InsideForm" to provide information about the " subforms being formatted to avoid second parsing step. + let indent_style = s:Conf('clojure_indent_style', s:clojure_indent_style) let base_indent = s:PosToCharCol(a:delim_pos) + + " Uniform indentation: just indent by 2 spaces. + if indent_style ==# 'uniform' | return base_indent + 1 | endif + let ln = getline(a:delim_pos[0]) let ln_content = ln[a:delim_pos[1]:] @@ -265,7 +278,7 @@ function! s:ListIndent(delim_pos) " TODO: handle complex indentation (e.g. letfn) and introduce " indentation config similar to Emacs' clojure-mode and cljfmt. " This new config option `clojure_indent_rules` should replace most - " other indentation options. + " other indentation options. Skip if "traditional" style was chosen. " TODO: simplify this. let syms = split(ln_content, '[[:space:],;()\[\]{}@\\"^~`]', 1) @@ -292,21 +305,18 @@ function! s:ListIndent(delim_pos) endif " 2. Function indentation - " if first operand is on the same line? (and not a keyword) + " if first operand is on the same line? " - Indent subsequent lines to align with first operand. " else " - Indent 1 or 2 spaces. - let indent_style = s:Conf('clojure_indent_style', s:clojure_indent_style) - if indent_style !=# 'uniform' && ln_content[0] !=# ':' - let pos = s:FirstFnArgPos(a:delim_pos) - if pos != [0, 0] | return s:PosToCharCol(pos) - 1 | endif - endif + let pos = s:FirstFnArgPos(a:delim_pos) + if pos != [0, 0] | return s:PosToCharCol(pos) - 1 | endif " Fallback indentation for operands. When "clojure_indent_style" is - " "standard", use 1 space indentation, else 2 space indentation. + " "traditional", use 2 space indentation, else 1 space indentation. " The "sym_match" check handles the case when "clojure_indent_rules" - " specified a value of "0". - return base_indent + (indent_style !=# 'standard' || sym_match == 0) + " specified a value of "0" for "standard" style. + return base_indent + (indent_style ==# 'traditional' || sym_match == 0) endfunction function! s:ClojureIndent() From 3487e077a53a9f305e2c2481980406b0ed1d9306 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Sat, 5 Oct 2024 14:39:10 +0100 Subject: [PATCH 56/88] Update README to better cover the new indentation options --- README.md | 132 ++++++++++++++++++++++++------------------------------ 1 file changed, 59 insertions(+), 73 deletions(-) diff --git a/README.md b/README.md index e33c8eb..8770e9c 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,18 @@ # Clojure.vim -[Clojure][] syntax highlighting for Vim and Neovim, including: +**Configurable [Clojure][] syntax highlighting, indentation (and more) for Vim and Neovim!** -- [Augmentable](#syntax-options) syntax highlighting. -- [Configurable](#indent-options) indentation. + +> [!TIP] +> This plugin comes packaged with Vim and Neovim. However if you would like to +> always use the latest version, you can install this plugin like you would any +> other. -## Installation - -These files are included in both Vim and Neovim. However if you would like the -latest changes just install this repository like any other plugin. - -Make sure that the following options are set in your vimrc so that all features -are enabled: +Make sure your vimrc contains the following options to enable all features: ```vim syntax on @@ -22,19 +20,7 @@ filetype plugin indent on ``` -## Configuration - -### Folding - -Setting `g:clojure_fold` to `1` will enable the folding of Clojure code. Any -list, vector or map that extends over more than one line can be folded using -the standard Vim fold commands. - -(Note that this option will not work with scripts that redefine the bracket -regions, such as rainbow parenphesis plugins.) - - -### Syntax options +## Syntax highlighting #### `g:clojure_syntax_keywords` @@ -69,34 +55,54 @@ stacked discard macros (e.g. `#_#_`). This inconsitency is why this option is disabled by default. -### Indent options +## Indentation -By default Clojure.vim will attempt to follow the indentation rules in the -[Clojure Style Guide](https://guide.clojure.style), but various configuration -options are provided to alter the indentation as you prefer. +Clojure indentation differs somewhat from traditional Lisps, due in part to the +use of square and curly brackets, and otherwise by community convention. As +these conventions are not universally followed, the Clojure indent script +offers ways to adjust the indentaion. -> **Warning**
-> If your installation of Vim does not include `searchpairpos()`, the indent -> script falls back to normal `'lisp'` and `'lispwords'` indenting, ignoring -> the following indentation options. +> [!WARNING] +> The indentation code has recently been rebuilt which included the removal of +> the following configuration options: +> +> - `clojure_fuzzy_indent` +> - `clojure_fuzzy_indent_blacklist` +> - `clojure_special_indent_words` +> - `clojure_cljfmt_compat` +> - `'lispwords'` -#### `clojure_indent_rules` +### Indentation style -> **Note**
-> The indentation code was recently rebuilt, which included the removal of -> several former configuration options (`clojure_fuzzy_indent`, -> `clojure_fuzzy_indent_patterns`, `clojure_fuzzy_indent_blacklist`, -> `clojure_special_indent_words`, `clojure_cljfmt_compat` and now ignores the -> value of `'lispwords'`). -> -> All of those options were rolled into this new option. +The `clojure_indent_style` config option controls the general indentation style +to use. Choose from several common presets: + +| Value | Default | Description | +|-------|---------|-------------| +| `standard` | ✅ | Conventional Clojure indentation. ([_Clojure Style Guide_](https://guide.clojure.style/).) | +| `traditional` | | Indent like traditional Lisps. (Earlier versions of Clojure.vim indented like this.) | +| `uniform` | | Indent uniformly to 2 spaces with no alignment (a.k.a. [_Tonsky_ indentation](https://tonsky.me/blog/clojurefmt/)). | + +```vim +let g:clojure_indent_style = 'uniform' " Set the default indent style... +let b:clojure_indent_style = 'traditional' " ...or override the default per-buffer. +``` -#### `clojure_align_multiline_strings` +### Indentation rules -Alter alignment of newly created lines within multi-line strings (and regular -expressions). +`clojure_indent_rules` + + +### Multi-line strings + +Control alignment of _new_ lines within Clojure multi-line strings and regular +expressions with `clojure_align_multiline_strings`. + +> [!NOTE] +> Indenting with `=` will not alter the indentation within multi-line strings, +> as this could break intentional formatting. ```clojure ;; let g:clojure_align_multiline_strings = 0 " Default @@ -117,32 +123,15 @@ eiusmod tempor incididunt ut labore et dolore magna aliqua.") There is also a buffer-local (`b:`) version of this option. -> **Note**
-> Indenting the string with `=` will not alter the indentation of existing -> multi-line strings as that would break intentional formatting. - -#### `clojure_align_subforms` - -By default, parenthesized compound forms that look like function calls and -whose head subform is on its own line have subsequent subforms indented by -two spaces relative to the opening paren: - -```clojure -(foo - bar - baz) -``` +## Code folding -Setting this option to `1` changes this behaviour so that all subforms are -aligned to the same column, emulating the default behaviour of -[clojure-mode.el](https://github.com/clojure-emacs/clojure-mode): +Setting `g:clojure_fold` to `1` will enable the folding of Clojure code. Any +list, vector or map that extends over more than one line can be folded using +the standard Vim fold commands. -```clojure -(foo - bar - baz) -``` +(Note that this option will not work with scripts that redefine the bracket +regions, such as rainbow parenphesis plugins.) ## Contribute @@ -157,18 +146,15 @@ Pull requests are welcome! Make sure to read the _Vim-clojure-static_ was created by [Sung Pae](https://github.com/guns). The original copies of the packaged runtime files came from [Meikel Brandmeyer](http://kotka.de/)'s [VimClojure][] project with permission. - -Thanks to [Tim Pope](https://github.com/tpope/) for advice in -[#vim](https://www.vi-improved.org/). +Thanks to [Tim Pope](https://github.com/tpope/) for advice in [#vim](https://www.vi-improved.org/). ## License -Clojure.vim is licensed under the [Vim -License](http://vimdoc.sourceforge.net/htmldoc/uganda.html#license) for -distribution with Vim. +Clojure.vim is licensed under the [Vim License](http://vimdoc.sourceforge.net/htmldoc/uganda.html#license) +for distribution with Vim. -- Copyright © 2020–2021, The clojure-vim contributors. +- Copyright © 2020–2024, The clojure-vim contributors. - Copyright © 2013–2018, Sung Pae. - Copyright © 2008–2012, Meikel Brandmeyer. - Copyright © 2007–2008, Toralf Wittner. From 3c5258c9059f34c4525e0814665ab9c7606b1b04 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Sat, 5 Oct 2024 15:40:09 +0100 Subject: [PATCH 57/88] Replace the old multi-line string config option --- README.md | 40 +++++++++---------- .../config.edn | 2 +- .../in.clj | 0 .../out.clj | 0 .../config.edn | 2 +- .../in.clj | 0 .../out.clj | 0 .../multi-line_strings_traditional/config.edn | 4 ++ .../multi-line_strings_traditional/in.clj | 27 +++++++++++++ .../multi-line_strings_traditional/out.clj | 33 +++++++++++++++ indent/clojure.vim | 22 ++++------ 11 files changed, 93 insertions(+), 37 deletions(-) rename clj/resources/indent-test-cases/{strings => multi-line_strings_pretty}/config.edn (68%) rename clj/resources/indent-test-cases/{strings => multi-line_strings_pretty}/in.clj (100%) rename clj/resources/indent-test-cases/{strings_align => multi-line_strings_pretty}/out.clj (100%) rename clj/resources/indent-test-cases/{strings_align => multi-line_strings_standard}/config.edn (67%) rename clj/resources/indent-test-cases/{strings_align => multi-line_strings_standard}/in.clj (100%) rename clj/resources/indent-test-cases/{strings => multi-line_strings_standard}/out.clj (100%) create mode 100644 clj/resources/indent-test-cases/multi-line_strings_traditional/config.edn create mode 100644 clj/resources/indent-test-cases/multi-line_strings_traditional/in.clj create mode 100644 clj/resources/indent-test-cases/multi-line_strings_traditional/out.clj diff --git a/README.md b/README.md index 8770e9c..5d25619 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,7 @@ offers ways to adjust the indentaion. > The indentation code has recently been rebuilt which included the removal of > the following configuration options: > +> - `clojure_align_multiline_strings` > - `clojure_fuzzy_indent` > - `clojure_fuzzy_indent_blacklist` > - `clojure_special_indent_words` @@ -85,43 +86,40 @@ to use. Choose from several common presets: | `uniform` | | Indent uniformly to 2 spaces with no alignment (a.k.a. [_Tonsky_ indentation](https://tonsky.me/blog/clojurefmt/)). | ```vim -let g:clojure_indent_style = 'uniform' " Set the default indent style... -let b:clojure_indent_style = 'traditional' " ...or override the default per-buffer. +let g:clojure_indent_style = 'uniform' " Set the default... +let b:clojure_indent_style = 'traditional' " ...or override it per-buffer. ``` ### Indentation rules -`clojure_indent_rules` +> [!NOTE] +> These options are ignored if an indentation style of "uniform" is selected. + +`clojure_indent_rules` & `clojure_fuzzy_indent_patterns` ### Multi-line strings Control alignment of _new_ lines within Clojure multi-line strings and regular -expressions with `clojure_align_multiline_strings`. +expressions with `clojure_indent_multiline_strings`. > [!NOTE] > Indenting with `=` will not alter the indentation within multi-line strings, > as this could break intentional formatting. -```clojure -;; let g:clojure_align_multiline_strings = 0 " Default -(def default - "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do - eiusmod tempor incididunt ut labore et dolore magna aliqua.") - -;; let g:clojure_align_multiline_strings = 1 -(def aligned - "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do - eiusmod tempor incididunt ut labore et dolore magna aliqua.") - -;; let g:clojure_align_multiline_strings = -1 -(def traditional - "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do -eiusmod tempor incididunt ut labore et dolore magna aliqua.") -``` +Pick from the following multi-line string indent styles: + +| Value | Default | Description | +|-------|---------|-------------| +| `standard` | ✅ | Align to the _front_ of the `"` or `#"` delimiter. Ideal for doc-strings. | +| `pretty` | | Align to the _back_ of the `"` or `#"` delimiter. | +| `traditional` | | No indent: align to left edge of file. | -There is also a buffer-local (`b:`) version of this option. +```vim +let g:clojure_indent_multiline_strings = 'pretty' " Set the default... +let b:clojure_indent_multiline_strings = 'traditional' " ...or override it per-buffer. +``` ## Code folding diff --git a/clj/resources/indent-test-cases/strings/config.edn b/clj/resources/indent-test-cases/multi-line_strings_pretty/config.edn similarity index 68% rename from clj/resources/indent-test-cases/strings/config.edn rename to clj/resources/indent-test-cases/multi-line_strings_pretty/config.edn index 6b367d4..a756dbb 100644 --- a/clj/resources/indent-test-cases/strings/config.edn +++ b/clj/resources/indent-test-cases/multi-line_strings_pretty/config.edn @@ -1,4 +1,4 @@ -{:extra-cmds ["let g:clojure_align_multiline_strings = 0" +{:extra-cmds ["let g:clojure_indent_multiline_strings = 'pretty'" "normal! G" "normal! o\u000atest \"hello\u000aworld\"" "normal! o\u000aregex #\"asdf\u000abar\""]} diff --git a/clj/resources/indent-test-cases/strings/in.clj b/clj/resources/indent-test-cases/multi-line_strings_pretty/in.clj similarity index 100% rename from clj/resources/indent-test-cases/strings/in.clj rename to clj/resources/indent-test-cases/multi-line_strings_pretty/in.clj diff --git a/clj/resources/indent-test-cases/strings_align/out.clj b/clj/resources/indent-test-cases/multi-line_strings_pretty/out.clj similarity index 100% rename from clj/resources/indent-test-cases/strings_align/out.clj rename to clj/resources/indent-test-cases/multi-line_strings_pretty/out.clj diff --git a/clj/resources/indent-test-cases/strings_align/config.edn b/clj/resources/indent-test-cases/multi-line_strings_standard/config.edn similarity index 67% rename from clj/resources/indent-test-cases/strings_align/config.edn rename to clj/resources/indent-test-cases/multi-line_strings_standard/config.edn index b1aff05..ba0c5e0 100644 --- a/clj/resources/indent-test-cases/strings_align/config.edn +++ b/clj/resources/indent-test-cases/multi-line_strings_standard/config.edn @@ -1,4 +1,4 @@ -{:extra-cmds ["let g:clojure_align_multiline_strings = 1" +{:extra-cmds ["let g:clojure_indent_multiline_strings = 'standard'" "normal! G" "normal! o\u000atest \"hello\u000aworld\"" "normal! o\u000aregex #\"asdf\u000abar\""]} diff --git a/clj/resources/indent-test-cases/strings_align/in.clj b/clj/resources/indent-test-cases/multi-line_strings_standard/in.clj similarity index 100% rename from clj/resources/indent-test-cases/strings_align/in.clj rename to clj/resources/indent-test-cases/multi-line_strings_standard/in.clj diff --git a/clj/resources/indent-test-cases/strings/out.clj b/clj/resources/indent-test-cases/multi-line_strings_standard/out.clj similarity index 100% rename from clj/resources/indent-test-cases/strings/out.clj rename to clj/resources/indent-test-cases/multi-line_strings_standard/out.clj diff --git a/clj/resources/indent-test-cases/multi-line_strings_traditional/config.edn b/clj/resources/indent-test-cases/multi-line_strings_traditional/config.edn new file mode 100644 index 0000000..a969070 --- /dev/null +++ b/clj/resources/indent-test-cases/multi-line_strings_traditional/config.edn @@ -0,0 +1,4 @@ +{:extra-cmds ["let g:clojure_indent_multiline_strings = 'traditional'" + "normal! G" + "normal! o\u000atest \"hello\u000aworld\"" + "normal! o\u000aregex #\"asdf\u000abar\""]} diff --git a/clj/resources/indent-test-cases/multi-line_strings_traditional/in.clj b/clj/resources/indent-test-cases/multi-line_strings_traditional/in.clj new file mode 100644 index 0000000..0efd8a2 --- /dev/null +++ b/clj/resources/indent-test-cases/multi-line_strings_traditional/in.clj @@ -0,0 +1,27 @@ +"foo + bar" + + asdf dfa sdfasdf " +asdf" + +(asdf [foo] + "hel + lo asd + fasdfa + sdf + asdf + as + as + asdf + df + df + world") + + #{:foo :bar + :biz + "ba + z"} + + #"foo + bar + biz" diff --git a/clj/resources/indent-test-cases/multi-line_strings_traditional/out.clj b/clj/resources/indent-test-cases/multi-line_strings_traditional/out.clj new file mode 100644 index 0000000..8860013 --- /dev/null +++ b/clj/resources/indent-test-cases/multi-line_strings_traditional/out.clj @@ -0,0 +1,33 @@ +"foo + bar" + +asdf dfa sdfasdf " +asdf" + +(asdf [foo] + "hel + lo asd + fasdfa + sdf + asdf + as + as + asdf + df + df + world") + +#{:foo :bar + :biz + "ba + z"} + +#"foo + bar + biz" + +test "hello +world" + +regex #"asdf +bar" diff --git a/indent/clojure.vim b/indent/clojure.vim index ac41847..bfaa18e 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -35,20 +35,14 @@ function! s:Conf(opt, fallback) abort return get(b:, a:opt, get(g:, a:opt, a:fallback)) endfunction -" Available options: -" - standard (Emacs equiv: always-align) -" - traditional (Emacs equiv: align-arguments) -" - uniform (Emacs equiv: always-indent) call s:SConf('clojure_indent_style', 'standard') -call s:SConf('clojure_align_multiline_strings', 0) +call s:SConf('clojure_indent_multiline_strings', 'standard') call s:SConf('clojure_fuzzy_indent_patterns', [ \ '^with-\%(meta\|in-str\|out-str\|loading-context\)\@!', \ '^def', \ '^let' \ ]) -" NOTE: When in "uniform" mode, ignores the "indent_style" and "indent_patterns" options. - " FIXME: fix reader conditional tests. Include (:require [...]) test cases. " Is it possible to fix reader conditional indentation? @@ -237,13 +231,13 @@ function! s:StringIndent(delim_pos) let m = mode() if m ==# 'i' || (m ==# 'n' && ! s:EqualsOperatorInEffect()) " If in insert mode, or normal mode but "=" is not in effect. - let alignment = s:Conf('clojure_align_multiline_strings', s:clojure_align_multiline_strings) - " -1: Indent along left edge, like traditional Lisps. - " 0: Indent in alignment with end of the string start delimiter. - " 1: Indent in alignment with string start delimiter. - if alignment == -1 | return 0 - elseif alignment == 1 | return s:PosToCharCol(a:delim_pos) - else + let alignment = s:Conf('clojure_indent_multiline_strings', s:clojure_indent_multiline_strings) + " standard: Indent in alignment with end of the string start delimiter. + " traditional: Indent along left edge, like traditional Lisps. + " pretty: Indent in alignment with string start delimiter. + if alignment ==# 'traditional' | return 0 + elseif alignment ==# 'pretty' | return s:PosToCharCol(a:delim_pos) + else " standard let col = a:delim_pos[1] let is_regex = col > 1 && getline(a:delim_pos[0])[col - 2] ==# '#' return s:PosToCharCol(a:delim_pos) - (is_regex ? 2 : 1) From 3b4dad8bd647b636a7ff257e85e81b936bd9d901 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Sat, 5 Oct 2024 16:02:23 +0100 Subject: [PATCH 58/88] Remove the link to the #vim IRC as the link is now dead --- README.md | 6 +++--- indent/clojure.vim | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5d25619..e51fbab 100644 --- a/README.md +++ b/README.md @@ -63,8 +63,8 @@ these conventions are not universally followed, the Clojure indent script offers ways to adjust the indentaion. > [!WARNING] -> The indentation code has recently been rebuilt which included the removal of -> the following configuration options: +> The indentation code has recently been rebuilt which included the +> removal/replacement of the following configuration options: > > - `clojure_align_multiline_strings` > - `clojure_fuzzy_indent` @@ -144,7 +144,7 @@ Pull requests are welcome! Make sure to read the _Vim-clojure-static_ was created by [Sung Pae](https://github.com/guns). The original copies of the packaged runtime files came from [Meikel Brandmeyer](http://kotka.de/)'s [VimClojure][] project with permission. -Thanks to [Tim Pope](https://github.com/tpope/) for advice in [#vim](https://www.vi-improved.org/). +Thanks to [Tim Pope](https://github.com/tpope/) for advice in `#vim` on IRC. ## License diff --git a/indent/clojure.vim b/indent/clojure.vim index bfaa18e..4e55426 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -37,6 +37,8 @@ endfunction call s:SConf('clojure_indent_style', 'standard') call s:SConf('clojure_indent_multiline_strings', 'standard') + +" TODO: rename this option. call s:SConf('clojure_fuzzy_indent_patterns', [ \ '^with-\%(meta\|in-str\|out-str\|loading-context\)\@!', \ '^def', From 117b082bb8d99a9a13800e011f726751557e133c Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Sat, 5 Oct 2024 16:06:19 +0100 Subject: [PATCH 59/88] Fix typos and re-add the insert-mode completion info to the README --- README.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index e51fbab..8d2b3c5 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,6 @@ **Configurable [Clojure][] syntax highlighting, indentation (and more) for Vim and Neovim!** - - > [!TIP] > This plugin comes packaged with Vim and Neovim. However if you would like to > always use the latest version, you can install this plugin like you would any @@ -51,7 +46,7 @@ set `(:refer-clojure :only [])`. Set this variable to `1` to enable highlighting of the "[discard reader macro](https://clojure.org/guides/weird_characters#_discard)". Due to current limitations in Vim's syntax rules, this option won't highlight -stacked discard macros (e.g. `#_#_`). This inconsitency is why this option is +stacked discard macros (e.g. `#_#_`). This inconsistency is why this option is disabled by default. @@ -60,7 +55,7 @@ disabled by default. Clojure indentation differs somewhat from traditional Lisps, due in part to the use of square and curly brackets, and otherwise by community convention. As these conventions are not universally followed, the Clojure indent script -offers ways to adjust the indentaion. +offers ways to adjust the indentation. > [!WARNING] > The indentation code has recently been rebuilt which included the @@ -129,7 +124,14 @@ list, vector or map that extends over more than one line can be folded using the standard Vim fold commands. (Note that this option will not work with scripts that redefine the bracket -regions, such as rainbow parenphesis plugins.) +regions, such as rainbow parenthesis plugins.) + + +## Insert-mode completion + +Very basic insert-mode completion of special forms and public vars from +`clojure.core` is included in Clojure.vim. Invoke it with `` or +``. ## Contribute From b26db95c9b9e1459e359ca0d641c418dca702da6 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Sat, 5 Oct 2024 19:06:49 +0100 Subject: [PATCH 60/88] Update the "About" section within the Vim help doc --- doc/clojure.txt | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/doc/clojure.txt b/doc/clojure.txt index a5034ea..a153217 100644 --- a/doc/clojure.txt +++ b/doc/clojure.txt @@ -114,22 +114,19 @@ ABOUT *clojure-about* This document and associated runtime files are maintained at: https://github.com/clojure-vim/clojure.vim -Distributed under the Vim license. See |license|. - -syntax/clojure.vim - - Copyright 2007-2008 (c) Toralf Wittner - Copyright 2008-2012 (c) Meikel Brandmeyer +Maintainer: Alex Vear +syntax/clojure.vim, ftdetect/clojure.vim, ftplugin/clojure.vim, indent/clojure.vim - Copyright 2008-2012 (c) Meikel Brandmeyer - -Modified and relicensed under the Vim License for distribution with Vim: + Distributed under the Vim license. See |license|. - Copyright 2013-2014 (c) Sung Pae + Copyright 2007-2008 (c) Toralf Wittner + Copyright 2008-2012 (c) Meikel Brandmeyer + Copyright 2013-2018 (c) Sung Pae + Copyright 2020–2024 (c) The clojure-vim contributors Last Change: %%RELEASE_DATE%% From c4a25c57a4313e366e8e2084e3f72d80f5528bc6 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Sat, 5 Oct 2024 22:58:39 +0100 Subject: [PATCH 61/88] Update indentation section of the Vim help doc --- README.md | 6 ++- doc/clojure.txt | 137 ++++++++++++++++++++++++++++++++---------------- 2 files changed, 97 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index 8d2b3c5..066cc1d 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ filetype plugin indent on ## Syntax highlighting -#### `g:clojure_syntax_keywords` +### `g:clojure_syntax_keywords` Syntax highlighting of public vars in `clojure.core` is provided by default, but additional symbols can be highlighted by adding them to the @@ -41,7 +41,7 @@ will not be highlighted by default. This is useful for namespaces that have set `(:refer-clojure :only [])`. -#### `g:clojure_discard_macro` +### `g:clojure_discard_macro` Set this variable to `1` to enable highlighting of the "[discard reader macro](https://clojure.org/guides/weird_characters#_discard)". @@ -61,6 +61,8 @@ offers ways to adjust the indentation. > The indentation code has recently been rebuilt which included the > removal/replacement of the following configuration options: > +> - `clojure_maxlines` +> - `clojure_align_subforms` > - `clojure_align_multiline_strings` > - `clojure_fuzzy_indent` > - `clojure_fuzzy_indent_blacklist` diff --git a/doc/clojure.txt b/doc/clojure.txt index a153217..e1c7bdd 100644 --- a/doc/clojure.txt +++ b/doc/clojure.txt @@ -5,68 +5,117 @@ INTRODUCTION *clojure-introduction* Clojure runtime files for Vim. -CLOJURE *ft-clojure-indent* *clojure-indent* +CLOJURE *ft-clojure-indent* *clojure-indent* -By default Clojure.vim will attempt to follow the indentation rules in the -Clojure Style Guide, [1] but various configuration options are provided to -alter the indentation as you prefer. +Clojure indentation differs somewhat from traditional Lisps, due in part to +the use of square and curly brackets, and otherwise by community convention. +As these conventions are not universally followed, the Clojure indent script +offers ways to adjust the indentation. -[1]: https://guide.clojure.style -WARNING: if your installation of Vim does not include `searchpairpos()`, the -indent script falls back to normal 'lisp' and 'lispwords' indenting, -ignoring the following indentation options. + *g:clojure_indent_style* + *b:clojure_indent_style* - *b:clojure_indent_rules* - *g:clojure_indent_rules* +The `clojure_indent_style` config option controls the general indentation style +to use. Choose from several common presets: -TODO: add this option and write this section. +* `standard` (default): + Conventional Clojure indentation. (Clojure Style Guide [1]) > + + (my-fn 1 + 2) - *b:clojure_align_multiline_strings* - *g:clojure_align_multiline_strings* + (my-fn + 1 + 2) +< +* `traditional`: + Indent like traditional Lisps. > + + (my-fn 1 + 2) -Alter alignment of newly created lines within multi-line strings (and regular -expressions). + (my-fn + 1 + 2) +< +* `uniform`: + Indent uniformly to 2 spaces with no alignment (aka Tonsky indentation [2]). > - ;; let g:clojure_align_multiline_strings = 0 " Default - (def default - "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do - eiusmod tempor incididunt ut labore et dolore magna aliqua.") - - ;; let g:clojure_align_multiline_strings = 1 - (def aligned - "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do - eiusmod tempor incididunt ut labore et dolore magna aliqua.") - - ;; let g:clojure_align_multiline_strings = -1 - (def traditional - "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do - eiusmod tempor incididunt ut labore et dolore magna aliqua.") + (my-fn 1 + 2) + + (my-fn + 1 + 2) < -NOTE: indenting the string with |=| will not alter the indentation of existing -multi-line strings as that would break intentional formatting. +[1]: https://guide.clojure.style/ +[2]: https://tonsky.me/blog/clojurefmt/ + + + *g:clojure_indent_rules* + *b:clojure_indent_rules* + +TODO: add this option and write this section. + - *g:clojure_align_subforms* + *g:clojure_fuzzy_indent_patterns* + *b:clojure_fuzzy_indent_patterns* -By default, parenthesized compound forms that look like function calls and -whose head subform is on its own line have subsequent subforms indented by -two spaces relative to the opening paren: +TODO: add this option and write this section. + + + *g:clojure_indent_multiline_strings* + *b:clojure_indent_multiline_strings* + +Control alignment of new lines within Clojure multi-line strings and regular +expressions with `clojure_indent_multiline_strings`. + +NOTE: indenting with |=| will not alter the indentation within multi-line +strings, as this could break intentional formatting. + +Pick from the following multi-line string indent styles: + +* `standard` (default): + Align to the front of the `"` or `#"` delimiter. Ideal for doc-strings. > - (foo - bar - baz) + |(def standard + | "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do + | eiusmod tempor incididunt ut labore et dolore magna aliqua.") < -Setting this option to `1` changes this behaviour so that all subforms are -aligned to the same column, emulating the default behaviour of -clojure-mode.el: +* `pretty`: + Align to the back of the `"` or `#"` delimiter. > - (foo - bar - baz) + |(def aligned + | "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do + | eiusmod tempor incididunt ut labore et dolore magna aliqua.") < +* `traditional`: + No indent, align to left edge of the file. +> + |(def traditional + | "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do + |eiusmod tempor incididunt ut labore et dolore magna aliqua.") +< + + *clojure-indent-deprecations* + +During the Clojure indentation script rebuild, the following configuration +options were removed/replaced: + +* *g:clojure_maxlines* -> none +* *g:clojure_cljfmt_compat*: -> |g:clojure_indent_style| +* *g:clojure_align_subforms* -> |g:clojure_indent_style| +* *g:clojure_align_multiline_strings* -> |g:clojure_indent_multiline_strings| +* *g:clojure_special_indent_words*: -> |g:clojure_indent_rules| +* *g:clojure_fuzzy_indent*: -> none +* *g:clojure_fuzzy_indent_blacklist*: -> none +* |'lispwords'| -> |g:clojure_indent_rules| + CLOJURE *ft-clojure-syntax* + *g:clojure_syntax_keywords* Syntax highlighting of public vars in "clojure.core" is provided by default, From 17425494e027fba98193f7d065fdb604bca2f1bb Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Sat, 5 Oct 2024 23:05:18 +0100 Subject: [PATCH 62/88] Improve indentation style examples in Vim help doc --- doc/clojure.txt | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/doc/clojure.txt b/doc/clojure.txt index e1c7bdd..d6a43ca 100644 --- a/doc/clojure.txt +++ b/doc/clojure.txt @@ -22,32 +22,32 @@ to use. Choose from several common presets: * `standard` (default): Conventional Clojure indentation. (Clojure Style Guide [1]) > - (my-fn 1 - 2) - - (my-fn - 1 - 2) + |(filter even? + | [1 2 3]) + | + |(filter + | even? + | [1 2 3]) < * `traditional`: Indent like traditional Lisps. > - (my-fn 1 - 2) - - (my-fn - 1 - 2) + |(filter even? + | [1 2 3]) + | + |(filter + | even? + | [1 2 3]) < * `uniform`: Indent uniformly to 2 spaces with no alignment (aka Tonsky indentation [2]). > - (my-fn 1 - 2) - - (my-fn - 1 - 2) + |(filter even? + | [1 2 3]) + | + |(filter + | even? + | [1 2 3]) < [1]: https://guide.clojure.style/ [2]: https://tonsky.me/blog/clojurefmt/ @@ -86,7 +86,7 @@ Pick from the following multi-line string indent styles: * `pretty`: Align to the back of the `"` or `#"` delimiter. > - |(def aligned + |(def pretty | "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do | eiusmod tempor incididunt ut labore et dolore magna aliqua.") < From 98003681219b6c81299aee48d7d72a631da1bcd3 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Sat, 5 Oct 2024 23:14:19 +0100 Subject: [PATCH 63/88] Minor documentation and comment refinements --- doc/clojure.txt | 12 ++++++------ indent/clojure.vim | 19 ++++++------------- 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/doc/clojure.txt b/doc/clojure.txt index d6a43ca..eaae572 100644 --- a/doc/clojure.txt +++ b/doc/clojure.txt @@ -100,16 +100,16 @@ Pick from the following multi-line string indent styles: *clojure-indent-deprecations* -During the Clojure indentation script rebuild, the following configuration -options were removed/replaced: +As part of the Clojure indentation script rebuild, the following configuration +options have been removed/replaced: * *g:clojure_maxlines* -> none -* *g:clojure_cljfmt_compat*: -> |g:clojure_indent_style| +* *g:clojure_cljfmt_compat* -> |g:clojure_indent_style| * *g:clojure_align_subforms* -> |g:clojure_indent_style| * *g:clojure_align_multiline_strings* -> |g:clojure_indent_multiline_strings| -* *g:clojure_special_indent_words*: -> |g:clojure_indent_rules| -* *g:clojure_fuzzy_indent*: -> none -* *g:clojure_fuzzy_indent_blacklist*: -> none +* *g:clojure_special_indent_words* -> |g:clojure_indent_rules| +* *g:clojure_fuzzy_indent* -> none +* *g:clojure_fuzzy_indent_blacklist* -> none * |'lispwords'| -> |g:clojure_indent_rules| diff --git a/indent/clojure.vim b/indent/clojure.vim index 4e55426..f81b297 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -22,6 +22,9 @@ setlocal noautoindent nosmartindent nolisp setlocal softtabstop=2 shiftwidth=2 expandtab setlocal indentkeys=!,o,O +" FIXME: fix reader conditional tests. Include (:require [...]) test cases. +" Is it possible to fix reader conditional indentation? + " Set a new configuration option with a default value. Assigns a script-local " version too, to be used as a default fallback if the global was "unlet". function! s:SConf(name, default) abort @@ -37,19 +40,12 @@ endfunction call s:SConf('clojure_indent_style', 'standard') call s:SConf('clojure_indent_multiline_strings', 'standard') - -" TODO: rename this option. call s:SConf('clojure_fuzzy_indent_patterns', [ \ '^with-\%(meta\|in-str\|out-str\|loading-context\)\@!', \ '^def', \ '^let' \ ]) -" FIXME: fix reader conditional tests. Include (:require [...]) test cases. -" Is it possible to fix reader conditional indentation? - -" TODO: make the indentation function usable from other Clojure-like languages. - " TODO: explain the different numbers. The "indent_style" option can override "0" " - -1 Not in dictionary, follow defaults. " - 0: Align to first argument, else 2 space indentation. @@ -93,8 +89,7 @@ endfunction " Used during list function indentation. Returns the position of the first " operand in the list on the first line of the form at "pos". function! s:FirstFnArgPos(pos) - " TODO: ignore comments. - " TODO: handle escaped characters! + " TODO: ignore comments and handle escaped characters! let lnr = a:pos[0] let s:in_form_current_form = a:pos call cursor(lnr, a:pos[1] + 1) @@ -234,9 +229,6 @@ function! s:StringIndent(delim_pos) if m ==# 'i' || (m ==# 'n' && ! s:EqualsOperatorInEffect()) " If in insert mode, or normal mode but "=" is not in effect. let alignment = s:Conf('clojure_indent_multiline_strings', s:clojure_indent_multiline_strings) - " standard: Indent in alignment with end of the string start delimiter. - " traditional: Indent along left edge, like traditional Lisps. - " pretty: Indent in alignment with string start delimiter. if alignment ==# 'traditional' | return 0 elseif alignment ==# 'pretty' | return s:PosToCharCol(a:delim_pos) else " standard @@ -288,7 +280,7 @@ function! s:ListIndent(delim_pos) let [_namespace, name] = split(sym, '/') endif - " TODO: replace `clojure_fuzzy_indent_patterns` with `clojure_indent_patterns` + " TODO: replace `clojure_fuzzy_indent_patterns` with `clojure_indent_patterns`? for pat in s:Conf('clojure_fuzzy_indent_patterns', []) if sym =~# pat | return base_indent + 1 | endif endfor @@ -315,6 +307,7 @@ function! s:ListIndent(delim_pos) return base_indent + (indent_style ==# 'traditional' || sym_match == 0) endfunction +" TODO: make this usable from other Clojure-like languages. function! s:ClojureIndent() " Calculate and return indent to use based on the matching form. let [form, pos] = s:InsideForm(v:lnum) From 69a0b6f90be276dcc1dd6246b6138603bc5f4b15 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Sat, 5 Oct 2024 23:34:17 +0100 Subject: [PATCH 64/88] In README, mention the replacements for each old indent config option --- README.md | 18 ++++++++++-------- indent/clojure.vim | 12 ++++-------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 066cc1d..99c0ca3 100644 --- a/README.md +++ b/README.md @@ -61,14 +61,16 @@ offers ways to adjust the indentation. > The indentation code has recently been rebuilt which included the > removal/replacement of the following configuration options: > -> - `clojure_maxlines` -> - `clojure_align_subforms` -> - `clojure_align_multiline_strings` -> - `clojure_fuzzy_indent` -> - `clojure_fuzzy_indent_blacklist` -> - `clojure_special_indent_words` -> - `clojure_cljfmt_compat` -> - `'lispwords'` +> | Config option | Replacement (if any) | +> |-----------------------------------|------------------------------------| +> | `clojure_maxlines` | | +> | `clojure_cljfmt_compat` | `clojure_indent_style` | +> | `clojure_align_subforms` | `clojure_indent_style` | +> | `clojure_align_multiline_strings` | `clojure_indent_multiline_strings` | +> | `clojure_fuzzy_indent` | | +> | `clojure_fuzzy_indent_blacklist` | | +> | `clojure_special_indent_words` | `clojure_indent_rules` | +> | `'lispwords'` | `clojure_indent_rules` | ### Indentation style diff --git a/indent/clojure.vim b/indent/clojure.vim index f81b297..004c54b 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -52,16 +52,13 @@ call s:SConf('clojure_fuzzy_indent_patterns', [ " - 1+: 2 space indentation, no alignment. " Defaults copied from: https://github.com/clojure-emacs/clojure-mode/blob/0e62583b5198f71856e4d7b80e1099789d47f2ed/clojure-mode.el#L1800-L1875 call s:SConf('clojure_indent_rules', { -\ 'ns': 1, -\ 'fn': 1, 'def': 1, 'defn': 1, 'bound-fn': 1, -\ 'let': 1, 'binding': 1, 'defmethod': 1, +\ 'fn': 1, 'def': 1, 'defn': 1, 'bound-fn': 1, 'let': 1, 'binding': 1, 'defmethod': 1, \ 'if': 1, 'if-not': 1, 'if-some': 1, 'if-let': 1, \ 'when': 1, 'when-not': 1, 'when-some': 1, 'when-let': 1, 'when-first': 1, \ 'case': 1, 'cond': 0, 'cond->': 1, 'cond->>': 1, 'condp': 2, \ 'while': 1, 'loop': 1, 'for': 1, 'doseq': 1, 'dotimes': 1, -\ 'do': 0, 'doto': 1, 'comment': 0, 'as->': 2, -\ 'delay': 0, 'future': 0, 'locking': 1, -\ 'try': 0, 'catch': 2, 'finally': 0, +\ 'ns': 1, 'do': 0, 'doto': 1, 'comment': 0, 'as->': 2, +\ 'delay': 0, 'future': 0, 'locking': 1, 'try': 0, 'catch': 2, 'finally': 0, \ 'reify': 1, 'proxy': 2, 'defrecord': 2, 'defprotocol': 1, 'definterface': 1, \ 'extend': 1, 'extend-protocol': 1, 'extend-type': 1, "\ (letfn) (1 ((:defn)) nil) @@ -265,8 +262,7 @@ function! s:ListIndent(delim_pos) " TODO: handle complex indentation (e.g. letfn) and introduce " indentation config similar to Emacs' clojure-mode and cljfmt. - " This new config option `clojure_indent_rules` should replace most - " other indentation options. Skip if "traditional" style was chosen. + " Skip if "traditional" style was chosen. " TODO: simplify this. let syms = split(ln_content, '[[:space:],;()\[\]{}@\\"^~`]', 1) From 058b4a75706419d7780997334c2d8d1998659ed2 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Sun, 6 Oct 2024 16:17:56 +0100 Subject: [PATCH 65/88] Add indentation tests covering keywords in function position --- README.md | 2 ++ .../indent-test-cases/s-expr_standard/in.clj | 12 ++++++++++++ .../indent-test-cases/s-expr_standard/out.clj | 12 ++++++++++++ .../indent-test-cases/s-expr_traditional/in.clj | 12 ++++++++++++ .../indent-test-cases/s-expr_traditional/out.clj | 12 ++++++++++++ .../indent-test-cases/s-expr_uniform/in.clj | 12 ++++++++++++ .../indent-test-cases/s-expr_uniform/out.clj | 12 ++++++++++++ indent/clojure.vim | 5 ++--- 8 files changed, 76 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 99c0ca3..ac82f82 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,8 @@ let b:clojure_indent_style = 'traditional' " ...or override it per-buffer. > [!NOTE] > These options are ignored if an indentation style of "uniform" is selected. + + `clojure_indent_rules` & `clojure_fuzzy_indent_patterns` diff --git a/clj/resources/indent-test-cases/s-expr_standard/in.clj b/clj/resources/indent-test-cases/s-expr_standard/in.clj index cb9f3e8..b703628 100644 --- a/clj/resources/indent-test-cases/s-expr_standard/in.clj +++ b/clj/resources/indent-test-cases/s-expr_standard/in.clj @@ -83,3 +83,15 @@ bar ('foo bar biz) + + (ns my-namespace + (:require [foo :as f] + [bar :refer [x]]) + (:import + (java.io File + IOException) + [clojure.lang PersistentQueue])) + + (import '(java.io File + IOException) + '(clojure.lang PersistentQueue)) diff --git a/clj/resources/indent-test-cases/s-expr_standard/out.clj b/clj/resources/indent-test-cases/s-expr_standard/out.clj index 0af4f24..03b8120 100644 --- a/clj/resources/indent-test-cases/s-expr_standard/out.clj +++ b/clj/resources/indent-test-cases/s-expr_standard/out.clj @@ -83,3 +83,15 @@ ('foo bar biz) + +(ns my-namespace + (:require [foo :as f] + [bar :refer [x]]) + (:import + (java.io File + IOException) + [clojure.lang PersistentQueue])) + +(import '(java.io File + IOException) + '(clojure.lang PersistentQueue)) diff --git a/clj/resources/indent-test-cases/s-expr_traditional/in.clj b/clj/resources/indent-test-cases/s-expr_traditional/in.clj index cb9f3e8..b703628 100644 --- a/clj/resources/indent-test-cases/s-expr_traditional/in.clj +++ b/clj/resources/indent-test-cases/s-expr_traditional/in.clj @@ -83,3 +83,15 @@ bar ('foo bar biz) + + (ns my-namespace + (:require [foo :as f] + [bar :refer [x]]) + (:import + (java.io File + IOException) + [clojure.lang PersistentQueue])) + + (import '(java.io File + IOException) + '(clojure.lang PersistentQueue)) diff --git a/clj/resources/indent-test-cases/s-expr_traditional/out.clj b/clj/resources/indent-test-cases/s-expr_traditional/out.clj index 35b9e00..51d8683 100644 --- a/clj/resources/indent-test-cases/s-expr_traditional/out.clj +++ b/clj/resources/indent-test-cases/s-expr_traditional/out.clj @@ -83,3 +83,15 @@ ('foo bar biz) + +(ns my-namespace + (:require [foo :as f] + [bar :refer [x]]) + (:import + (java.io File + IOException) + [clojure.lang PersistentQueue])) + +(import '(java.io File + IOException) + '(clojure.lang PersistentQueue)) diff --git a/clj/resources/indent-test-cases/s-expr_uniform/in.clj b/clj/resources/indent-test-cases/s-expr_uniform/in.clj index cb9f3e8..b703628 100644 --- a/clj/resources/indent-test-cases/s-expr_uniform/in.clj +++ b/clj/resources/indent-test-cases/s-expr_uniform/in.clj @@ -83,3 +83,15 @@ bar ('foo bar biz) + + (ns my-namespace + (:require [foo :as f] + [bar :refer [x]]) + (:import + (java.io File + IOException) + [clojure.lang PersistentQueue])) + + (import '(java.io File + IOException) + '(clojure.lang PersistentQueue)) diff --git a/clj/resources/indent-test-cases/s-expr_uniform/out.clj b/clj/resources/indent-test-cases/s-expr_uniform/out.clj index 9ca0cc7..82342ce 100644 --- a/clj/resources/indent-test-cases/s-expr_uniform/out.clj +++ b/clj/resources/indent-test-cases/s-expr_uniform/out.clj @@ -83,3 +83,15 @@ ('foo bar biz) + +(ns my-namespace + (:require [foo :as f] + [bar :refer [x]]) + (:import + (java.io File + IOException) + [clojure.lang PersistentQueue])) + +(import '(java.io File + IOException) + '(clojure.lang PersistentQueue)) diff --git a/indent/clojure.vim b/indent/clojure.vim index 004c54b..fc8e8a9 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -22,9 +22,6 @@ setlocal noautoindent nosmartindent nolisp setlocal softtabstop=2 shiftwidth=2 expandtab setlocal indentkeys=!,o,O -" FIXME: fix reader conditional tests. Include (:require [...]) test cases. -" Is it possible to fix reader conditional indentation? - " Set a new configuration option with a default value. Assigns a script-local " version too, to be used as a default fallback if the global was "unlet". function! s:SConf(name, default) abort @@ -46,6 +43,8 @@ call s:SConf('clojure_fuzzy_indent_patterns', [ \ '^let' \ ]) +" FIXME: reader conditional indentation? + " TODO: explain the different numbers. The "indent_style" option can override "0" " - -1 Not in dictionary, follow defaults. " - 0: Align to first argument, else 2 space indentation. From 0a835d8746857d51ff1197a6c4f3222b4028da86 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Sun, 6 Oct 2024 16:52:35 +0100 Subject: [PATCH 66/88] Update indent regular expressions to set the required 'magic' mode Makes indentation more robust against users toggling the `'magic'` option. --- indent/clojure.vim | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/indent/clojure.vim b/indent/clojure.vim index fc8e8a9..3ad08cd 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -38,9 +38,9 @@ endfunction call s:SConf('clojure_indent_style', 'standard') call s:SConf('clojure_indent_multiline_strings', 'standard') call s:SConf('clojure_fuzzy_indent_patterns', [ -\ '^with-\%(meta\|in-str\|out-str\|loading-context\)\@!', -\ '^def', -\ '^let' +\ '\m^with-\%(meta\|in-str\|out-str\|loading-context\)\@!', +\ '\m^def', +\ '\m^let' \ ]) " FIXME: reader conditional indentation? @@ -89,13 +89,13 @@ function! s:FirstFnArgPos(pos) let lnr = a:pos[0] let s:in_form_current_form = a:pos call cursor(lnr, a:pos[1] + 1) - return searchpos('[ ,]\+\zs', 'z', lnr, 0, function('IsSubForm')) + return searchpos('\m[ ,]\+\zs', 'z', lnr, 0, function('IsSubForm')) endfunction " Used by "s:FirstFnArgPos" function to skip over subforms as the first value " in a list form. function! s:IsSubForm() - let pos = searchpairpos('[([{"]', '', '[)\]}"]', 'b') + let pos = searchpairpos('\m[([{"]', '', '\m[)\]}"]', 'b') return pos != [0, 0] && pos != s:in_form_current_form endfunction @@ -118,7 +118,7 @@ function! s:TokeniseLine(line_num) while 1 " We perform searches within the buffer (and move the cusor) " for better performance than looping char by char in a line. - let token_pos = searchpos('[()[\]{};"]', 'bW', a:line_num) + let token_pos = searchpos('\m[()[\]{};"]', 'bW', a:line_num) " No more matches, exit loop. if token_pos == [0, 0] | break | endif @@ -266,13 +266,13 @@ function! s:ListIndent(delim_pos) " TODO: simplify this. let syms = split(ln_content, '[[:space:],;()\[\]{}@\\"^~`]', 1) - if !empty(syms) + if ! empty(syms) let sym = syms[0] if sym =~# '\v^%([a-zA-Z!$&*_+=|<>?-]|[^\x00-\x7F])' " TODO: handle namespaced and non-namespaced variants. - if sym =~# './.' - let [_namespace, name] = split(sym, '/') + if sym =~# '\m./.' + let [_namespace, name] = split(sym, '\m/') endif " TODO: replace `clojure_fuzzy_indent_patterns` with `clojure_indent_patterns`? From 0d865c578661de5a51355bf64dadfe2d62218682 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Mon, 7 Oct 2024 20:48:32 +0100 Subject: [PATCH 67/88] Compress comments on indent code --- indent/clojure.vim | 33 +++++++++++---------------------- 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/indent/clojure.vim b/indent/clojure.vim index 3ad08cd..2bc1a37 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -18,9 +18,8 @@ set cpoptions&vim let b:undo_indent = 'setlocal autoindent< smartindent< expandtab< softtabstop< shiftwidth< indentexpr< indentkeys< lisp<' -setlocal noautoindent nosmartindent nolisp +setlocal noautoindent nosmartindent nolisp indentkeys=!,o,O setlocal softtabstop=2 shiftwidth=2 expandtab -setlocal indentkeys=!,o,O " Set a new configuration option with a default value. Assigns a script-local " version too, to be used as a default fallback if the global was "unlet". @@ -60,9 +59,8 @@ call s:SConf('clojure_indent_rules', { \ 'delay': 0, 'future': 0, 'locking': 1, 'try': 0, 'catch': 2, 'finally': 0, \ 'reify': 1, 'proxy': 2, 'defrecord': 2, 'defprotocol': 1, 'definterface': 1, \ 'extend': 1, 'extend-protocol': 1, 'extend-type': 1, -"\ (letfn) (1 ((:defn)) nil) -"\ (deftype defrecord proxy) (2 nil nil (:defn)) -"\ (defprotocol definterface extend-protocol extend-type) (1 (:defn)) +"\ [letfn] [1 [[:defn]] nil] [deftype defrecord proxy] [2 nil nil [:defn]] +"\ [defprotocol definterface extend-protocol extend-type] [1 [:defn]] "\ ClojureScript \ 'this-as': 1, 'specify': 1, 'specify!': 1, "\ clojure.test @@ -159,9 +157,7 @@ function! s:InsideForm(lnum) let [line_tokens, possible_comment] = s:TokeniseLine(lnum) " In case of comments, copy "tokens" so we can undo alterations. - if possible_comment - let prev_tokens = copy(tokens) - endif + if possible_comment | let prev_tokens = copy(tokens) | endif " Reduce tokens from line "lnum" into "tokens". for tk in line_tokens @@ -180,11 +176,9 @@ function! s:InsideForm(lnum) let first_string_pos = tk endif endif - elseif in_string - " In string: ignore other tokens. + elseif in_string " In string: ignore other tokens. elseif possible_comment && tk[0] ==# ';' - " Comment: undo previous token applications on - " this line. + " Comment: undo previous token applications on this line. let tokens = copy(prev_tokens) elseif ! empty(tokens) && get(s:pairs, tk[0], '') ==# tokens[-1][0] " Matching pair: drop the last item in tokens. @@ -232,8 +226,7 @@ function! s:StringIndent(delim_pos) let is_regex = col > 1 && getline(a:delim_pos[0])[col - 2] ==# '#' return s:PosToCharCol(a:delim_pos) - (is_regex ? 2 : 1) endif - else - return -1 " Keep existing indent. + else | return -1 " Keep existing indent. endif endfunction @@ -250,21 +243,18 @@ function! s:ListIndent(delim_pos) let ln = getline(a:delim_pos[0]) let ln_content = ln[a:delim_pos[1]:] - let sym_match = -1 - " 1. Macro/rule indentation " if starts with a symbol, extract it. " - Split namespace off symbol and #'/' syntax. " - Check against pattern rules and apply indent on match. " - Look up in rules table and apply indent on match. - " else, not found, go to 2. + " else: not found, go to 2. - " TODO: handle complex indentation (e.g. letfn) and introduce - " indentation config similar to Emacs' clojure-mode and cljfmt. - " Skip if "traditional" style was chosen. + " TODO: handle complex indentation (e.g. letfn). Skip if "traditional" style was chosen? " TODO: simplify this. let syms = split(ln_content, '[[:space:],;()\[\]{}@\\"^~`]', 1) + let sym_match = -1 if ! empty(syms) let sym = syms[0] @@ -290,8 +280,7 @@ function! s:ListIndent(delim_pos) " 2. Function indentation " if first operand is on the same line? " - Indent subsequent lines to align with first operand. - " else - " - Indent 1 or 2 spaces. + " else: indent 1 or 2 spaces. let pos = s:FirstFnArgPos(a:delim_pos) if pos != [0, 0] | return s:PosToCharCol(pos) - 1 | endif From 052a49874f6aaa27a2eaca1117c4814349303fed Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Mon, 7 Oct 2024 21:12:06 +0100 Subject: [PATCH 68/88] Compress more comments on indent code --- indent/clojure.vim | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/indent/clojure.vim b/indent/clojure.vim index 2bc1a37..33942b7 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -16,10 +16,9 @@ let b:did_indent = 1 let s:save_cpo = &cpoptions set cpoptions&vim -let b:undo_indent = 'setlocal autoindent< smartindent< expandtab< softtabstop< shiftwidth< indentexpr< indentkeys< lisp<' - setlocal noautoindent nosmartindent nolisp indentkeys=!,o,O setlocal softtabstop=2 shiftwidth=2 expandtab +let b:undo_indent = 'setlocal autoindent< smartindent< expandtab< softtabstop< shiftwidth< indentexpr< indentkeys< lisp<' " Set a new configuration option with a default value. Assigns a script-local " version too, to be used as a default fallback if the global was "unlet". @@ -76,8 +75,7 @@ call s:SConf('clojure_indent_rules', { " Returns "1" if position "i_char" in "line_str" is preceded by an odd number " of backslash characters (i.e. escaped). function! s:IsEscaped(line_str, i_char) - let ln = a:line_str[: a:i_char - 1] - return ! strlen(trim(ln, '\', 2)) % 2 + return ! strlen(trim(a:line_str[: a:i_char - 1], '\', 2)) % 2 endfunction " Used during list function indentation. Returns the position of the first @@ -100,8 +98,7 @@ endfunction " Converts a cursor position into a characterwise cursor column position (to " handle multibyte characters). function! s:PosToCharCol(pos) - call cursor(a:pos) - return getcursorcharpos()[2] + call cursor(a:pos) | return getcursorcharpos()[2] endfunction " Repeatedly search for indentation significant Clojure tokens on a given line @@ -190,8 +187,7 @@ function! s:InsideForm(lnum) endfor if ! empty(tokens) && has_key(s:pairs, tokens[0][0]) && ! in_string - " Match found! - return tokens[0] + return tokens[0] " Match found! endif let lnum -= 1 @@ -199,8 +195,7 @@ function! s:InsideForm(lnum) " TODO: can this conditional be simplified? if (in_string && first_string_pos != []) || (! empty(tokens) && tokens[0][0] ==# '"') - " Must have been in a multi-line string or regular expression - " as the string was never closed. + " String was not closed, must have been in a multi-line string or regex. return first_string_pos endif @@ -209,8 +204,7 @@ endfunction " Returns "1" when the "=" operator is currently active, else "0". function! s:EqualsOperatorInEffect() - return exists('*state') - \ ? v:operator ==# '=' && state('o') ==# 'o' : 0 + return exists('*state') ? v:operator ==# '=' && state('o') ==# 'o' : 0 endfunction function! s:StringIndent(delim_pos) @@ -249,8 +243,7 @@ function! s:ListIndent(delim_pos) " - Check against pattern rules and apply indent on match. " - Look up in rules table and apply indent on match. " else: not found, go to 2. - - " TODO: handle complex indentation (e.g. letfn). Skip if "traditional" style was chosen? + " TODO: handle complex indentation (e.g. letfn). Skip if "traditional" style was chosen? " TODO: simplify this. let syms = split(ln_content, '[[:space:],;()\[\]{}@\\"^~`]', 1) @@ -259,7 +252,6 @@ function! s:ListIndent(delim_pos) if ! empty(syms) let sym = syms[0] if sym =~# '\v^%([a-zA-Z!$&*_+=|<>?-]|[^\x00-\x7F])' - " TODO: handle namespaced and non-namespaced variants. if sym =~# '\m./.' let [_namespace, name] = split(sym, '\m/') From 55543c4923df518735591d2b37fc8b36b1452b4c Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Mon, 14 Oct 2024 00:02:05 +0100 Subject: [PATCH 69/88] Use `` to show keyboard shortcuts in `README.md` --- README.md | 9 +++++---- indent/clojure.vim | 10 ++++------ 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index ac82f82..8ae4560 100644 --- a/README.md +++ b/README.md @@ -106,8 +106,8 @@ Control alignment of _new_ lines within Clojure multi-line strings and regular expressions with `clojure_indent_multiline_strings`. > [!NOTE] -> Indenting with `=` will not alter the indentation within multi-line strings, -> as this could break intentional formatting. +> Indenting with = will not alter the indentation within multi-line +> strings, as this could break intentional formatting. Pick from the following multi-line string indent styles: @@ -136,8 +136,9 @@ regions, such as rainbow parenthesis plugins.) ## Insert-mode completion Very basic insert-mode completion of special forms and public vars from -`clojure.core` is included in Clojure.vim. Invoke it with `` or -``. +`clojure.core` is included in Clojure.vim. Invoke it with +CtrlxCtrlo or +CtrlxCtrlu. ## Contribute diff --git a/indent/clojure.vim b/indent/clojure.vim index 33942b7..5c7b27f 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -36,17 +36,15 @@ endfunction call s:SConf('clojure_indent_style', 'standard') call s:SConf('clojure_indent_multiline_strings', 'standard') call s:SConf('clojure_fuzzy_indent_patterns', [ -\ '\m^with-\%(meta\|in-str\|out-str\|loading-context\)\@!', -\ '\m^def', -\ '\m^let' +\ '\m^def', '\m^let', '\m^with-\%(meta\|in-str\|out-str\|loading-context\)\@!' \ ]) " FIXME: reader conditional indentation? " TODO: explain the different numbers. The "indent_style" option can override "0" -" - -1 Not in dictionary, follow defaults. -" - 0: Align to first argument, else 2 space indentation. -" - 1+: 2 space indentation, no alignment. +" -1 : Not in dictionary, follow defaults. +" 0 : Align to first argument, else 2 space indentation. +" 1+: 2 space indentation, no alignment. " Defaults copied from: https://github.com/clojure-emacs/clojure-mode/blob/0e62583b5198f71856e4d7b80e1099789d47f2ed/clojure-mode.el#L1800-L1875 call s:SConf('clojure_indent_rules', { \ 'fn': 1, 'def': 1, 'defn': 1, 'bound-fn': 1, 'let': 1, 'binding': 1, 'defmethod': 1, From 208ff9fc53daa62b7028925f9c28ef8c38fdd9e1 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Sat, 18 Jan 2025 21:54:32 +0000 Subject: [PATCH 70/88] Improved accuracy of first function argument detection in indentation Indentation improvements: - Fixed escape character detection `s:IsEscaped`. - Improved accuracy of first function argument detection. --- .../indent-test-cases/s-expr_standard/in.clj | 43 +++++++++++++++++++ .../indent-test-cases/s-expr_standard/out.clj | 43 +++++++++++++++++++ .../s-expr_traditional/in.clj | 43 +++++++++++++++++++ .../s-expr_traditional/out.clj | 43 +++++++++++++++++++ .../indent-test-cases/s-expr_uniform/in.clj | 43 +++++++++++++++++++ .../indent-test-cases/s-expr_uniform/out.clj | 43 +++++++++++++++++++ indent/clojure.vim | 40 +++++++++++------ 7 files changed, 286 insertions(+), 12 deletions(-) diff --git a/clj/resources/indent-test-cases/s-expr_standard/in.clj b/clj/resources/indent-test-cases/s-expr_standard/in.clj index b703628..c0a3237 100644 --- a/clj/resources/indent-test-cases/s-expr_standard/in.clj +++ b/clj/resources/indent-test-cases/s-expr_standard/in.clj @@ -95,3 +95,46 @@ bar (import '(java.io File IOException) '(clojure.lang PersistentQueue)) + +((if true + -) 1 + 3) + +((if true ++ + -) + 1 +3) + +(#'if (even? 1) + 2 + 3) + +(#(foo) bar + biz) + +("foo bar" biz + baz) + +(~@foo + ~bar) + +(~@foo ~bar + biz) + +(o bar + biz) + +({foo \} bar biz} foo +biz) + +('foo bar + 'biz) + +('#{foo bar} 1 + 2) + +(foo ; bar + biz) + +'(\" \b + \c) diff --git a/clj/resources/indent-test-cases/s-expr_standard/out.clj b/clj/resources/indent-test-cases/s-expr_standard/out.clj index 03b8120..a3e38bd 100644 --- a/clj/resources/indent-test-cases/s-expr_standard/out.clj +++ b/clj/resources/indent-test-cases/s-expr_standard/out.clj @@ -95,3 +95,46 @@ (import '(java.io File IOException) '(clojure.lang PersistentQueue)) + +((if true + -) 1 + 3) + +((if true + + + -) + 1 + 3) + +(#'if (even? 1) + 2 + 3) + +(#(foo) bar + biz) + +("foo bar" biz + baz) + +(~@foo + ~bar) + +(~@foo ~bar + biz) + +(o bar + biz) + +({foo \} bar biz} foo + biz) + +('foo bar + 'biz) + +('#{foo bar} 1 + 2) + +(foo ; bar + biz) + +'(\" \b + \c) diff --git a/clj/resources/indent-test-cases/s-expr_traditional/in.clj b/clj/resources/indent-test-cases/s-expr_traditional/in.clj index b703628..c0a3237 100644 --- a/clj/resources/indent-test-cases/s-expr_traditional/in.clj +++ b/clj/resources/indent-test-cases/s-expr_traditional/in.clj @@ -95,3 +95,46 @@ bar (import '(java.io File IOException) '(clojure.lang PersistentQueue)) + +((if true + -) 1 + 3) + +((if true ++ + -) + 1 +3) + +(#'if (even? 1) + 2 + 3) + +(#(foo) bar + biz) + +("foo bar" biz + baz) + +(~@foo + ~bar) + +(~@foo ~bar + biz) + +(o bar + biz) + +({foo \} bar biz} foo +biz) + +('foo bar + 'biz) + +('#{foo bar} 1 + 2) + +(foo ; bar + biz) + +'(\" \b + \c) diff --git a/clj/resources/indent-test-cases/s-expr_traditional/out.clj b/clj/resources/indent-test-cases/s-expr_traditional/out.clj index 51d8683..98f2129 100644 --- a/clj/resources/indent-test-cases/s-expr_traditional/out.clj +++ b/clj/resources/indent-test-cases/s-expr_traditional/out.clj @@ -95,3 +95,46 @@ (import '(java.io File IOException) '(clojure.lang PersistentQueue)) + +((if true + -) 1 + 3) + +((if true + + + -) + 1 + 3) + +(#'if (even? 1) + 2 + 3) + +(#(foo) bar + biz) + +("foo bar" biz + baz) + +(~@foo + ~bar) + +(~@foo ~bar + biz) + +(o bar + biz) + +({foo \} bar biz} foo + biz) + +('foo bar + 'biz) + +('#{foo bar} 1 + 2) + +(foo ; bar + biz) + +'(\" \b + \c) diff --git a/clj/resources/indent-test-cases/s-expr_uniform/in.clj b/clj/resources/indent-test-cases/s-expr_uniform/in.clj index b703628..c0a3237 100644 --- a/clj/resources/indent-test-cases/s-expr_uniform/in.clj +++ b/clj/resources/indent-test-cases/s-expr_uniform/in.clj @@ -95,3 +95,46 @@ bar (import '(java.io File IOException) '(clojure.lang PersistentQueue)) + +((if true + -) 1 + 3) + +((if true ++ + -) + 1 +3) + +(#'if (even? 1) + 2 + 3) + +(#(foo) bar + biz) + +("foo bar" biz + baz) + +(~@foo + ~bar) + +(~@foo ~bar + biz) + +(o bar + biz) + +({foo \} bar biz} foo +biz) + +('foo bar + 'biz) + +('#{foo bar} 1 + 2) + +(foo ; bar + biz) + +'(\" \b + \c) diff --git a/clj/resources/indent-test-cases/s-expr_uniform/out.clj b/clj/resources/indent-test-cases/s-expr_uniform/out.clj index 82342ce..083ddc8 100644 --- a/clj/resources/indent-test-cases/s-expr_uniform/out.clj +++ b/clj/resources/indent-test-cases/s-expr_uniform/out.clj @@ -95,3 +95,46 @@ (import '(java.io File IOException) '(clojure.lang PersistentQueue)) + +((if true + -) 1 + 3) + +((if true + + + -) + 1 + 3) + +(#'if (even? 1) + 2 + 3) + +(#(foo) bar + biz) + +("foo bar" biz + baz) + +(~@foo + ~bar) + +(~@foo ~bar + biz) + +(o bar + biz) + +({foo \} bar biz} foo + biz) + +('foo bar + 'biz) + +('#{foo bar} 1 + 2) + +(foo ; bar + biz) + +'(\" \b + \c) diff --git a/indent/clojure.vim b/indent/clojure.vim index 5c7b27f..7209a5a 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -73,24 +73,40 @@ call s:SConf('clojure_indent_rules', { " Returns "1" if position "i_char" in "line_str" is preceded by an odd number " of backslash characters (i.e. escaped). function! s:IsEscaped(line_str, i_char) - return ! strlen(trim(a:line_str[: a:i_char - 1], '\', 2)) % 2 + let ln = a:line_str[: a:i_char - 1] + return (strlen(ln) - strlen(trim(ln, '\', 2))) % 2 +endfunction + +" Variation of "s:IsEscaped" which can be used within "search(pair)pos". +function! s:SkipIfEscaped() + let pos = getcursorcharpos() + return s:IsEscaped(getline(pos[1]), pos[2] - 1) endfunction " Used during list function indentation. Returns the position of the first " operand in the list on the first line of the form at "pos". function! s:FirstFnArgPos(pos) - " TODO: ignore comments and handle escaped characters! - let lnr = a:pos[0] - let s:in_form_current_form = a:pos - call cursor(lnr, a:pos[1] + 1) - return searchpos('\m[ ,]\+\zs', 'z', lnr, 0, function('IsSubForm')) -endfunction + let [lnr, base_idx] = a:pos + let ln = getline(lnr) + call cursor([lnr, base_idx + 1]) + + if ln[base_idx] =~# '["\\,[:space:]]' | return [0, 0] | endif + + " Find first collection delimiter or char preceeding whitespace. + let pos = searchpos('\([{\[(]\|.[[:space:],]\)', 'cWz', lnr) + if pos == [0, 0] | return pos | endif + + " If at collection delimiter, jump to end delimiter. + let ch = ln[pos[1] - 1] + if has_key(s:pairs, ch) + let pos = searchpairpos('\V' . ch, '', '\V' . get(s:pairs, ch), 'Wz', function('s:SkipIfEscaped'), lnr) + " If end not on same line: no arg. + if pos == [0, 0] | return pos | endif + endif -" Used by "s:FirstFnArgPos" function to skip over subforms as the first value -" in a list form. -function! s:IsSubForm() - let pos = searchpairpos('\m[([{"]', '', '\m[)\]}"]', 'b') - return pos != [0, 0] && pos != s:in_form_current_form + " Search forwards for first non-whitespace/comment char on line. + let pos = searchpos('[^[:space:],]', 'Wz', lnr) + return ln[pos[1] - 1] ==# ';' ? [0, 0] : pos endfunction " Converts a cursor position into a characterwise cursor column position (to From 968340e26837d36877e2795a4cfc9d6bc910fb33 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Sat, 18 Jan 2025 22:16:34 +0000 Subject: [PATCH 71/88] Slightly improve keyboard shortcut formatting in `README.md` --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8ae4560..86b1635 100644 --- a/README.md +++ b/README.md @@ -137,8 +137,8 @@ regions, such as rainbow parenthesis plugins.) Very basic insert-mode completion of special forms and public vars from `clojure.core` is included in Clojure.vim. Invoke it with -CtrlxCtrlo or -CtrlxCtrlu. +Ctrl x Ctrl o or +Ctrl x Ctrl u. ## Contribute From 082120ae3f7f3b7d6439f1155aad15fcbfe989c9 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Sat, 18 Jan 2025 22:45:34 +0000 Subject: [PATCH 72/88] Set mode in new regexprs --- indent/clojure.vim | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/indent/clojure.vim b/indent/clojure.vim index 7209a5a..ed7ea5c 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -90,10 +90,10 @@ function! s:FirstFnArgPos(pos) let ln = getline(lnr) call cursor([lnr, base_idx + 1]) - if ln[base_idx] =~# '["\\,[:space:]]' | return [0, 0] | endif + if ln[base_idx] =~# '\m["\\,[:space:]]' | return [0, 0] | endif " Find first collection delimiter or char preceeding whitespace. - let pos = searchpos('\([{\[(]\|.[[:space:],]\)', 'cWz', lnr) + let pos = searchpos('\m\([{\[(]\|.[[:space:],]\)', 'cWz', lnr) if pos == [0, 0] | return pos | endif " If at collection delimiter, jump to end delimiter. @@ -105,7 +105,7 @@ function! s:FirstFnArgPos(pos) endif " Search forwards for first non-whitespace/comment char on line. - let pos = searchpos('[^[:space:],]', 'Wz', lnr) + let pos = searchpos('\m[^[:space:],]', 'Wz', lnr) return ln[pos[1] - 1] ==# ';' ? [0, 0] : pos endfunction From 161ea5ff7be0fccf1175c755ca9d221cc5ee9421 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Sun, 19 Jan 2025 09:51:04 +0000 Subject: [PATCH 73/88] Make `ClojureIndent()` function public --- indent/clojure.vim | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/indent/clojure.vim b/indent/clojure.vim index ed7ea5c..ecd952e 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -297,8 +297,8 @@ function! s:ListIndent(delim_pos) return base_indent + (indent_style ==# 'traditional' || sym_match == 0) endfunction -" TODO: make this usable from other Clojure-like languages. -function! s:ClojureIndent() +" TODO: improve configurability for other Clojure-like languages. +function! ClojureIndent() " Calculate and return indent to use based on the matching form. let [form, pos] = s:InsideForm(v:lnum) if form ==# '^' | return 0 " At top-level, no indent. @@ -315,7 +315,7 @@ if exists('&lispoptions') setlocal lisp lispoptions=expr:1 let b:undo_indent .= ' lispoptions<' endif -setlocal indentexpr=s:ClojureIndent() +setlocal indentexpr=ClojureIndent() let &cpoptions = s:save_cpo unlet! s:save_cpo From 78ecad55e95beeaf31b3e26f1b8b3ca1036c8cc9 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Sun, 19 Jan 2025 12:08:50 +0000 Subject: [PATCH 74/88] Indent `with-` macros and functions like cljfmt default --- clj/resources/indent-test-cases/with/in.clj | 4 ++-- clj/resources/indent-test-cases/with/out.clj | 10 +++++----- indent/clojure.vim | 4 +--- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/clj/resources/indent-test-cases/with/in.clj b/clj/resources/indent-test-cases/with/in.clj index c307037..993a33d 100644 --- a/clj/resources/indent-test-cases/with/in.clj +++ b/clj/resources/indent-test-cases/with/in.clj @@ -2,10 +2,10 @@ (slurp f)) (with-meta obj - {:foo 1}) + {:foo 1}) (with-meta - obj + obj {:foo 1}) (with-out-str diff --git a/clj/resources/indent-test-cases/with/out.clj b/clj/resources/indent-test-cases/with/out.clj index 48edfef..2738a16 100644 --- a/clj/resources/indent-test-cases/with/out.clj +++ b/clj/resources/indent-test-cases/with/out.clj @@ -2,14 +2,14 @@ (slurp f)) (with-meta obj - {:foo 1}) + {:foo 1}) (with-meta - obj - {:foo 1}) + obj + {:foo 1}) (with-out-str - ()) + ()) (with-in-str - ()) + ()) diff --git a/indent/clojure.vim b/indent/clojure.vim index ecd952e..09764b6 100644 --- a/indent/clojure.vim +++ b/indent/clojure.vim @@ -35,9 +35,7 @@ endfunction call s:SConf('clojure_indent_style', 'standard') call s:SConf('clojure_indent_multiline_strings', 'standard') -call s:SConf('clojure_fuzzy_indent_patterns', [ -\ '\m^def', '\m^let', '\m^with-\%(meta\|in-str\|out-str\|loading-context\)\@!' -\ ]) +call s:SConf('clojure_fuzzy_indent_patterns', ['\m^def', '\m^let', '\m^with-']) " FIXME: reader conditional indentation? From 6ce7017f926bca7a2b00db19d3940826b8ffaf87 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Sat, 25 Jan 2025 21:35:17 +0000 Subject: [PATCH 75/88] Run indentation tests against Vim AND Neovim Closes: #39 --- .github/workflows/clojure.yml | 2 ++ clj/test/vim/indent_test.clj | 12 +++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.github/workflows/clojure.yml b/.github/workflows/clojure.yml index 1efc3a7..154d4bb 100644 --- a/.github/workflows/clojure.yml +++ b/.github/workflows/clojure.yml @@ -29,6 +29,8 @@ jobs: with: lein: 2.11.2 + - run: sudo apt install -y neovim + - name: Cache m2 uses: actions/cache@v3 env: diff --git a/clj/test/vim/indent_test.clj b/clj/test/vim/indent_test.clj index 2aa63e9..52bf00b 100644 --- a/clj/test/vim/indent_test.clj +++ b/clj/test/vim/indent_test.clj @@ -31,9 +31,7 @@ :actual (slurp actual) :actual-file actual}))) -;; TODO: do this parallisation more intelligently with agents. -(deftest test-indent - "Runs all indentation tests in parallel" +(defn- run-tests [] (let [test-case-dir (io/file (io/resource "indent-test-cases")) test-cases (get-test-cases test-case-dir)] (doseq [{:keys [test-case expected expected-file actual actual-file]} @@ -41,3 +39,11 @@ (testing test-case (is (= expected actual) (format "(not= \"%s\"\n \"%s\")" expected-file actual-file)))))) + +(deftest test-indent-vim + "Runs all indentation tests in parallel against Vim" + (run-tests)) + +(deftest test-indent-nvim + "Runs all indentation tests in parallel against Neovim" + (binding [h/*vim* "nvim"] (run-tests))) From d2f39f9e4ff818e1074bb455d0f0baee3457b0cb Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Sat, 25 Jan 2025 21:43:22 +0000 Subject: [PATCH 76/88] Install latest Vim and Neovim in GitHub Actions --- .github/workflows/clojure.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/clojure.yml b/.github/workflows/clojure.yml index 154d4bb..c516fd2 100644 --- a/.github/workflows/clojure.yml +++ b/.github/workflows/clojure.yml @@ -29,7 +29,16 @@ jobs: with: lein: 2.11.2 - - run: sudo apt install -y neovim + - name: Install Vim + uses: rhysd/action-setup-vim@v1 + with: + version: stable + + - name: Install Neovim + uses: rhysd/action-setup-vim@v1 + with: + neovim: true + version: stable - name: Cache m2 uses: actions/cache@v3 From e5f86af8e00140736b99202c3b18c42fbcebebd6 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Sun, 26 Jan 2025 01:46:19 +0000 Subject: [PATCH 77/88] New indentation test runner --- .github/workflows/indent.yml | 31 +++++++++ .github/workflows/{clojure.yml => syntax.yml} | 13 +--- .../multi-line_strings_pretty/config.edn | 4 -- .../multi-line_strings_standard/config.edn | 4 -- .../multi-line_strings_traditional/config.edn | 4 -- .../s-expr_standard/config.edn | 1 - .../s-expr_traditional/config.edn | 1 - .../s-expr_uniform/config.edn | 1 - clj/test/vim/helpers.clj | 21 ------ clj/test/vim/indent_test.clj | 49 -------------- dev/do/test-indent | 67 +++++++++++++++++++ dev/test-vimrc.vim | 3 + .../tests}/comments/in.clj | 0 .../tests}/comments/out.clj | 0 dev/tests/custom_types/SKIP | 0 .../tests}/custom_types/in.clj | 0 .../tests}/custom_types/out.clj | 0 .../tests}/def/in.clj | 0 .../tests}/def/out.clj | 0 dev/tests/letfn/SKIP | 0 .../tests}/letfn/in.clj | 0 .../tests}/letfn/out.clj | 0 .../tests}/multi-line_strings_pretty/in.clj | 0 .../tests}/multi-line_strings_pretty/out.clj | 0 dev/tests/multi-line_strings_pretty/test.vim | 5 ++ .../tests}/multi-line_strings_standard/in.clj | 0 .../multi-line_strings_standard/out.clj | 0 .../multi-line_strings_standard/test.vim | 5 ++ .../multi-line_strings_traditional/in.clj | 0 .../multi-line_strings_traditional/out.clj | 0 .../multi-line_strings_traditional/test.vim | 5 ++ .../tests}/multibyte/in.clj | 0 .../tests}/multibyte/out.clj | 0 dev/tests/reader_conditionals/SKIP | 0 .../tests}/reader_conditionals/in.clj | 0 .../tests}/reader_conditionals/out.clj | 0 .../tests}/s-expr_standard/in.clj | 0 .../tests}/s-expr_standard/out.clj | 0 .../tests}/s-expr_traditional/in.clj | 0 .../tests}/s-expr_traditional/out.clj | 0 dev/tests/s-expr_traditional/test.vim | 2 + .../tests}/s-expr_uniform/in.clj | 0 .../tests}/s-expr_uniform/out.clj | 0 dev/tests/s-expr_uniform/test.vim | 2 + .../tests}/special_forms/in.clj | 0 .../tests}/special_forms/out.clj | 0 .../tests}/with/in.clj | 0 .../tests}/with/out.clj | 0 48 files changed, 121 insertions(+), 97 deletions(-) create mode 100644 .github/workflows/indent.yml rename .github/workflows/{clojure.yml => syntax.yml} (81%) delete mode 100644 clj/resources/indent-test-cases/multi-line_strings_pretty/config.edn delete mode 100644 clj/resources/indent-test-cases/multi-line_strings_standard/config.edn delete mode 100644 clj/resources/indent-test-cases/multi-line_strings_traditional/config.edn delete mode 100644 clj/resources/indent-test-cases/s-expr_standard/config.edn delete mode 100644 clj/resources/indent-test-cases/s-expr_traditional/config.edn delete mode 100644 clj/resources/indent-test-cases/s-expr_uniform/config.edn delete mode 100644 clj/test/vim/helpers.clj delete mode 100644 clj/test/vim/indent_test.clj create mode 100755 dev/do/test-indent create mode 100644 dev/test-vimrc.vim rename {clj/resources/indent-test-cases => dev/tests}/comments/in.clj (100%) rename {clj/resources/indent-test-cases => dev/tests}/comments/out.clj (100%) create mode 100644 dev/tests/custom_types/SKIP rename {clj/resources/indent-test-cases => dev/tests}/custom_types/in.clj (100%) rename {clj/resources/indent-test-cases => dev/tests}/custom_types/out.clj (100%) rename {clj/resources/indent-test-cases => dev/tests}/def/in.clj (100%) rename {clj/resources/indent-test-cases => dev/tests}/def/out.clj (100%) create mode 100644 dev/tests/letfn/SKIP rename {clj/resources/indent-test-cases => dev/tests}/letfn/in.clj (100%) rename {clj/resources/indent-test-cases => dev/tests}/letfn/out.clj (100%) rename {clj/resources/indent-test-cases => dev/tests}/multi-line_strings_pretty/in.clj (100%) rename {clj/resources/indent-test-cases => dev/tests}/multi-line_strings_pretty/out.clj (100%) create mode 100644 dev/tests/multi-line_strings_pretty/test.vim rename {clj/resources/indent-test-cases => dev/tests}/multi-line_strings_standard/in.clj (100%) rename {clj/resources/indent-test-cases => dev/tests}/multi-line_strings_standard/out.clj (100%) create mode 100644 dev/tests/multi-line_strings_standard/test.vim rename {clj/resources/indent-test-cases => dev/tests}/multi-line_strings_traditional/in.clj (100%) rename {clj/resources/indent-test-cases => dev/tests}/multi-line_strings_traditional/out.clj (100%) create mode 100644 dev/tests/multi-line_strings_traditional/test.vim rename {clj/resources/indent-test-cases => dev/tests}/multibyte/in.clj (100%) rename {clj/resources/indent-test-cases => dev/tests}/multibyte/out.clj (100%) create mode 100644 dev/tests/reader_conditionals/SKIP rename {clj/resources/indent-test-cases => dev/tests}/reader_conditionals/in.clj (100%) rename {clj/resources/indent-test-cases => dev/tests}/reader_conditionals/out.clj (100%) rename {clj/resources/indent-test-cases => dev/tests}/s-expr_standard/in.clj (100%) rename {clj/resources/indent-test-cases => dev/tests}/s-expr_standard/out.clj (100%) rename {clj/resources/indent-test-cases => dev/tests}/s-expr_traditional/in.clj (100%) rename {clj/resources/indent-test-cases => dev/tests}/s-expr_traditional/out.clj (100%) create mode 100644 dev/tests/s-expr_traditional/test.vim rename {clj/resources/indent-test-cases => dev/tests}/s-expr_uniform/in.clj (100%) rename {clj/resources/indent-test-cases => dev/tests}/s-expr_uniform/out.clj (100%) create mode 100644 dev/tests/s-expr_uniform/test.vim rename {clj/resources/indent-test-cases => dev/tests}/special_forms/in.clj (100%) rename {clj/resources/indent-test-cases => dev/tests}/special_forms/out.clj (100%) rename {clj/resources/indent-test-cases => dev/tests}/with/in.clj (100%) rename {clj/resources/indent-test-cases => dev/tests}/with/out.clj (100%) diff --git a/.github/workflows/indent.yml b/.github/workflows/indent.yml new file mode 100644 index 0000000..3bfb129 --- /dev/null +++ b/.github/workflows/indent.yml @@ -0,0 +1,31 @@ +name: Indent +on: + push: + branches: [ master ] + pull_request: + workflow_dispatch: + +jobs: + vim-latest: + runs-on: ubuntu-latest + steps: + - name: Fetch source + uses: actions/checkout@v3 + - name: Install Vim + uses: rhysd/action-setup-vim@v1 + with: { version: stable } + - name: Run indentation tests + run: EDITOR=vim do/test-indent + working-directory: ./dev + + neovim-latest: + runs-on: ubuntu-latest + steps: + - name: Fetch source + uses: actions/checkout@v3 + - name: Install Neovim + uses: rhysd/action-setup-vim@v1 + with: { neovim: true, version: stable } + - name: Run indentation tests + run: EDITOR=nvim do/test-indent + working-directory: ./dev diff --git a/.github/workflows/clojure.yml b/.github/workflows/syntax.yml similarity index 81% rename from .github/workflows/clojure.yml rename to .github/workflows/syntax.yml index c516fd2..c3934e1 100644 --- a/.github/workflows/clojure.yml +++ b/.github/workflows/syntax.yml @@ -1,4 +1,4 @@ -name: CI +name: Syntax on: push: branches: @@ -29,17 +29,6 @@ jobs: with: lein: 2.11.2 - - name: Install Vim - uses: rhysd/action-setup-vim@v1 - with: - version: stable - - - name: Install Neovim - uses: rhysd/action-setup-vim@v1 - with: - neovim: true - version: stable - - name: Cache m2 uses: actions/cache@v3 env: diff --git a/clj/resources/indent-test-cases/multi-line_strings_pretty/config.edn b/clj/resources/indent-test-cases/multi-line_strings_pretty/config.edn deleted file mode 100644 index a756dbb..0000000 --- a/clj/resources/indent-test-cases/multi-line_strings_pretty/config.edn +++ /dev/null @@ -1,4 +0,0 @@ -{:extra-cmds ["let g:clojure_indent_multiline_strings = 'pretty'" - "normal! G" - "normal! o\u000atest \"hello\u000aworld\"" - "normal! o\u000aregex #\"asdf\u000abar\""]} diff --git a/clj/resources/indent-test-cases/multi-line_strings_standard/config.edn b/clj/resources/indent-test-cases/multi-line_strings_standard/config.edn deleted file mode 100644 index ba0c5e0..0000000 --- a/clj/resources/indent-test-cases/multi-line_strings_standard/config.edn +++ /dev/null @@ -1,4 +0,0 @@ -{:extra-cmds ["let g:clojure_indent_multiline_strings = 'standard'" - "normal! G" - "normal! o\u000atest \"hello\u000aworld\"" - "normal! o\u000aregex #\"asdf\u000abar\""]} diff --git a/clj/resources/indent-test-cases/multi-line_strings_traditional/config.edn b/clj/resources/indent-test-cases/multi-line_strings_traditional/config.edn deleted file mode 100644 index a969070..0000000 --- a/clj/resources/indent-test-cases/multi-line_strings_traditional/config.edn +++ /dev/null @@ -1,4 +0,0 @@ -{:extra-cmds ["let g:clojure_indent_multiline_strings = 'traditional'" - "normal! G" - "normal! o\u000atest \"hello\u000aworld\"" - "normal! o\u000aregex #\"asdf\u000abar\""]} diff --git a/clj/resources/indent-test-cases/s-expr_standard/config.edn b/clj/resources/indent-test-cases/s-expr_standard/config.edn deleted file mode 100644 index b0a5737..0000000 --- a/clj/resources/indent-test-cases/s-expr_standard/config.edn +++ /dev/null @@ -1 +0,0 @@ -{:extra-cmds []} diff --git a/clj/resources/indent-test-cases/s-expr_traditional/config.edn b/clj/resources/indent-test-cases/s-expr_traditional/config.edn deleted file mode 100644 index 245de34..0000000 --- a/clj/resources/indent-test-cases/s-expr_traditional/config.edn +++ /dev/null @@ -1 +0,0 @@ -{:extra-cmds ["let g:clojure_indent_style = 'traditional'"]} diff --git a/clj/resources/indent-test-cases/s-expr_uniform/config.edn b/clj/resources/indent-test-cases/s-expr_uniform/config.edn deleted file mode 100644 index cfc5d71..0000000 --- a/clj/resources/indent-test-cases/s-expr_uniform/config.edn +++ /dev/null @@ -1 +0,0 @@ -{:extra-cmds ["let g:clojure_indent_style = 'uniform'"]} diff --git a/clj/test/vim/helpers.clj b/clj/test/vim/helpers.clj deleted file mode 100644 index 65e4b87..0000000 --- a/clj/test/vim/helpers.clj +++ /dev/null @@ -1,21 +0,0 @@ -(ns vim.helpers - (:require [clojure.edn :as edn] - [clojure.java.shell :as shell]) - (:import [java.io File FileReader PushbackReader])) - -(defn read-edn-file [^File file] - (when (.exists file) - (with-open [rdr (FileReader. file)] - (edn/read (PushbackReader. rdr))))) - -(def ^:dynamic *vim* "vim") - -(defn vim! - "Run commands on a file in Vim." - [^File file cmds & {:keys [vimrc], :or {vimrc "NONE"}}] - (let [cmds (mapcat (fn [cmd] ["-c" cmd]) cmds) - args (concat ["--clean" "-N" "-u" (str vimrc)] cmds ["-c" "quitall!" "--" (str file)]) - ret (apply shell/sh *vim* args)] - (when (pos? (:exit ret)) - (throw (ex-info "Failed to run Vim command" - (assoc ret :vim *vim*, :args args)))))) diff --git a/clj/test/vim/indent_test.clj b/clj/test/vim/indent_test.clj deleted file mode 100644 index 52bf00b..0000000 --- a/clj/test/vim/indent_test.clj +++ /dev/null @@ -1,49 +0,0 @@ -(ns vim.indent-test - (:require [clojure.test :refer [deftest testing is]] - [clojure.string :as str] - [clojure.java.io :as io] - [vim.helpers :as h]) - (:import [java.io File])) - -(defn get-test-cases [^File test-case-dir] - (into [] - (comp - (filter #(.isDirectory ^File %)) - (map #(.getName ^File %))) - (.listFiles test-case-dir))) - -(defn run-test-case [test-case-dir test-case] - (testing (str "Preparation for " test-case) - (let [input (io/file test-case-dir test-case "in.clj") - expected (io/file test-case-dir test-case "out.clj") - actual (File/createTempFile test-case ".clj") - config (let [f (io/file test-case-dir test-case "config.edn")] - (or (h/read-edn-file f) {})) - cmds (concat (:extra-cmds config) - (when (:indent? config true) ["normal! gg=G"]) - ["write"])] - (io/make-parents actual) - (io/copy input actual) - (h/vim! actual cmds :vimrc (io/file "vim/test-runtime.vim")) - {:test-case test-case - :expected (slurp expected) - :expected-file expected - :actual (slurp actual) - :actual-file actual}))) - -(defn- run-tests [] - (let [test-case-dir (io/file (io/resource "indent-test-cases")) - test-cases (get-test-cases test-case-dir)] - (doseq [{:keys [test-case expected expected-file actual actual-file]} - (pmap (partial run-test-case test-case-dir) test-cases)] - (testing test-case - (is (= expected actual) - (format "(not= \"%s\"\n \"%s\")" expected-file actual-file)))))) - -(deftest test-indent-vim - "Runs all indentation tests in parallel against Vim" - (run-tests)) - -(deftest test-indent-nvim - "Runs all indentation tests in parallel against Neovim" - (binding [h/*vim* "nvim"] (run-tests))) diff --git a/dev/do/test-indent b/dev/do/test-indent new file mode 100755 index 0000000..54afdee --- /dev/null +++ b/dev/do/test-indent @@ -0,0 +1,67 @@ +#!/usr/bin/env bash + +# Run Clojure.vim indentation tests + +# TODO: colour messages? + +if [ "$EDITOR" != 'vim' ] && [ "$EDITOR" != 'nvim' ]; then + echo 'ERROR: Set the "EDITOR" environment variable to "vim" or "nvim" and run again.' + exit 1 +fi + +PASSED=() +FAILED=() +SKIPPED=() + +tmp_base_dir='/tmp/clojure.vim/indent' +mkdir -p "$tmp_base_dir" +tmp_dir="$(mktemp --directory "$tmp_base_dir/XXXXXX")" +test_case_dir='tests' + +test_pass() { PASSED+=("$1"); } +test_fail() { + FAILED+=("$1") + echo "::error file=clj/indent-test/$test_case_dir/$1/out.clj::Failed indent test case." +} +test_skip() { + SKIPPED+=("$1") + echo "::warning file=clj/indent-test/$test_case_dir/$1/out.clj::Skipped indent test case." +} + +run_test_case() { + test_case="$1" + in_file="$test_case_dir/$test_case/in.clj" + expected_file="$test_case_dir/$test_case/out.clj" + + echo "::group::$EDITOR -- $test_case" + + if [ -f "$test_case_dir/$test_case/SKIP" ]; then + test_skip "$test_case" + else + actual_file="$tmp_dir/$test_case.clj" + cp "$in_file" "$actual_file" + + # Override the default test commands with a `test.vim` file. + test_script="$test_case_dir/$test_case/test.vim" + if [ -f "$test_script" ]; then + test_cmd=('-S' "$test_script") + else + test_cmd=('+normal! gg=G') + fi + + "$EDITOR" --clean -NXnu test-vimrc.vim "${test_cmd[@]}" '+xall!' -- "$actual_file" + + diff --color=always -u "$expected_file" "$actual_file" + + [ $? -eq 0 ] && test_pass "$test_case" || test_fail "$test_case" + fi + + echo '::endgroup::' +} + +for tcase in $test_case_dir/*/; do + run_test_case "$(basename "$tcase")" +done + +echo "passed: ${#PASSED[@]}, failed: ${#FAILED[@]}, skipped: ${#SKIPPED[@]}" +exit ${#FAILED[@]} diff --git a/dev/test-vimrc.vim b/dev/test-vimrc.vim new file mode 100644 index 0000000..770bf0d --- /dev/null +++ b/dev/test-vimrc.vim @@ -0,0 +1,3 @@ +let &rtp = getcwd() . '/..,' . &rtp +filetype plugin indent on +syntax enable diff --git a/clj/resources/indent-test-cases/comments/in.clj b/dev/tests/comments/in.clj similarity index 100% rename from clj/resources/indent-test-cases/comments/in.clj rename to dev/tests/comments/in.clj diff --git a/clj/resources/indent-test-cases/comments/out.clj b/dev/tests/comments/out.clj similarity index 100% rename from clj/resources/indent-test-cases/comments/out.clj rename to dev/tests/comments/out.clj diff --git a/dev/tests/custom_types/SKIP b/dev/tests/custom_types/SKIP new file mode 100644 index 0000000..e69de29 diff --git a/clj/resources/indent-test-cases/custom_types/in.clj b/dev/tests/custom_types/in.clj similarity index 100% rename from clj/resources/indent-test-cases/custom_types/in.clj rename to dev/tests/custom_types/in.clj diff --git a/clj/resources/indent-test-cases/custom_types/out.clj b/dev/tests/custom_types/out.clj similarity index 100% rename from clj/resources/indent-test-cases/custom_types/out.clj rename to dev/tests/custom_types/out.clj diff --git a/clj/resources/indent-test-cases/def/in.clj b/dev/tests/def/in.clj similarity index 100% rename from clj/resources/indent-test-cases/def/in.clj rename to dev/tests/def/in.clj diff --git a/clj/resources/indent-test-cases/def/out.clj b/dev/tests/def/out.clj similarity index 100% rename from clj/resources/indent-test-cases/def/out.clj rename to dev/tests/def/out.clj diff --git a/dev/tests/letfn/SKIP b/dev/tests/letfn/SKIP new file mode 100644 index 0000000..e69de29 diff --git a/clj/resources/indent-test-cases/letfn/in.clj b/dev/tests/letfn/in.clj similarity index 100% rename from clj/resources/indent-test-cases/letfn/in.clj rename to dev/tests/letfn/in.clj diff --git a/clj/resources/indent-test-cases/letfn/out.clj b/dev/tests/letfn/out.clj similarity index 100% rename from clj/resources/indent-test-cases/letfn/out.clj rename to dev/tests/letfn/out.clj diff --git a/clj/resources/indent-test-cases/multi-line_strings_pretty/in.clj b/dev/tests/multi-line_strings_pretty/in.clj similarity index 100% rename from clj/resources/indent-test-cases/multi-line_strings_pretty/in.clj rename to dev/tests/multi-line_strings_pretty/in.clj diff --git a/clj/resources/indent-test-cases/multi-line_strings_pretty/out.clj b/dev/tests/multi-line_strings_pretty/out.clj similarity index 100% rename from clj/resources/indent-test-cases/multi-line_strings_pretty/out.clj rename to dev/tests/multi-line_strings_pretty/out.clj diff --git a/dev/tests/multi-line_strings_pretty/test.vim b/dev/tests/multi-line_strings_pretty/test.vim new file mode 100644 index 0000000..ebff716 --- /dev/null +++ b/dev/tests/multi-line_strings_pretty/test.vim @@ -0,0 +1,5 @@ +let g:clojure_indent_multiline_strings = 'pretty' +normal! gg=G +normal! G +exec "normal! o\test \"hello\world\"" +exec "normal! o\regex #\"asdf\bar\"" diff --git a/clj/resources/indent-test-cases/multi-line_strings_standard/in.clj b/dev/tests/multi-line_strings_standard/in.clj similarity index 100% rename from clj/resources/indent-test-cases/multi-line_strings_standard/in.clj rename to dev/tests/multi-line_strings_standard/in.clj diff --git a/clj/resources/indent-test-cases/multi-line_strings_standard/out.clj b/dev/tests/multi-line_strings_standard/out.clj similarity index 100% rename from clj/resources/indent-test-cases/multi-line_strings_standard/out.clj rename to dev/tests/multi-line_strings_standard/out.clj diff --git a/dev/tests/multi-line_strings_standard/test.vim b/dev/tests/multi-line_strings_standard/test.vim new file mode 100644 index 0000000..9c40753 --- /dev/null +++ b/dev/tests/multi-line_strings_standard/test.vim @@ -0,0 +1,5 @@ +let g:clojure_indent_multiline_strings = 'standard' +normal! gg=G +normal! G +exec "normal! o\test \"hello\world\"" +exec "normal! o\regex #\"asdf\bar\"" diff --git a/clj/resources/indent-test-cases/multi-line_strings_traditional/in.clj b/dev/tests/multi-line_strings_traditional/in.clj similarity index 100% rename from clj/resources/indent-test-cases/multi-line_strings_traditional/in.clj rename to dev/tests/multi-line_strings_traditional/in.clj diff --git a/clj/resources/indent-test-cases/multi-line_strings_traditional/out.clj b/dev/tests/multi-line_strings_traditional/out.clj similarity index 100% rename from clj/resources/indent-test-cases/multi-line_strings_traditional/out.clj rename to dev/tests/multi-line_strings_traditional/out.clj diff --git a/dev/tests/multi-line_strings_traditional/test.vim b/dev/tests/multi-line_strings_traditional/test.vim new file mode 100644 index 0000000..25c7a13 --- /dev/null +++ b/dev/tests/multi-line_strings_traditional/test.vim @@ -0,0 +1,5 @@ +let g:clojure_indent_multiline_strings = 'traditional' +normal! gg=G +normal! G +exec "normal! o\test \"hello\world\"" +exec "normal! o\regex #\"asdf\bar\"" diff --git a/clj/resources/indent-test-cases/multibyte/in.clj b/dev/tests/multibyte/in.clj similarity index 100% rename from clj/resources/indent-test-cases/multibyte/in.clj rename to dev/tests/multibyte/in.clj diff --git a/clj/resources/indent-test-cases/multibyte/out.clj b/dev/tests/multibyte/out.clj similarity index 100% rename from clj/resources/indent-test-cases/multibyte/out.clj rename to dev/tests/multibyte/out.clj diff --git a/dev/tests/reader_conditionals/SKIP b/dev/tests/reader_conditionals/SKIP new file mode 100644 index 0000000..e69de29 diff --git a/clj/resources/indent-test-cases/reader_conditionals/in.clj b/dev/tests/reader_conditionals/in.clj similarity index 100% rename from clj/resources/indent-test-cases/reader_conditionals/in.clj rename to dev/tests/reader_conditionals/in.clj diff --git a/clj/resources/indent-test-cases/reader_conditionals/out.clj b/dev/tests/reader_conditionals/out.clj similarity index 100% rename from clj/resources/indent-test-cases/reader_conditionals/out.clj rename to dev/tests/reader_conditionals/out.clj diff --git a/clj/resources/indent-test-cases/s-expr_standard/in.clj b/dev/tests/s-expr_standard/in.clj similarity index 100% rename from clj/resources/indent-test-cases/s-expr_standard/in.clj rename to dev/tests/s-expr_standard/in.clj diff --git a/clj/resources/indent-test-cases/s-expr_standard/out.clj b/dev/tests/s-expr_standard/out.clj similarity index 100% rename from clj/resources/indent-test-cases/s-expr_standard/out.clj rename to dev/tests/s-expr_standard/out.clj diff --git a/clj/resources/indent-test-cases/s-expr_traditional/in.clj b/dev/tests/s-expr_traditional/in.clj similarity index 100% rename from clj/resources/indent-test-cases/s-expr_traditional/in.clj rename to dev/tests/s-expr_traditional/in.clj diff --git a/clj/resources/indent-test-cases/s-expr_traditional/out.clj b/dev/tests/s-expr_traditional/out.clj similarity index 100% rename from clj/resources/indent-test-cases/s-expr_traditional/out.clj rename to dev/tests/s-expr_traditional/out.clj diff --git a/dev/tests/s-expr_traditional/test.vim b/dev/tests/s-expr_traditional/test.vim new file mode 100644 index 0000000..b9a80a9 --- /dev/null +++ b/dev/tests/s-expr_traditional/test.vim @@ -0,0 +1,2 @@ +let g:clojure_indent_style = 'traditional' +normal! gg=G diff --git a/clj/resources/indent-test-cases/s-expr_uniform/in.clj b/dev/tests/s-expr_uniform/in.clj similarity index 100% rename from clj/resources/indent-test-cases/s-expr_uniform/in.clj rename to dev/tests/s-expr_uniform/in.clj diff --git a/clj/resources/indent-test-cases/s-expr_uniform/out.clj b/dev/tests/s-expr_uniform/out.clj similarity index 100% rename from clj/resources/indent-test-cases/s-expr_uniform/out.clj rename to dev/tests/s-expr_uniform/out.clj diff --git a/dev/tests/s-expr_uniform/test.vim b/dev/tests/s-expr_uniform/test.vim new file mode 100644 index 0000000..bc715f3 --- /dev/null +++ b/dev/tests/s-expr_uniform/test.vim @@ -0,0 +1,2 @@ +let g:clojure_indent_style = 'uniform' +normal! gg=G diff --git a/clj/resources/indent-test-cases/special_forms/in.clj b/dev/tests/special_forms/in.clj similarity index 100% rename from clj/resources/indent-test-cases/special_forms/in.clj rename to dev/tests/special_forms/in.clj diff --git a/clj/resources/indent-test-cases/special_forms/out.clj b/dev/tests/special_forms/out.clj similarity index 100% rename from clj/resources/indent-test-cases/special_forms/out.clj rename to dev/tests/special_forms/out.clj diff --git a/clj/resources/indent-test-cases/with/in.clj b/dev/tests/with/in.clj similarity index 100% rename from clj/resources/indent-test-cases/with/in.clj rename to dev/tests/with/in.clj diff --git a/clj/resources/indent-test-cases/with/out.clj b/dev/tests/with/out.clj similarity index 100% rename from clj/resources/indent-test-cases/with/out.clj rename to dev/tests/with/out.clj From 7659541ea4f0c079270c8c2503790e753cbb4483 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Sun, 26 Jan 2025 02:03:17 +0000 Subject: [PATCH 78/88] Improve new test runner output and detect script errors --- dev/do/test-indent | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/dev/do/test-indent b/dev/do/test-indent index 54afdee..21e2bde 100755 --- a/dev/do/test-indent +++ b/dev/do/test-indent @@ -18,14 +18,16 @@ mkdir -p "$tmp_base_dir" tmp_dir="$(mktemp --directory "$tmp_base_dir/XXXXXX")" test_case_dir='tests' -test_pass() { PASSED+=("$1"); } +test_pass() { PASSED+=("$1"); echo '::endgroup::'; } test_fail() { FAILED+=("$1") - echo "::error file=clj/indent-test/$test_case_dir/$1/out.clj::Failed indent test case." + echo '::endgroup::' + echo "::error file=dev/$test_case_dir/$1/out.clj::Failed indent test case." } test_skip() { SKIPPED+=("$1") - echo "::warning file=clj/indent-test/$test_case_dir/$1/out.clj::Skipped indent test case." + echo '::endgroup::' + echo "::warning file=dev/$test_case_dir/$1/out.clj::Skipped indent test case." } run_test_case() { @@ -33,7 +35,7 @@ run_test_case() { in_file="$test_case_dir/$test_case/in.clj" expected_file="$test_case_dir/$test_case/out.clj" - echo "::group::$EDITOR -- $test_case" + echo "::group::$EDITOR: $test_case" if [ -f "$test_case_dir/$test_case/SKIP" ]; then test_skip "$test_case" @@ -55,8 +57,6 @@ run_test_case() { [ $? -eq 0 ] && test_pass "$test_case" || test_fail "$test_case" fi - - echo '::endgroup::' } for tcase in $test_case_dir/*/; do @@ -64,4 +64,8 @@ for tcase in $test_case_dir/*/; do done echo "passed: ${#PASSED[@]}, failed: ${#FAILED[@]}, skipped: ${#SKIPPED[@]}" -exit ${#FAILED[@]} + +# If none passed, or some failed, exit with error. +if [ ${#PASSED[@]} -eq 0 ] || [ ${#FAILED[@]} -gt 0 ]; then + exit 1 +fi From 1ea333ada370711ac804d8863bcd849910edd4ff Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Sun, 26 Jan 2025 02:10:26 +0000 Subject: [PATCH 79/88] Speed up indentation tests on Vim --- dev/do/test-indent | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/dev/do/test-indent b/dev/do/test-indent index 21e2bde..312624d 100755 --- a/dev/do/test-indent +++ b/dev/do/test-indent @@ -9,6 +9,12 @@ if [ "$EDITOR" != 'vim' ] && [ "$EDITOR" != 'nvim' ]; then exit 1 fi +if [ "$EDITOR" = 'vim' ]; then + editor_opts=('--not-a-term') +else + editor_opts=() +fi + PASSED=() FAILED=() SKIPPED=() @@ -51,7 +57,7 @@ run_test_case() { test_cmd=('+normal! gg=G') fi - "$EDITOR" --clean -NXnu test-vimrc.vim "${test_cmd[@]}" '+xall!' -- "$actual_file" + "$EDITOR" "${editor_opts[@]}" --clean -NXnu test-vimrc.vim "${test_cmd[@]}" '+xall!' -- "$actual_file" diff --color=always -u "$expected_file" "$actual_file" From aae9ae86685ef29c092b5b99c827a55b5827daaf Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Sun, 26 Jan 2025 02:41:06 +0000 Subject: [PATCH 80/88] Move `indenttime` script to `dev` folder as `time-indent` --- dev/do/test-indent | 2 ++ clj/bin/indenttime => dev/do/time-indent | 11 ++++------- 2 files changed, 6 insertions(+), 7 deletions(-) rename clj/bin/indenttime => dev/do/time-indent (65%) diff --git a/dev/do/test-indent b/dev/do/test-indent index 312624d..07902e9 100755 --- a/dev/do/test-indent +++ b/dev/do/test-indent @@ -4,6 +4,8 @@ # TODO: colour messages? +pushd "$(dirname "$0")/.." + if [ "$EDITOR" != 'vim' ] && [ "$EDITOR" != 'nvim' ]; then echo 'ERROR: Set the "EDITOR" environment variable to "vim" or "nvim" and run again.' exit 1 diff --git a/clj/bin/indenttime b/dev/do/time-indent similarity index 65% rename from clj/bin/indenttime rename to dev/do/time-indent index 615c473..f6562b1 100755 --- a/clj/bin/indenttime +++ b/dev/do/time-indent @@ -9,8 +9,8 @@ PREFIX='report_indent' while getopts :p: opt; do case "$opt" in - p) PREFIX="$OPTARG";; - h) abort_with_help;; + p) PREFIX="$OPTARG";; + h) abort_with_help;; esac done shift $((OPTIND-1)) @@ -21,12 +21,9 @@ VIMRC=" set runtimepath^=$(dirname "$0")/../.. filetype plugin indent on syntax on +let g:clojure_maxlines = 0 profile start $(echo "${PREFIX}-$(date +%s.%N).log") -profile! file $(dirname "$0")/../../syntax/clojure.vim profile! file $(dirname "$0")/../../indent/clojure.vim " -exec vim -N -u <(echo "$VIMRC") \ - -c 'call feedkeys("gg=G")' \ - -c 'call feedkeys(":silent quitall!\")' \ - "$1" +exec vim --clean -NXnu <(echo "$VIMRC") '+normal! gg=G' '+quitall!' "$1" From 18832150ea8663dee2a6fad62de6f75eaceca305 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Sun, 26 Jan 2025 02:49:16 +0000 Subject: [PATCH 81/88] Update GitHub Actions --- .github/workflows/indent.yml | 13 +++++-------- .github/workflows/syntax.yml | 8 +++----- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/.github/workflows/indent.yml b/.github/workflows/indent.yml index 3bfb129..d75dde4 100644 --- a/.github/workflows/indent.yml +++ b/.github/workflows/indent.yml @@ -1,7 +1,6 @@ name: Indent on: - push: - branches: [ master ] + push: { branches: [ master ] } pull_request: workflow_dispatch: @@ -10,22 +9,20 @@ jobs: runs-on: ubuntu-latest steps: - name: Fetch source - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Vim uses: rhysd/action-setup-vim@v1 with: { version: stable } - name: Run indentation tests - run: EDITOR=vim do/test-indent - working-directory: ./dev + run: EDITOR=vim dev/do/test-indent neovim-latest: runs-on: ubuntu-latest steps: - name: Fetch source - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Neovim uses: rhysd/action-setup-vim@v1 with: { neovim: true, version: stable } - name: Run indentation tests - run: EDITOR=nvim do/test-indent - working-directory: ./dev + run: EDITOR=nvim dev/do/test-indent diff --git a/.github/workflows/syntax.yml b/.github/workflows/syntax.yml index c3934e1..4862da4 100644 --- a/.github/workflows/syntax.yml +++ b/.github/workflows/syntax.yml @@ -1,8 +1,6 @@ name: Syntax on: - push: - branches: - - master + push: { branches: [ master ] } pull_request: workflow_dispatch: @@ -15,7 +13,7 @@ jobs: with: clj-kondo: latest - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Lint working-directory: ./clj @@ -24,7 +22,7 @@ jobs: test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: DeLaGuardo/setup-clojure@7.0 with: lein: 2.11.2 From 5f995a14b3966ebed7d927351a2d9cf19b68ecc4 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Sun, 26 Jan 2025 10:40:04 +0000 Subject: [PATCH 82/88] Kaocha not needed anymore, so removing dependency --- clj/project.clj | 4 +--- clj/tests.edn | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) delete mode 100644 clj/tests.edn diff --git a/clj/project.clj b/clj/project.clj index f84f080..3eda633 100644 --- a/clj/project.clj +++ b/clj/project.clj @@ -8,6 +8,4 @@ [org.clojure/data.csv "1.1.0"] [frak "0.1.9" :exclusions [org.clojure/clojurescript]]] :profiles {:test {:managed-dependencies [[org.clojure/tools.cli "1.0.219"] - [org.clojure/tools.reader "1.3.6"]] - :dependencies [[lambdaisland/kaocha "1.85.1342"]]}} - :aliases {"test" ["with-profile" "+test" "run" "-m" "kaocha.runner"]}) + [org.clojure/tools.reader "1.3.6"]]}}) diff --git a/clj/tests.edn b/clj/tests.edn deleted file mode 100644 index 9d8d845..0000000 --- a/clj/tests.edn +++ /dev/null @@ -1 +0,0 @@ -#kaocha/v1 {} From f3358f39adafe81b347ee79e96d8253a4cec2eff Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Sun, 26 Jan 2025 11:53:40 +0000 Subject: [PATCH 83/88] Bump copyright year --- README.md | 2 +- doc/clojure.txt | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 86b1635..53503ad 100644 --- a/README.md +++ b/README.md @@ -161,7 +161,7 @@ Thanks to [Tim Pope](https://github.com/tpope/) for advice in `#vim` on IRC. Clojure.vim is licensed under the [Vim License](http://vimdoc.sourceforge.net/htmldoc/uganda.html#license) for distribution with Vim. -- Copyright © 2020–2024, The clojure-vim contributors. +- Copyright © 2020–2025, The clojure-vim contributors. - Copyright © 2013–2018, Sung Pae. - Copyright © 2008–2012, Meikel Brandmeyer. - Copyright © 2007–2008, Toralf Wittner. diff --git a/doc/clojure.txt b/doc/clojure.txt index eaae572..583a2bb 100644 --- a/doc/clojure.txt +++ b/doc/clojure.txt @@ -172,10 +172,10 @@ indent/clojure.vim Distributed under the Vim license. See |license|. - Copyright 2007-2008 (c) Toralf Wittner - Copyright 2008-2012 (c) Meikel Brandmeyer - Copyright 2013-2018 (c) Sung Pae - Copyright 2020–2024 (c) The clojure-vim contributors + Copyright 2007–2008 (c) Toralf Wittner + Copyright 2008–2012 (c) Meikel Brandmeyer + Copyright 2013–2018 (c) Sung Pae + Copyright 2020–2025 (c) The clojure-vim contributors Last Change: %%RELEASE_DATE%% From 13ca6feb58670934ea0aa40a39c454a4d06c35b1 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Sun, 26 Jan 2025 11:55:25 +0000 Subject: [PATCH 84/88] Remove unneeded config from `clj/profile.clj` --- clj/project.clj | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/clj/project.clj b/clj/project.clj index 3eda633..c0172fe 100644 --- a/clj/project.clj +++ b/clj/project.clj @@ -6,6 +6,4 @@ :comments ":help license"} :dependencies [[org.clojure/clojure "1.12.0"] [org.clojure/data.csv "1.1.0"] - [frak "0.1.9" :exclusions [org.clojure/clojurescript]]] - :profiles {:test {:managed-dependencies [[org.clojure/tools.cli "1.0.219"] - [org.clojure/tools.reader "1.3.6"]]}}) + [frak "0.1.9" :exclusions [org.clojure/clojurescript]]]) From 5b277db747743cf45ce88d503913c857666506a9 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Sun, 26 Jan 2025 12:34:04 +0000 Subject: [PATCH 85/88] Run indentation tests in Vim's silent Ex-improved mode Avoids screen flickering during test execution. --- dev/do/test-indent | 11 ++++------- dev/do/time-indent | 2 +- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/dev/do/test-indent b/dev/do/test-indent index 07902e9..f9be8a9 100755 --- a/dev/do/test-indent +++ b/dev/do/test-indent @@ -1,6 +1,6 @@ #!/usr/bin/env bash -# Run Clojure.vim indentation tests +# Run Clojure.vim indentation tests. # TODO: colour messages? @@ -11,11 +11,8 @@ if [ "$EDITOR" != 'vim' ] && [ "$EDITOR" != 'nvim' ]; then exit 1 fi -if [ "$EDITOR" = 'vim' ]; then - editor_opts=('--not-a-term') -else - editor_opts=() -fi +extra_opts=() +[ "$EDITOR" = 'vim' ] && extra_opts+=('--not-a-term') PASSED=() FAILED=() @@ -59,7 +56,7 @@ run_test_case() { test_cmd=('+normal! gg=G') fi - "$EDITOR" "${editor_opts[@]}" --clean -NXnu test-vimrc.vim "${test_cmd[@]}" '+xall!' -- "$actual_file" + "$EDITOR" "${extra_opts[@]}" --clean -EsNXnu test-vimrc.vim "${test_cmd[@]}" '+xall!' -- "$actual_file" diff --color=always -u "$expected_file" "$actual_file" diff --git a/dev/do/time-indent b/dev/do/time-indent index f6562b1..a7b95d7 100755 --- a/dev/do/time-indent +++ b/dev/do/time-indent @@ -26,4 +26,4 @@ profile start $(echo "${PREFIX}-$(date +%s.%N).log") profile! file $(dirname "$0")/../../indent/clojure.vim " -exec vim --clean -NXnu <(echo "$VIMRC") '+normal! gg=G' '+quitall!' "$1" +exec vim --clean -ENXnu <(echo "$VIMRC") '+normal! gg=G' '+quitall!' "$1" From d8dc8a77d1b7147866266c463d5f8c0b23bee494 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Tue, 4 Feb 2025 22:54:35 +0000 Subject: [PATCH 86/88] Reduce indentation test runner noise and add colour output --- dev/do/test-indent | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/dev/do/test-indent b/dev/do/test-indent index f9be8a9..9e2a853 100755 --- a/dev/do/test-indent +++ b/dev/do/test-indent @@ -2,13 +2,26 @@ # Run Clojure.vim indentation tests. -# TODO: colour messages? +# TODO: option to enable/disable (Lua / Vim9script) versions. + +C_GREEN='\033[1;32m' +C_RED='\033[1;31m' +C_YELLOW='\033[1;33m' +C_BLUE='\033[1;34m' +C_RESET='\033[0m' + +log() { printf "$*$C_RESET\n"; } +logc() { log "$1$2"; } +succ() { logc "$C_GREEN" "$*"; } +warn() { logc "$C_YELLOW" "$*"; } +err() { logc "$C_RED" "$*"; } +info() { logc "$C_BLUE" "$*"; } +abort() { err "ABORT: $*"; exit 1; } pushd "$(dirname "$0")/.." if [ "$EDITOR" != 'vim' ] && [ "$EDITOR" != 'nvim' ]; then - echo 'ERROR: Set the "EDITOR" environment variable to "vim" or "nvim" and run again.' - exit 1 + abort 'Set the "EDITOR" environment variable to "vim" or "nvim" and run again.' fi extra_opts=() @@ -23,16 +36,14 @@ mkdir -p "$tmp_base_dir" tmp_dir="$(mktemp --directory "$tmp_base_dir/XXXXXX")" test_case_dir='tests' -test_pass() { PASSED+=("$1"); echo '::endgroup::'; } +test_pass() { PASSED+=("$1"); } test_fail() { FAILED+=("$1") - echo '::endgroup::' - echo "::error file=dev/$test_case_dir/$1/out.clj::Failed indent test case." + err "::error file=dev/$test_case_dir/$1/out.clj::Failed indent test case." } test_skip() { SKIPPED+=("$1") - echo '::endgroup::' - echo "::warning file=dev/$test_case_dir/$1/out.clj::Skipped indent test case." + warn "::warning file=dev/$test_case_dir/$1/out.clj::Skipped indent test case." } run_test_case() { @@ -40,7 +51,7 @@ run_test_case() { in_file="$test_case_dir/$test_case/in.clj" expected_file="$test_case_dir/$test_case/out.clj" - echo "::group::$EDITOR: $test_case" + info "> $EDITOR: $test_case" if [ -f "$test_case_dir/$test_case/SKIP" ]; then test_skip "$test_case" @@ -56,7 +67,8 @@ run_test_case() { test_cmd=('+normal! gg=G') fi - "$EDITOR" "${extra_opts[@]}" --clean -EsNXnu test-vimrc.vim "${test_cmd[@]}" '+xall!' -- "$actual_file" + "$EDITOR" "${extra_opts[@]}" --clean -EsNXnu test-vimrc.vim \ + "${test_cmd[@]}" '+xall!' -- "$actual_file" diff --color=always -u "$expected_file" "$actual_file" @@ -68,9 +80,10 @@ for tcase in $test_case_dir/*/; do run_test_case "$(basename "$tcase")" done -echo "passed: ${#PASSED[@]}, failed: ${#FAILED[@]}, skipped: ${#SKIPPED[@]}" +printf "passed: $C_GREEN%s$C_RESET, failed: $C_RED%s$C_RESET, skipped: $C_YELLOW%s$C_RESET\n" \ + "${#PASSED[@]}" "${#FAILED[@]}" "${#SKIPPED[@]}" # If none passed, or some failed, exit with error. if [ ${#PASSED[@]} -eq 0 ] || [ ${#FAILED[@]} -gt 0 ]; then - exit 1 + abort 'Failed test cases.' fi From e50122eb36cd2fd864ba4fd392744716c8836f3b Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Tue, 4 Feb 2025 23:14:54 +0000 Subject: [PATCH 87/88] GitHub Actions "annotations" feature doesn't work with colour output --- dev/do/test-indent | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/dev/do/test-indent b/dev/do/test-indent index 9e2a853..8869b15 100755 --- a/dev/do/test-indent +++ b/dev/do/test-indent @@ -10,13 +10,14 @@ C_YELLOW='\033[1;33m' C_BLUE='\033[1;34m' C_RESET='\033[0m' -log() { printf "$*$C_RESET\n"; } -logc() { log "$1$2"; } -succ() { logc "$C_GREEN" "$*"; } -warn() { logc "$C_YELLOW" "$*"; } -err() { logc "$C_RED" "$*"; } -info() { logc "$C_BLUE" "$*"; } -abort() { err "ABORT: $*"; exit 1; } +log() { printf "$*$C_RESET\n"; } +logc() { log "$1$2"; } +succ() { logc "$C_GREEN" "$*"; } +warn() { logc "$C_YELLOW" "$*"; } +err() { logc "$C_RED" "$*"; } +info() { logc "$C_BLUE" "$*"; } +abort() { err "ABORT: $*"; exit 1; } +gh_do() { [ -n "$GITHUB_ACTIONS" ] && return 0 || return 1; } pushd "$(dirname "$0")/.." @@ -39,11 +40,13 @@ test_case_dir='tests' test_pass() { PASSED+=("$1"); } test_fail() { FAILED+=("$1") - err "::error file=dev/$test_case_dir/$1/out.clj::Failed indent test case." + gh_do && echo "::error file=dev/$test_case_dir/$1/out.clj::Failed indent test case." + err "Failed \"$1\"" } test_skip() { SKIPPED+=("$1") - warn "::warning file=dev/$test_case_dir/$1/out.clj::Skipped indent test case." + gh_do && echo "::warning file=dev/$test_case_dir/$1/out.clj::Skipped indent test case." + warn "Skipped \"$1\"" } run_test_case() { From c4b8d48c5f7514a825073926474f31dc4a85ea30 Mon Sep 17 00:00:00 2001 From: Alex Vear Date: Tue, 4 Feb 2025 23:23:08 +0000 Subject: [PATCH 88/88] Simplify indentation test runner output on GitHub Actions --- dev/do/test-indent | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/dev/do/test-indent b/dev/do/test-indent index 8869b15..1d93d1a 100755 --- a/dev/do/test-indent +++ b/dev/do/test-indent @@ -40,13 +40,15 @@ test_case_dir='tests' test_pass() { PASSED+=("$1"); } test_fail() { FAILED+=("$1") - gh_do && echo "::error file=dev/$test_case_dir/$1/out.clj::Failed indent test case." - err "Failed \"$1\"" + gh_do \ + && echo "::error file=dev/$test_case_dir/$1/out.clj::Failed indent test case." \ + || err "Failed \"$1\"" } test_skip() { SKIPPED+=("$1") - gh_do && echo "::warning file=dev/$test_case_dir/$1/out.clj::Skipped indent test case." - warn "Skipped \"$1\"" + gh_do \ + && echo "::warning file=dev/$test_case_dir/$1/out.clj::Skipped indent test case." \ + || warn "Skipped \"$1\"" } run_test_case() {