diff --git a/autoload/ale/debugging.vim b/autoload/ale/debugging.vim index e56aea5..bbe546d 100644 --- a/autoload/ale/debugging.vim +++ b/autoload/ale/debugging.vim @@ -60,6 +60,18 @@ function! s:EchoGlobalVariables() abort endfor endfunction +function! s:EchoCommandHistory() abort + let l:buffer = bufnr('%') + + if !has_key(g:ale_buffer_info, l:buffer) + return + endif + + for l:item in g:ale_buffer_info[l:buffer].history + echom '(' . l:item.status . ') ' . string(l:item.command) + endfor +endfunction + function! ale#debugging#Info() abort let l:filetype = &filetype @@ -91,4 +103,7 @@ function! ale#debugging#Info() abort echom ' Global Variables:' echom '' call s:EchoGlobalVariables() + echom ' Command History:' + echom '' + call s:EchoCommandHistory() endfunction diff --git a/autoload/ale/engine.vim b/autoload/ale/engine.vim index d816a96..b54ad6e 100644 --- a/autoload/ale/engine.vim +++ b/autoload/ale/engine.vim @@ -9,6 +9,30 @@ " output: The array of lines for the output of the job. let s:job_info_map = {} +function! ale#engine#AddToHistory(buffer, status, job_id, command) abort + if g:ale_max_buffer_history_size <= 0 + " Don't save anything if the history isn't a positive number. + let g:ale_buffer_info[a:buffer].history = [] + + return + endif + + let l:history = g:ale_buffer_info[a:buffer].history + + " Remove the first item if we hit the max history size. + if len(l:history) >= g:ale_max_buffer_history_size + let l:history = l:history[1:] + endif + + call add(l:history, { + \ 'status': a:status, + \ 'job_id': a:job_id, + \ 'command': a:command, + \}) + + let g:ale_buffer_info[a:buffer].history = l:history +endfunction + function! s:GetJobID(job) abort if has('nvim') "In NeoVim, job values are just IDs. @@ -33,12 +57,14 @@ function! ale#engine#InitBufferInfo(buffer) abort " new_loclist holds loclist items while jobs are being run. " temporary_file_list holds temporary files to be cleaned up " temporary_directory_list holds temporary directories to be cleaned up + " history holds a list of previously run commands for this buffer let g:ale_buffer_info[a:buffer] = { \ 'job_list': [], \ 'loclist': [], \ 'new_loclist': [], \ 'temporary_file_list': [], \ 'temporary_directory_list': [], + \ 'history': [], \} endif endfunction @@ -437,19 +463,26 @@ function! s:RunJob(options) abort let l:job = job_start(l:command, l:job_options) endif + let l:status = 'failed' + let l:job_id = 0 + " Only proceed if the job is being run. if has('nvim') || (l:job !=# 'no process' && job_status(l:job) ==# 'run') " Add the job to the list of jobs, so we can track them. call add(g:ale_buffer_info[l:buffer].job_list, l:job) + let l:status = 'ran' + let l:job_id = s:GetJobID(l:job) " Store the ID for the job in the map to read back again. - let s:job_info_map[s:GetJobID(l:job)] = { + let s:job_info_map[l:job_id] = { \ 'linter': l:linter, \ 'buffer': l:buffer, \ 'output': [], \ 'next_chain_index': l:next_chain_index, \} endif + + call ale#engine#AddToHistory(l:buffer, l:status, l:job_id, l:command) endfunction " Determine which commands to run for a link in a command chain, or diff --git a/plugin/ale.vim b/plugin/ale.vim index 45b0b90..7074e46 100644 --- a/plugin/ale.vim +++ b/plugin/ale.vim @@ -131,6 +131,9 @@ let g:ale_statusline_format = get(g:, 'ale_statusline_format', let g:ale_warn_about_trailing_whitespace = \ get(g:, 'ale_warn_about_trailing_whitespace', 1) +" A flag for controlling the maximum size of the command history to store. +let g:ale_max_buffer_history_size = get(g:, 'ale_max_buffer_history_size', 20) + function! s:ALEInitAuGroups() abort augroup ALERunOnTextChangedGroup autocmd! diff --git a/test/test_ale_info.vader b/test/test_ale_info.vader index 838abe7..76699d6 100644 --- a/test/test_ale_info.vader +++ b/test/test_ale_info.vader @@ -34,10 +34,17 @@ Before: \ 'let g:ale_statusline_format = [''%d error(s)'', ''%d warning(s)'', ''OK'']', \ 'let g:ale_warn_about_trailing_whitespace = 1', \], "\n") + let g:command_header = "\n Command History:\n" After: unlet! g:output unlet! g:globals_string + unlet! g:command_header + let g:ale_buffer_info = {} + unlet! g:ale_testft_testlinter1_foo + unlet! g:ale_testft_testlinter1_bar + unlet! g:ale_testft2_testlinter2_foo + unlet! g:ale_testft2_testlinter2_bar Given nolintersft (Empty buffer with no linters): Execute (ALEInfo with no linters should return the right output): @@ -49,7 +56,7 @@ Execute (ALEInfo with no linters should return the right output): \Available Linters: []\n \ Enabled Linters: []\n \ Linter Variables:\n - \" . g:globals_string, g:output + \" . g:globals_string . g:command_header, g:output Given (Empty buffer with no filetype): Execute (ALEInfo with no filetype should return the right output): @@ -61,7 +68,7 @@ Execute (ALEInfo with no filetype should return the right output): \Available Linters: []\n \ Enabled Linters: []\n \ Linter Variables:\n - \" . g:globals_string, g:output + \" . g:globals_string . g:command_header, g:output Given testft (Empty buffer): Execute (ALEInfo with a single linter should return the right output): @@ -74,7 +81,7 @@ Execute (ALEInfo with a single linter should return the right output): \Available Linters: ['testlinter1']\n \ Enabled Linters: ['testlinter1']\n \ Linter Variables:\n - \" . g:globals_string, g:output + \" . g:globals_string . g:command_header, g:output Given testft (Empty buffer): Execute (ALEInfo with two linters should return the right output): @@ -88,7 +95,7 @@ Execute (ALEInfo with two linters should return the right output): \Available Linters: ['testlinter1', 'testlinter2']\n \ Enabled Linters: ['testlinter1', 'testlinter2']\n \ Linter Variables:\n - \" . g:globals_string, g:output + \" . g:globals_string . g:command_header, g:output Given testft (Empty buffer): Execute (ALEInfo should calculate enabled linters correctly): @@ -118,7 +125,7 @@ Execute (ALEInfo should only return linters for current filetype): \Available Linters: ['testlinter1']\n \ Enabled Linters: ['testlinter1']\n \ Linter Variables:\n - \" . g:globals_string, g:output + \" . g:globals_string . g:command_header, g:output Given testft.testft2 (Empty buffer with two filetypes): Execute (ALEInfo with compound filetypes should return linters for both of them): @@ -132,7 +139,7 @@ Execute (ALEInfo with compound filetypes should return linters for both of them) \Available Linters: ['testlinter1', 'testlinter2']\n \ Enabled Linters: ['testlinter1', 'testlinter2']\n \ Linter Variables:\n - \" . g:globals_string, g:output + \" . g:globals_string . g:command_header, g:output Given testft.testft2 (Empty buffer with two filetypes): Execute (ALEInfo should return appropriately named global variables): @@ -155,4 +162,32 @@ Execute (ALEInfo should return appropriately named global variables): \let g:ale_testft2_testlinter2_bar = {'x': 'y'}\n \let g:ale_testft2_testlinter2_foo = 123\n \let g:ale_testft_testlinter1_bar = ['abc']\n - \let g:ale_testft_testlinter1_foo = 'abc'" . g:globals_string, g:output + \let g:ale_testft_testlinter1_foo = 'abc'" + \ . g:globals_string . g:command_header, g:output + +Given testft.testft2 (Empty buffer with two filetypes): +Execute (ALEInfo should return command history): + let g:ale_buffer_info[bufnr('%')] = { + \ 'history': [ + \ {'status': 'ran', 'job_id': 347, 'command': 'first command'}, + \ {'status': 'ran', 'job_id': 347, 'command': ['/bin/bash', '\c', 'last command']}, + \ ], + \} + + call ale#linter#Define('testft', g:testlinter1) + call ale#linter#Define('testft2', g:testlinter2) + redir => g:output + silent ALEInfo + redir END + AssertEqual + \ join([ + \ '', + \ ' Current Filetype: testft.testft2', + \ 'Available Linters: [''testlinter1'', ''testlinter2'']', + \ ' Enabled Linters: [''testlinter1'', ''testlinter2'']', + \ ' Linter Variables:', + \ g:globals_string . g:command_header, + \ '(ran) ''first command''', + \ '(ran) [''/bin/bash'', ''\c'', ''last command'']', + \ ], "\n"), + \ g:output diff --git a/test/test_history_saving.vader b/test/test_history_saving.vader new file mode 100644 index 0000000..a06f9a7 --- /dev/null +++ b/test/test_history_saving.vader @@ -0,0 +1,69 @@ +Before: + let g:history = [] + let g:ale_buffer_info = {} + let g:ale_max_buffer_history_size = 20 + + function! CollectResults(buffer, output) + return [] + endfunction + + call ale#linter#Define('foobar', { + \ 'name': 'testlinter', + \ 'callback': 'CollectResults', + \ 'executable': 'echo', + \ 'command': 'echo command history test', + \ 'read_buffer': 0, + \}) + +After: + unlet g:history + let g:ale_buffer_info = {} + let g:ale_max_buffer_history_size = 20 + call ale#linter#Reset() + delfunction CollectResults + +Given foobar (Some imaginary filetype): + anything + +Execute(History should be set when commands are run): + AssertEqual 'foobar', &filetype + + call ale#Lint() + call ale#engine#WaitForJobs(2000) + + let g:history = g:ale_buffer_info[bufnr('%')].history + + AssertEqual 1, len(g:history) + AssertEqual ['status', 'job_id', 'command'], keys(g:history[0]) + AssertEqual ['/bin/bash', '-c', 'echo command history test'], g:history[0].command + AssertEqual 'ran', g:history[0].status + " The Job ID will change each time, but we can check the type. + AssertEqual type(1), type(g:history[0].job_id) + +Execute(History items should be popped after going over the max): + let g:ale_buffer_info[1] = { + \ 'history': map(range(20), '{''status'': ''ran'', ''job_id'': v:val, ''command'': ''foobar''}'), + \} + + call ale#engine#AddToHistory(1, 'ran', 347, 'last command') + + AssertEqual + \ ( + \ map(range(1, 19), '{''status'': ''ran'', ''job_id'': v:val, ''command'': ''foobar''}') + \ + [{'status': 'ran', 'job_id': 347, 'command': 'last command'}] + \ ), + \ g:ale_buffer_info[1].history + +Execute(Nothing should be added to history if the size is too low): + let g:ale_max_buffer_history_size = 0 + let g:ale_buffer_info[1] = {'history': []} + + call ale#engine#AddToHistory(1, 'ran', 347, 'last command') + + AssertEqual [], g:ale_buffer_info[1].history + + let g:ale_max_buffer_history_size = -2 + + call ale#engine#AddToHistory(1, 'ran', 347, 'last command') + + AssertEqual [], g:ale_buffer_info[1].history