vimtricks.wiki Concise Vim tricks, one at a time.

How do I get real-time asynchronous linting and fixing in Vim?

Answer

:ALEToggle

Explanation

The ALE (Asynchronous Lint Engine) plugin provides real-time linting and automatic fixing for dozens of languages without blocking your editor. Unlike older synchronous linting approaches that freeze Vim while running external tools, ALE runs linters in the background using Vim's job control and displays errors in the sign column and location list as you type.

How it works

Once ALE is installed, it automatically detects available linters for the current filetype and starts checking your code as you edit. Error and warning signs appear in the gutter, and you can navigate between them with built-in mappings.

Navigating between errors

ALE provides mappings to jump between lint errors and warnings:

]a    " jump to the next ALE error or warning (with vim-unimpaired style)
[a    " jump to the previous ALE error or warning

Or use ALE's native commands:

:ALENext         " jump to next issue
:ALEPrevious     " jump to previous issue
:ALENextWrap     " jump to next issue, wrapping around the file
:ALEPreviousWrap " jump to previous issue, wrapping around

Automatic fixing

ALE can auto-fix files using formatters like Prettier, Black, gofmt, and rustfmt:

:ALEFix

Configure fixers per language in your vimrc:

let g:ale_fixers = {
\   'javascript': ['prettier', 'eslint'],
\   'python': ['black', 'isort'],
\   'go': ['gofmt', 'goimports'],
\   'rust': ['rustfmt'],
\   'css': ['prettier'],
\   '*': ['remove_trailing_lines', 'trim_whitespace'],
\}

Enable fix-on-save to automatically format every time you write:

let g:ale_fix_on_save = 1

Configuring linters

Specify which linters to use per filetype:

let g:ale_linters = {
\   'javascript': ['eslint'],
\   'python': ['flake8', 'mypy'],
\   'go': ['golangci-lint'],
\   'ruby': ['rubocop'],
\}

Controlling when linting runs

By default, ALE lints on every text change. Adjust the trigger behavior:

let g:ale_lint_on_text_changed = 'normal'  " only lint in normal mode (not insert)
let g:ale_lint_on_insert_leave = 1         " lint when leaving insert mode
let g:ale_lint_on_save = 1                 " lint on save
let g:ale_lint_on_enter = 1                " lint when opening a file

Displaying error details

ALE shows inline error messages in the echo area as you move your cursor over an error line. For more detail:

:ALEDetail     " show the full error message in a preview window
:ALEInfo       " show which linters and fixers are active for this buffer

Sign column and virtual text

Customize how errors appear in the gutter:

let g:ale_sign_error = '✘'
let g:ale_sign_warning = '⚠'
let g:ale_sign_column_always = 1         " keep sign column always visible
let g:ale_virtualtext_cursor = 'current' " show error text inline at cursor

LSP integration

ALE can also act as a lightweight LSP client, providing hover information, go-to-definition, and completion without needing a separate LSP plugin:

:ALEGoToDefinition
:ALEHover
:ALEFindReferences
:ALERename

Tips

  • Map :ALEFix for quick access: nnoremap <Leader>af :ALEFix<CR>
  • Map error navigation: nmap ]a <Plug>(ale_next_wrap) and nmap [a <Plug>(ale_previous_wrap)
  • Use :ALEInfo to debug why a linter is not running — it shows detected linters, their paths, and any errors
  • ALE works with both Vim 8+ and Neovim — for Neovim users who want a Lua-native alternative, nvim-lint combined with conform.nvim provides similar linting and formatting capabilities
  • Disable ALE for specific filetypes with let g:ale_linters_ignore = {'text': ['all']}
  • ALE and vim-fugitive complement each other well — ALE handles code quality, fugitive handles version control

Next

How do I edit multiple lines at once using multiple cursors in Vim?