Skip to content

Instantly share code, notes, and snippets.

@eyalk11
Last active January 20, 2026 22:09
Show Gist options
  • Select an option

  • Save eyalk11/3a0c3404fba880fb11ffa853ea06c5c0 to your computer and use it in GitHub Desktop.

Select an option

Save eyalk11/3a0c3404fba880fb11ffa853ea06c5c0 to your computer and use it in GitHub Desktop.
fix indentation of python code in vim
function! AlignWithTopLine() range
" Handle case when selection is at the start of the file
if a:firstline <= 1
" If at the start of file, use default indentation (0)
let reference_indent = 0
let above_line = ""
else
" Find the first non-empty line above the selection
let above_line_num = a:firstline - 1
while above_line_num >= 1 && getline(above_line_num) =~ '^\s*$'
let above_line_num = above_line_num - 1
endwhile
" If we couldn't find a non-empty line, use default indentation
if above_line_num < 1
let reference_indent = 0
let above_line = ""
else
let above_line = getline(above_line_num)
let reference_indent = indent(above_line_num)
endif
endif
" Check characteristics of the line above (if it exists)
let ends_with_colon = !empty(above_line) && above_line =~# ':$'
let starts_with_special = !empty(above_line) && above_line =~# '^\s*\(with\|await\|if\|for\|while\|def\|class\)'
let is_continuation = !empty(above_line) && above_line =~# '\((\|\[\|{\).*\(,\|\\\)$'
" Determine target indentation
let target_indent = reference_indent
" Handle different cases for alignment
if ends_with_colon || starts_with_special
" For block starts or special constructs, add one level of indentation
let target_indent = reference_indent + &shiftwidth
elseif is_continuation
" For line continuations (ending with comma or backslash), align with extra indent
let target_indent = reference_indent + &shiftwidth
endif
" Get the first line's current indentation as reference for relative shifts
let first_line = getline(a:firstline)
let first_line_indent = indent(a:firstline)
let first_line_content = substitute(first_line, '^\s*', '', '')
" Check if the first line of selection is a continuation of a previous statement
let first_line_is_continuation = !empty(above_line) &&
\ (above_line =~# '\((\|\[\|{\|\\$\|,$\)' &&
\ first_line_content !~# '^\()\|]\|}\)')
" Calculate the indentation shift needed
let indent_shift = 0
if first_line_is_continuation
let indent_shift = target_indent - first_line_indent
else
let indent_shift = target_indent - first_line_indent
endif
" Apply the indentation to all lines in the selection
for lineno in range(a:firstline, a:lastline)
let line = getline(lineno)
let current_indent = indent(lineno)
let current_line_content = substitute(line, '^\s*', '', '')
" Skip empty lines
if current_line_content == ''
continue
endif
" Calculate new indentation preserving relative structure
let new_indent = current_indent + indent_shift
" Special handling for closing brackets/braces/parentheses
if current_line_content =~# '^\()\|]\|}\)'
" Closing brackets align with the opening line (one level back)
let new_indent = max([0, target_indent - &shiftwidth])
endif
" Ensure indentation is never negative
let new_indent = max([0, new_indent])
" Create the new line with proper indentation
let new_line = repeat(' ', new_indent) . current_line_content
call setline(lineno, new_line)
endfor
endfunction
function! ConditionalAlign()
if &filetype == 'python'
" Store the original selection boundaries
let l:start_line = a:firstline
let l:end_line = a:lastline
" Apply indent to the selection. autopep8 will not align if
" with xx:
" dosomethin
" if there are not indentation
norm gv4>
" Run autopep8 on the selection, assume indentation = 0
silent execute l:start_line . ',' . l:end_line . '!autopep8 -'
" Re-indent to above line
silent execute l:start_line . ',' . l:end_line . 'call AlignWithTopLine()'
else
normal! gv=
endif
endfunction
vnoremap = :'<,'>call ConditionalAlign()<CR>
@ipatch
Copy link

ipatch commented Jan 20, 2026

thanks for sharing this. daily neovim user here, and coming from many different languages i often use == to format the indentation of the current line, and i've been having the worst time working with python files. using the native vim / neovim indentexpr is not that great nor is the indentexpr provided treesitter that much better. and i tried using ruff to try and format a few visually selected lines, but apparently that can only format the entire file, not what i'm looking for.

do you still actively use this? or have you moved on to something else? thanks again for sharing 🙏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment