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
:ALEFixfor quick access:nnoremap <Leader>af :ALEFix<CR> - Map error navigation:
nmap ]a <Plug>(ale_next_wrap)andnmap [a <Plug>(ale_previous_wrap) - Use
:ALEInfoto 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