Skip to content
Open
Changes from 1 commit
Commits
Show all changes
90 commits
Select commit Hold shift + click to select a range
7d6bdd7
Begin rewrite of indentation code
axvr Apr 21, 2023
5706ea3
Double (or better) indent performance!
axvr Apr 21, 2023
5f083ff
A bit of code clean up and minor optimisations
axvr Apr 21, 2023
f4e0689
Check for maps before vectors for further performance enhancement
axvr Apr 21, 2023
2627e1c
Add fallback for when Vim was not compiled with `searchpairpos`
axvr Apr 21, 2023
4a3cab7
Fix string detection when entering a newline while in insert mode
axvr Apr 26, 2023
d1ec02c
Minor performance improvement when file contains multi-line strings
axvr Apr 26, 2023
c3047be
Mimic multi-line string/regex indent behaviour of VS Code and Emacs
axvr Apr 27, 2023
eb0463e
When `clojure_align_multiline_strings` is `-1`, no indentation
axvr Apr 27, 2023
d69008f
Update indentation options sections of the README and vim help file
axvr Apr 27, 2023
d73e408
Improve some of the comments in the indentation code
axvr Apr 27, 2023
d177b3b
Move string indent picker to a separate function
axvr Apr 27, 2023
51e6a43
Fix false positive multi-line string detection
axvr Apr 28, 2023
e573da7
EDN files are unlikely to contain many lists, so check them last
axvr Apr 28, 2023
242dc9d
Yet more string detection fixes and slight code clean up
axvr Apr 28, 2023
342f35f
Initial work on list indentation
axvr Apr 28, 2023
51f5dcb
More indentation code clean up and readability improvements
axvr Apr 28, 2023
cc9cda7
Start of a small Clojure reader for indentation w/o syntax highlighting
axvr Apr 30, 2023
ee2acc2
Completed the mini Clojure reader as the core of the indentation system
axvr Apr 30, 2023
bdbc281
Reader indent algorithm perf has surpassed the prev syntax highlight one
axvr May 1, 2023
8ee73c5
Small refactor to improve code clarity in indentation code
axvr May 1, 2023
a4beb52
Fix indentation bug during comment detection
axvr May 29, 2023
3d8197e
Fix accidental detection of backslashes as tokens
axvr Jun 10, 2023
101c9a4
Update indent comments and move `s:Conf` function to the top
axvr Jun 26, 2023
35e0234
Add basic function parameter alignment indentation
axvr Jun 26, 2023
787e1e8
Replace `clojure_align_subforms` with `clojure_indent_style`
axvr Jun 26, 2023
bfce724
Update and add a bunch of indentation tests
axvr Jun 26, 2023
a2ffcba
Fix indentation for multibyte characters
axvr Jun 26, 2023
09720fe
Switch test runner to Kaocha for nicer output
axvr Jun 26, 2023
2728db2
Only run GitHub Actions workflow once for PRs
axvr Jun 26, 2023
d51154a
Neovim does not have a `state()` function
axvr Aug 12, 2023
f3d889f
Initial work on building up macro indent rules
axvr Aug 13, 2023
bf4cf3d
Fix false-positive comment detection within strings
axvr Aug 13, 2023
caef0c5
Improve accuracy of multiline string detection for indentation
axvr Aug 13, 2023
3dbc6dd
Fix indentation when file contains multibyte characters
axvr Aug 13, 2023
5550111
Add more indent test cases
axvr Aug 13, 2023
b8aef1b
Neovim has added the `state()` function; use it if available
axvr Dec 23, 2023
b2c392d
Fix application of inline comments during indentation
axvr Dec 23, 2023
4edeef0
Add tests to check that comments don't affect indentation
axvr Dec 23, 2023
0388414
Fix indentation of `with-*` macros
axvr Dec 23, 2023
41a45d4
Update comments and add `with-in-str` indent rule
axvr Dec 23, 2023
68999ca
Improve accuracy of function operand indenting
axvr Dec 25, 2023
8f38c11
Slight code refactor and added more comments
axvr Dec 26, 2023
7e90f09
When `lispoptions` feature is available, we can enable the `lisp` option
axvr Dec 26, 2023
0b388ae
Indent some macros a bit like functions
axvr Dec 26, 2023
289352d
More indent test cases
axvr Mar 9, 2024
cefb7bc
Fix escape character detection logic
axvr Mar 10, 2024
16026f7
Simplify character column resolution
axvr Mar 10, 2024
d87f254
Add some indent rules for more built in macros
axvr Mar 10, 2024
6a3d2d3
Don't use fn arg alignment indent when a keyword is in function position
axvr Sep 22, 2024
083f554
Merge 'origin/master' into indent-forms
axvr Sep 22, 2024
f271dca
Add extra indentation test cases for records and protocols
axvr Sep 22, 2024
b86a4c0
Begin refinements to configuration options
axvr Oct 4, 2024
01edfec
Fix config option lookup
axvr Oct 4, 2024
9b5e42c
Discovered a way to put comments within dict defs in Vim script
axvr Oct 4, 2024
a41fa1b
Improve indentation in "uniform" style and undo reader conditional work
axvr Oct 5, 2024
3487e07
Update README to better cover the new indentation options
axvr Oct 5, 2024
3c5258c
Replace the old multi-line string config option
axvr Oct 5, 2024
3b4dad8
Remove the link to the #vim IRC as the link is now dead
axvr Oct 5, 2024
117b082
Fix typos and re-add the insert-mode completion info to the README
axvr Oct 5, 2024
b26db95
Update the "About" section within the Vim help doc
axvr Oct 5, 2024
c4a25c5
Update indentation section of the Vim help doc
axvr Oct 5, 2024
1742549
Improve indentation style examples in Vim help doc
axvr Oct 5, 2024
9800368
Minor documentation and comment refinements
axvr Oct 5, 2024
69a0b6f
In README, mention the replacements for each old indent config option
axvr Oct 5, 2024
058b4a7
Add indentation tests covering keywords in function position
axvr Oct 6, 2024
0a835d8
Update indent regular expressions to set the required 'magic' mode
axvr Oct 6, 2024
0d865c5
Compress comments on indent code
axvr Oct 7, 2024
052a498
Compress more comments on indent code
axvr Oct 7, 2024
55543c4
Use `<kbd>` to show keyboard shortcuts in `README.md`
axvr Oct 13, 2024
208ff9f
Improved accuracy of first function argument detection in indentation
axvr Jan 18, 2025
968340e
Slightly improve keyboard shortcut formatting in `README.md`
axvr Jan 18, 2025
082120a
Set mode in new regexprs
axvr Jan 18, 2025
161ea5f
Make `ClojureIndent()` function public
axvr Jan 19, 2025
78ecad5
Indent `with-` macros and functions like cljfmt default
axvr Jan 19, 2025
6ce7017
Run indentation tests against Vim AND Neovim
axvr Jan 25, 2025
d2f39f9
Install latest Vim and Neovim in GitHub Actions
axvr Jan 25, 2025
8e9ed28
Merge latest changes from 'origin/master'
axvr Jan 25, 2025
e5f86af
New indentation test runner
axvr Jan 26, 2025
7659541
Improve new test runner output and detect script errors
axvr Jan 26, 2025
1ea333a
Speed up indentation tests on Vim
axvr Jan 26, 2025
aae9ae8
Move `indenttime` script to `dev` folder as `time-indent`
axvr Jan 26, 2025
1883215
Update GitHub Actions
axvr Jan 26, 2025
5f995a1
Kaocha not needed anymore, so removing dependency
axvr Jan 26, 2025
f3358f3
Bump copyright year
axvr Jan 26, 2025
13ca6fe
Remove unneeded config from `clj/profile.clj`
axvr Jan 26, 2025
5b277db
Run indentation tests in Vim's silent Ex-improved mode
axvr Jan 26, 2025
d8dc8a7
Reduce indentation test runner noise and add colour output
axvr Feb 4, 2025
e50122e
GitHub Actions "annotations" feature doesn't work with colour output
axvr Feb 4, 2025
c4b8d48
Simplify indentation test runner output on GitHub Actions
axvr Feb 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
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.)
  • Loading branch information
axvr committed May 1, 2023
commit ee2acc20b1b23c76a4f232e344673c61345b0704
128 changes: 76 additions & 52 deletions indent/clojure.vim
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
Expand All @@ -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())
Expand All @@ -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
Expand Down