+" Author: w0rp <devw0rp@gmail.com>, David Alexander <opensource@thelonelyghost.com>
+" Description: Primary code path for the plugin
+" Manages execution of linters when requested by autocommands
+
+" Strings used for severity in the echoed message
+let g:ale_echo_msg_error_str = get(g:, 'ale_echo_msg_error_str', 'Error')
+let g:ale_echo_msg_info_str = get(g:, 'ale_echo_msg_info_str', 'Info')
+let g:ale_echo_msg_log_str = get(g:, 'ale_echo_msg_log_str', 'Log')
+let g:ale_echo_msg_warning_str = get(g:, 'ale_echo_msg_warning_str', 'Warning')
+
+" LSP window/showMessage format
+let g:ale_lsp_show_message_format = get(g:, 'ale_lsp_show_message_format', '%severity%:%linter%: %s')
+" Valid values mimic LSP definitions (error, warning and information; log is
+" never shown)
+let g:ale_lsp_show_message_severity = get(g:, 'ale_lsp_show_message_severity', 'error')
+
+let s:lint_timer = -1
+let s:getcmdwintype_exists = exists('*getcmdwintype')
+
+" Return 1 if a file is too large for ALE to handle.
+function! ale#FileTooLarge(buffer) abort
+ let l:max = getbufvar(a:buffer, 'ale_maximum_file_size', get(g:, 'ale_maximum_file_size', 0))
+
+ return l:max > 0 ? (line2byte(line('$') + 1) > l:max) : 0
+endfunction
+
+" A function for checking various conditions whereby ALE just shouldn't
+" attempt to do anything, say if particular buffer types are open in Vim.
+function! ale#ShouldDoNothing(buffer) abort
+ " The checks are split into separate if statements to make it possible to
+ " profile each check individually with Vim's profiling tools.
+ "
+ " Do nothing if ALE is disabled.
+ if !getbufvar(a:buffer, 'ale_enabled', get(g:, 'ale_enabled', 0))
+ return 1
+ endif
+
+ " Don't perform any checks when newer NeoVim versions are exiting.
+ if get(v:, 'exiting', v:null) isnot v:null
+ return 1
+ endif
+
+ let l:filetype = getbufvar(a:buffer, '&filetype')
+
+ " Do nothing when there's no filetype.
+ if l:filetype is# ''
+ return 1
+ endif
+
+ " Do nothing for diff buffers.
+ if getbufvar(a:buffer, '&diff')
+ return 1
+ endif
+
+ " Do nothing for blacklisted files.
+ if index(get(g:, 'ale_filetype_blacklist', []), l:filetype) >= 0
+ return 1
+ endif
+
+ " Do nothing if running from command mode.
+ if s:getcmdwintype_exists && !empty(getcmdwintype())
+ return 1
+ endif
+
+ let l:filename = fnamemodify(bufname(a:buffer), ':t')
+
+ " Do nothing for directories.
+ if l:filename is# '.'
+ return 1
+ endif
+
+ " Don't start linting and so on when an operator is pending.
+ if ale#util#Mode(1) is# 'no'
+ return 1
+ endif
+
+ " Do nothing if running in the sandbox.
+ if ale#util#InSandbox()
+ return 1
+ endif
+
+ " Do nothing if the file is too large.
+ if ale#FileTooLarge(a:buffer)
+ return 1
+ endif
+
+ " Do nothing from CtrlP buffers with CtrlP-funky.
+ if exists(':CtrlPFunky') is 2
+ \&& getbufvar(a:buffer, '&l:statusline') =~# 'CtrlPMode.*funky'
+ return 1
+ endif
+
+ return 0
+endfunction
+
+function! s:Lint(buffer, should_lint_file, timer_id) abort
+ " Use the filetype from the buffer
+ let l:filetype = getbufvar(a:buffer, '&filetype')
+ let l:linters = ale#linter#Get(l:filetype)
+
+ let l:ignore_config = ale#Var(a:buffer, 'linters_ignore')
+ let l:disable_lsp = ale#Var(a:buffer, 'disable_lsp')
+
+ " Load code to ignore linters only if we need to.
+ if (
+ \ !empty(l:ignore_config)
+ \ || l:disable_lsp is 1
+ \ || l:disable_lsp is v:true
+ \ || (l:disable_lsp is# 'auto' && get(g:, 'lspconfig', 0))
+ \)
+ let l:linters = ale#engine#ignore#Exclude(
+ \ l:filetype,
+ \ l:linters,
+ \ l:ignore_config,
+ \ l:disable_lsp,
+ \)
+ endif
+
+ " Tell other sources that they can start checking the buffer now.
+ let g:ale_want_results_buffer = a:buffer
+ silent doautocmd <nomodeline> User ALEWantResults
+ unlet! g:ale_want_results_buffer
+
+ " Don't set up buffer data and so on if there are no linters to run.
+ if !has_key(g:ale_buffer_info, a:buffer) && empty(l:linters)
+ return
+ endif
+
+ " Clear lint_file linters, or only run them if the file exists.
+ let l:lint_file = empty(l:linters)
+ \ || (a:should_lint_file && filereadable(expand('#' . a:buffer . ':p')))
+
+ call ale#engine#RunLinters(a:buffer, l:linters, l:lint_file)
+endfunction
+
+" (delay, [linting_flag, buffer_number])
+function! ale#Queue(delay, ...) abort
+ if a:0 > 2
+ throw 'too many arguments!'
+ endif
+
+ let l:buffer = get(a:000, 1, v:null)
+
+ if l:buffer is v:null
+ let l:buffer = bufnr('')
+ endif
+
+ if type(l:buffer) isnot v:t_number
+ throw 'buffer_number must be a Number'
+ endif
+
+ if ale#ShouldDoNothing(l:buffer)
+ return
+ endif
+
+ " Default linting_flag to ''
+ let l:should_lint_file = get(a:000, 0) is# 'lint_file'
+
+ if s:lint_timer != -1
+ call timer_stop(s:lint_timer)
+ let s:lint_timer = -1
+ endif
+
+ if a:delay > 0
+ let s:lint_timer = timer_start(
+ \ a:delay,
+ \ function('s:Lint', [l:buffer, l:should_lint_file])
+ \)
+ else
+ call s:Lint(l:buffer, l:should_lint_file, 0)
+ endif
+endfunction
+
+let s:current_ale_version = [4, 0, 0]
+
+" A function used to check for ALE features in files outside of the project.
+function! ale#Has(feature) abort
+ let l:match = matchlist(a:feature, '\c\v^ale-(\d+)\.(\d+)(\.(\d+))?$')
+
+ if !empty(l:match)
+ let l:version = [l:match[1] + 0, l:match[2] + 0, l:match[4] + 0]
+
+ return ale#semver#GTE(s:current_ale_version, l:version)
+ endif
+
+ return 0
+endfunction
+
+" Given a buffer number and a variable name, look for that variable in the
+" buffer scope, then in global scope. If the name does not exist in the global
+" scope, an exception will be thrown.
+"
+" Every variable name will be prefixed with 'ale_'.
+function! ale#Var(buffer, variable_name) abort
+ let l:full_name = 'ale_' . a:variable_name
+ let l:vars = getbufvar(str2nr(a:buffer), '', {})
+
+ return get(l:vars, l:full_name, g:[l:full_name])
+endfunction
+
+" Initialize a variable with a default value, if it isn't already set.
+"
+" Every variable name will be prefixed with 'ale_'.
+function! ale#Set(variable_name, default) abort
+ let l:full_name = 'ale_' . a:variable_name
+
+ if !has_key(g:, l:full_name)
+ let g:[l:full_name] = a:default
+ endif
+endfunction
+
+" Given a string for adding to a command, return the string padded with a
+" space on the left if it is not empty. Otherwise return an empty string.
+"
+" This can be used for making command strings cleaner and easier to test.
+function! ale#Pad(string) abort
+ return !empty(a:string) ? ' ' . a:string : ''
+endfunction
+
+" Given a environment variable name and a value, produce part of a command for
+" setting an environment variable before running a command. The syntax will be
+" valid for cmd on Windows, or most shells on Unix.
+function! ale#Env(variable_name, value) abort
+ if has('win32')
+ return 'set ' . ale#Escape(a:variable_name . '=' . a:value) . ' && '
+ endif
+
+ return a:variable_name . '=' . ale#Escape(a:value) . ' '
+endfunction
+
+" Escape a string suitably for each platform.
+" shellescape does not work on Windows.
+function! ale#Escape(str) abort
+ if fnamemodify(&shell, ':t') is? 'cmd.exe'
+ " If the string contains spaces, it will be surrounded by quotes.
+ " Otherwise, special characters will be escaped with carets (^).
+ return substitute(
+ \ a:str =~# ' '
+ \ ? '"' . substitute(a:str, '"', '""', 'g') . '"'
+ \ : substitute(a:str, '\v([&|<>^])', '^\1', 'g'),
+ \ '%',
+ \ '%%',
+ \ 'g',
+ \)
+ endif
+
+ return shellescape (a:str)
+endfunction
+
+" Get the loclist item message according to a given format string.
+"
+" See `:help g:ale_loclist_msg_format` and `:help g:ale_echo_msg_format`
+function! ale#GetLocItemMessage(item, format_string) abort
+ let l:msg = a:format_string
+ let l:severity = g:ale_echo_msg_warning_str
+ let l:code = get(a:item, 'code', '')
+ let l:type = get(a:item, 'type', 'E')
+ let l:linter_name = get(a:item, 'linter_name', '')
+ let l:code_repl = !empty(l:code) ? '\=submatch(1) . l:code . submatch(2)' : ''
+
+ if l:type is# 'E'
+ let l:severity = g:ale_echo_msg_error_str
+ elseif l:type is# 'I'
+ let l:severity = g:ale_echo_msg_info_str
+ endif
+
+ " Replace special markers with certain information.
+ " \=l:variable is used to avoid escaping issues.
+ let l:msg = substitute(l:msg, '\v\%([^\%]*)code([^\%]*)\%', l:code_repl, 'g')
+ let l:msg = substitute(l:msg, '\V%severity%', '\=l:severity', 'g')
+ let l:msg = substitute(l:msg, '\V%type%', '\=l:type', 'g')
+ let l:msg = substitute(l:msg, '\V%linter%', '\=l:linter_name', 'g')
+ " Replace %s with the text.
+ let l:msg = substitute(l:msg, '\V%s', '\=a:item.text', 'g')
+ " Windows may insert carriage return line endings (^M), strip these characters.
+ let l:msg = substitute(l:msg, '\r', '', 'g')
+
+ return l:msg
+endfunction
+
+" Given a buffer and a linter or fixer name, return an Array of two-item
+" Arrays describing how to map filenames to and from the local to foreign file
+" systems.
+function! ale#GetFilenameMappings(buffer, name) abort
+ let l:linter_mappings = ale#Var(a:buffer, 'filename_mappings')
+
+ if type(l:linter_mappings) is v:t_list
+ return l:linter_mappings
+ endif
+
+ let l:name = a:name
+
+ if !has_key(l:linter_mappings, l:name)
+ " Use * as a default setting for all tools.
+ let l:name = '*'
+ endif
+
+ return get(l:linter_mappings, l:name, [])
+endfunction