From f7fbbd16f7fc75ab0622a2eb9f7149e06e2f1516 Mon Sep 17 00:00:00 2001 From: dhleong Date: Mon, 10 Oct 2016 14:08:51 -0400 Subject: [PATCH 01/17] Implement a preliminary quest_messages with regex support --- plugin/youcompleteme.vim | 4 + python/ycm/diagnostic_filter.py | 95 ++++++++++++++++++ python/ycm/diagnostic_interface.py | 4 +- python/ycm/tests/diagnostic_filter_tests.py | 103 ++++++++++++++++++++ 4 files changed, 205 insertions(+), 1 deletion(-) create mode 100644 python/ycm/diagnostic_filter.py create mode 100644 python/ycm/tests/diagnostic_filter_tests.py diff --git a/plugin/youcompleteme.vim b/plugin/youcompleteme.vim index 268a7274..ec5399c9 100644 --- a/plugin/youcompleteme.vim +++ b/plugin/youcompleteme.vim @@ -121,6 +121,10 @@ let g:ycm_warning_symbol = \ get( g:, 'ycm_warning_symbol', \ get( g:, 'syntastic_warning_symbol', '>>' ) ) +let g:ycm_quiet_messages = + \ get( g:, 'ycm_quiet_messages', + \ get( g:, 'syntastic_quiet_messages', {} ) ) + let g:ycm_goto_buffer_command = \ get( g:, 'ycm_goto_buffer_command', 'same-buffer' ) diff --git a/python/ycm/diagnostic_filter.py b/python/ycm/diagnostic_filter.py new file mode 100644 index 00000000..523d605d --- /dev/null +++ b/python/ycm/diagnostic_filter.py @@ -0,0 +1,95 @@ +# Copyright (C) 2013 Google Inc. +# +# This file is part of YouCompleteMe. +# +# YouCompleteMe is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# YouCompleteMe is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with YouCompleteMe. If not, see . + +from __future__ import unicode_literals +from __future__ import print_function +from __future__ import division +from __future__ import absolute_import +from future import standard_library +standard_library.install_aliases() +from builtins import * # noqa + +from future.utils import itervalues, iteritems +from collections import defaultdict, namedtuple +from ycm import vimsupport +import re +import vim + + +class DiagnosticFilter( object ): + def __init__( self, config ): + self._filters = [] + + for filter_type in config.iterkeys(): + wrapper = lambda x: x + actual_filter_type = filter_type + + if filter_type[0] == '!': + wrapper = _Not + filter_type = filter_type[1:] + compiler = FILTER_COMPILERS.get( filter_type ) + + if compiler is not None: + for filter_config in _ListOf( config[ actual_filter_type ] ): + fn = wrapper( compiler( filter_config ) ) + self._filters.append( fn ) + + + def Accept( self, diagnostic ): + # NB: we Accept() the diagnostic ONLY if + # no filters match it + for f in self._filters: + if f( diagnostic ): + return False + + return True + + + @staticmethod + def from_filetype( user_options, filetypes ): + base = dict( user_options[ 'quiet_messages' ] ) + + for filetype in filetypes: + type_specific = user_options.get( filetype + '_quiet_messages', {} ) + base.update( type_specific ) + return DiagnosticFilter( base ) + + +def _ListOf( config_entry ): + if type( config_entry ) == type( [] ): + return config_entry + + return [ config_entry ] + + +def _Not( fn ): + def Inverted( diagnostic ): + return not fn( diagnostic ) + + return Inverted + + +def _CompileRegex( raw_regex ): + pattern = re.compile( raw_regex, re.IGNORECASE ) + + def Filter( diagnostic ): + return pattern.search( diagnostic[ 'text' ] ) is not None + + return Filter + + +FILTER_COMPILERS = { 'regex' : _CompileRegex } diff --git a/python/ycm/diagnostic_interface.py b/python/ycm/diagnostic_interface.py index 8607d8ec..089eac2d 100644 --- a/python/ycm/diagnostic_interface.py +++ b/python/ycm/diagnostic_interface.py @@ -26,6 +26,7 @@ from builtins import * # noqa from future.utils import itervalues, iteritems from collections import defaultdict, namedtuple from ycm import vimsupport +from ycm.diagnostic_filter import DiagnosticFilter import vim @@ -65,7 +66,8 @@ class DiagnosticInterface( object ): def UpdateWithNewDiagnostics( self, diags ): - normalized_diags = [ _NormalizeDiagnostic( x ) for x in diags ] + diag_filter = DiagnosticFilter.from_filetype( self._user_options, vimsupport.CurrentFiletypes() ) + normalized_diags = [ _NormalizeDiagnostic( x ) for x in diags if diag_filter.Accept(x) ] self._buffer_number_to_line_to_diags = _ConvertDiagListToDict( normalized_diags ) diff --git a/python/ycm/tests/diagnostic_filter_tests.py b/python/ycm/tests/diagnostic_filter_tests.py new file mode 100644 index 00000000..e03d96d8 --- /dev/null +++ b/python/ycm/tests/diagnostic_filter_tests.py @@ -0,0 +1,103 @@ +# Copyright (C) 2013 Google Inc. +# +# This file is part of YouCompleteMe. +# +# YouCompleteMe is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# YouCompleteMe is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with YouCompleteMe. If not, see . + +from __future__ import unicode_literals +from __future__ import print_function +from __future__ import division +from __future__ import absolute_import +from future import standard_library +standard_library.install_aliases() +from builtins import * # noqa + +from ycm.test_utils import MockVimModule +MockVimModule() + +import os +from hamcrest import assert_that, equal_to +from ycm.diagnostic_filter import DiagnosticFilter + + +def _assert_accept_equals( filter, text, expected ): + assert_that( filter.Accept( { 'text': text } ), equal_to( expected ) ) + +def _assert_accepts( filter, text ): + _assert_accept_equals( filter, text, True ) + +def _assert_rejects( filter, text ): + _assert_accept_equals( filter, text, False ) + + +class ConfigPriority_test(): + + def ConfigPriority_Global_test( self ): + opts = { 'quiet_messages': { 'regex': 'taco' } } + f = DiagnosticFilter.from_filetype( opts, [ 'java' ] ) + + _assert_rejects( f, 'This is a Taco' ) + _assert_accepts( f, 'This is a Burrito' ) + + + def ConfigPriority_Filetype_test( self ): + opts = { 'quiet_messages' : {}, + 'java_quiet_messages' : { 'regex': 'taco' } } + f = DiagnosticFilter.from_filetype( opts, [ 'java' ] ) + + _assert_rejects( f, 'This is a Taco' ) + _assert_accepts( f, 'This is a Burrito' ) + + + def ConfigPriority_FiletypeOverridesGlobal_test( self ): + # NB: if the filetype doesn't override the global, + # we would reject burrito and accept taco + opts = { 'quiet_messages' : { 'regex': 'burrito'}, + 'java_quiet_messages' : { 'regex': 'taco' } } + f = DiagnosticFilter.from_filetype( opts, [ 'java' ] ) + + _assert_rejects( f, 'This is a Taco' ) + _assert_accepts( f, 'This is a Burrito' ) + + +class ListOrSingle_test(): + # NB: we already test the single config above + + def ListOrSingle_SingleList_test( self ): + # NB: if the filetype doesn't override the global, + # we would reject burrito and accept taco + opts = { 'quiet_messages' : { 'regex': [ 'taco' ] } } + f = DiagnosticFilter.from_filetype( opts, [ 'java' ] ) + + _assert_rejects( f, 'This is a Taco' ) + _assert_accepts( f, 'This is a Burrito' ) + + + def ListOrSingle_MultiList_test( self ): + # NB: if the filetype doesn't override the global, + # we would reject burrito and accept taco + opts = { 'quiet_messages' : { 'regex': [ 'taco', 'burrito' ] } } + f = DiagnosticFilter.from_filetype( opts, [ 'java' ] ) + + _assert_rejects( f, 'This is a Taco' ) + _assert_rejects( f, 'This is a Burrito' ) + + +def Invert_test(): + opts = { 'quiet_messages' : { '!regex': [ 'taco' ] } } + f = DiagnosticFilter.from_filetype( opts, [ 'java' ] ) + + _assert_accepts( f, 'This is a Taco' ) + _assert_rejects( f, 'This is a Burrito' ) + From 73343bd98fdbc5599435ff2ec911e4d33f06fcc9 Mon Sep 17 00:00:00 2001 From: dhleong Date: Mon, 10 Oct 2016 14:20:49 -0400 Subject: [PATCH 02/17] Support filtering on level/kind --- python/ycm/diagnostic_filter.py | 20 +++++++++++--- python/ycm/tests/diagnostic_filter_tests.py | 30 ++++++++++++++++++--- 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/python/ycm/diagnostic_filter.py b/python/ycm/diagnostic_filter.py index 523d605d..6c653911 100644 --- a/python/ycm/diagnostic_filter.py +++ b/python/ycm/diagnostic_filter.py @@ -86,10 +86,24 @@ def _Not( fn ): def _CompileRegex( raw_regex ): pattern = re.compile( raw_regex, re.IGNORECASE ) - def Filter( diagnostic ): + def FilterRegex( diagnostic ): return pattern.search( diagnostic[ 'text' ] ) is not None - return Filter + return FilterRegex -FILTER_COMPILERS = { 'regex' : _CompileRegex } +def _CompileLevel( level ): + # valid kinds are WARNING and ERROR; + # expected input levels are `warnings` and `errors` + # NB: we don't validate the input... + expected_kind = level.upper()[:-1] + + def FilterLevel( diagnostic ): + print( diagnostic, 'matches?', expected_kind ) + return diagnostic[ 'kind' ] == expected_kind + + return FilterLevel + + +FILTER_COMPILERS = { 'regex' : _CompileRegex, + 'level' : _CompileLevel } diff --git a/python/ycm/tests/diagnostic_filter_tests.py b/python/ycm/tests/diagnostic_filter_tests.py index e03d96d8..dc84bcf9 100644 --- a/python/ycm/tests/diagnostic_filter_tests.py +++ b/python/ycm/tests/diagnostic_filter_tests.py @@ -31,8 +31,11 @@ from hamcrest import assert_that, equal_to from ycm.diagnostic_filter import DiagnosticFilter -def _assert_accept_equals( filter, text, expected ): - assert_that( filter.Accept( { 'text': text } ), equal_to( expected ) ) +def _assert_accept_equals( filter, text_or_obj, expected ): + if type( text_or_obj ) is not type( {} ): + text_or_obj = { 'text': text_or_obj } + + assert_that( filter.Accept( text_or_obj ), equal_to( expected ) ) def _assert_accepts( filter, text ): _assert_accept_equals( filter, text, True ) @@ -95,9 +98,30 @@ class ListOrSingle_test(): def Invert_test(): - opts = { 'quiet_messages' : { '!regex': [ 'taco' ] } } + opts = { 'quiet_messages' : { '!regex': 'taco' } } f = DiagnosticFilter.from_filetype( opts, [ 'java' ] ) _assert_accepts( f, 'This is a Taco' ) _assert_rejects( f, 'This is a Burrito' ) + +class Level_test(): + + def Level_warnings_test( self ): + opts = { 'quiet_messages' : { 'level': 'warnings' } } + f = DiagnosticFilter.from_filetype( opts, [ 'java' ] ) + + _assert_rejects( f, { 'text': 'This is an unimportant taco', + 'kind': 'WARNING' } ) + _assert_accepts( f, { 'text': 'This taco will be shown', + 'kind': 'ERROR' } ) + + + def Level_errors_test( self ): + opts = { 'quiet_messages' : { 'level': 'errors' } } + f = DiagnosticFilter.from_filetype( opts, [ 'java' ] ) + + _assert_accepts( f, { 'text': 'This is an IMPORTANT taco', + 'kind': 'WARNING' } ) + _assert_rejects( f, { 'text': 'This taco will NOT be shown', + 'kind': 'ERROR' } ) From d274dcd37acea27808cda9d305921db043da01e5 Mon Sep 17 00:00:00 2001 From: dhleong Date: Mon, 10 Oct 2016 14:30:55 -0400 Subject: [PATCH 03/17] Fix test error --- python/ycm/diagnostic_filter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ycm/diagnostic_filter.py b/python/ycm/diagnostic_filter.py index 6c653911..24936dd2 100644 --- a/python/ycm/diagnostic_filter.py +++ b/python/ycm/diagnostic_filter.py @@ -61,7 +61,7 @@ class DiagnosticFilter( object ): @staticmethod def from_filetype( user_options, filetypes ): - base = dict( user_options[ 'quiet_messages' ] ) + base = dict( user_options.get( 'quiet_messages', {} ) ) for filetype in filetypes: type_specific = user_options.get( filetype + '_quiet_messages', {} ) From f15f9f225530eb3ac72e0cb770203aa6e2eea080 Mon Sep 17 00:00:00 2001 From: dhleong Date: Mon, 10 Oct 2016 18:51:18 -0400 Subject: [PATCH 04/17] Fix style/lint issues --- python/ycm/diagnostic_filter.py | 22 ++++++++++----------- python/ycm/diagnostic_interface.py | 7 +++++-- python/ycm/tests/diagnostic_filter_tests.py | 15 +++++++------- 3 files changed, 24 insertions(+), 20 deletions(-) diff --git a/python/ycm/diagnostic_filter.py b/python/ycm/diagnostic_filter.py index 24936dd2..67ac8c38 100644 --- a/python/ycm/diagnostic_filter.py +++ b/python/ycm/diagnostic_filter.py @@ -1,4 +1,4 @@ -# Copyright (C) 2013 Google Inc. +# Copyright (C) 2016 YouCompleteMe contributors # # This file is part of YouCompleteMe. # @@ -23,11 +23,7 @@ from future import standard_library standard_library.install_aliases() from builtins import * # noqa -from future.utils import itervalues, iteritems -from collections import defaultdict, namedtuple -from ycm import vimsupport import re -import vim class DiagnosticFilter( object ): @@ -35,14 +31,14 @@ class DiagnosticFilter( object ): self._filters = [] for filter_type in config.iterkeys(): - wrapper = lambda x: x + wrapper = _AsIs actual_filter_type = filter_type if filter_type[0] == '!': wrapper = _Not filter_type = filter_type[1:] compiler = FILTER_COMPILERS.get( filter_type ) - + if compiler is not None: for filter_config in _ListOf( config[ actual_filter_type ] ): fn = wrapper( compiler( filter_config ) ) @@ -56,7 +52,7 @@ class DiagnosticFilter( object ): if f( diagnostic ): return False - return True + return True @staticmethod @@ -64,18 +60,22 @@ class DiagnosticFilter( object ): base = dict( user_options.get( 'quiet_messages', {} ) ) for filetype in filetypes: - type_specific = user_options.get( filetype + '_quiet_messages', {} ) + type_specific = user_options.get( filetype + '_quiet_messages', {} ) base.update( type_specific ) return DiagnosticFilter( base ) def _ListOf( config_entry ): - if type( config_entry ) == type( [] ): + if isinstance( config_entry, list ): return config_entry return [ config_entry ] +def _AsIs( fn ): + return fn + + def _Not( fn ): def Inverted( diagnostic ): return not fn( diagnostic ) @@ -93,7 +93,7 @@ def _CompileRegex( raw_regex ): def _CompileLevel( level ): - # valid kinds are WARNING and ERROR; + # valid kinds are WARNING and ERROR; # expected input levels are `warnings` and `errors` # NB: we don't validate the input... expected_kind = level.upper()[:-1] diff --git a/python/ycm/diagnostic_interface.py b/python/ycm/diagnostic_interface.py index 089eac2d..e0e349b3 100644 --- a/python/ycm/diagnostic_interface.py +++ b/python/ycm/diagnostic_interface.py @@ -66,8 +66,11 @@ class DiagnosticInterface( object ): def UpdateWithNewDiagnostics( self, diags ): - diag_filter = DiagnosticFilter.from_filetype( self._user_options, vimsupport.CurrentFiletypes() ) - normalized_diags = [ _NormalizeDiagnostic( x ) for x in diags if diag_filter.Accept(x) ] + diag_filter = DiagnosticFilter.from_filetype( + self._user_options, + vimsupport.CurrentFiletypes() ) + normalized_diags = [ _NormalizeDiagnostic( x ) for x in diags + if diag_filter.Accept(x) ] self._buffer_number_to_line_to_diags = _ConvertDiagListToDict( normalized_diags ) diff --git a/python/ycm/tests/diagnostic_filter_tests.py b/python/ycm/tests/diagnostic_filter_tests.py index dc84bcf9..c3126940 100644 --- a/python/ycm/tests/diagnostic_filter_tests.py +++ b/python/ycm/tests/diagnostic_filter_tests.py @@ -1,4 +1,4 @@ -# Copyright (C) 2013 Google Inc. +# Copyright (C) 2016 YouCompleteMe contributors # # This file is part of YouCompleteMe. # @@ -26,20 +26,21 @@ from builtins import * # noqa from ycm.test_utils import MockVimModule MockVimModule() -import os from hamcrest import assert_that, equal_to from ycm.diagnostic_filter import DiagnosticFilter def _assert_accept_equals( filter, text_or_obj, expected ): - if type( text_or_obj ) is not type( {} ): + if not isinstance( text_or_obj, dict ): text_or_obj = { 'text': text_or_obj } assert_that( filter.Accept( text_or_obj ), equal_to( expected ) ) + def _assert_accepts( filter, text ): _assert_accept_equals( filter, text, True ) + def _assert_rejects( filter, text ): _assert_accept_equals( filter, text, False ) @@ -111,9 +112,9 @@ class Level_test(): opts = { 'quiet_messages' : { 'level': 'warnings' } } f = DiagnosticFilter.from_filetype( opts, [ 'java' ] ) - _assert_rejects( f, { 'text': 'This is an unimportant taco', + _assert_rejects( f, { 'text': 'This is an unimportant taco', 'kind': 'WARNING' } ) - _assert_accepts( f, { 'text': 'This taco will be shown', + _assert_accepts( f, { 'text': 'This taco will be shown', 'kind': 'ERROR' } ) @@ -121,7 +122,7 @@ class Level_test(): opts = { 'quiet_messages' : { 'level': 'errors' } } f = DiagnosticFilter.from_filetype( opts, [ 'java' ] ) - _assert_accepts( f, { 'text': 'This is an IMPORTANT taco', + _assert_accepts( f, { 'text': 'This is an IMPORTANT taco', 'kind': 'WARNING' } ) - _assert_rejects( f, { 'text': 'This taco will NOT be shown', + _assert_rejects( f, { 'text': 'This taco will NOT be shown', 'kind': 'ERROR' } ) From ca29f6abe80f0e476d885f428b5e95374b0168e7 Mon Sep 17 00:00:00 2001 From: dhleong Date: Mon, 10 Oct 2016 19:22:38 -0400 Subject: [PATCH 05/17] Verify that an empty list overrides (disables) rules --- python/ycm/tests/diagnostic_filter_tests.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/python/ycm/tests/diagnostic_filter_tests.py b/python/ycm/tests/diagnostic_filter_tests.py index c3126940..4038a175 100644 --- a/python/ycm/tests/diagnostic_filter_tests.py +++ b/python/ycm/tests/diagnostic_filter_tests.py @@ -75,6 +75,17 @@ class ConfigPriority_test(): _assert_accepts( f, 'This is a Burrito' ) + def ConfigPriority_FiletypeDisablesGlobal_test( self ): + # NB: if the filetype doesn't override the global, + # we would reject burrito and accept taco + opts = { 'quiet_messages' : { 'regex': 'taco'}, + 'java_quiet_messages' : { 'regex': [] } } + f = DiagnosticFilter.from_filetype( opts, [ 'java' ] ) + + _assert_accepts( f, 'This is a Taco' ) + _assert_accepts( f, 'This is a Burrito' ) + + class ListOrSingle_test(): # NB: we already test the single config above From abc0dd5e31e62e60795636ffa4cf473c5caa2d0c Mon Sep 17 00:00:00 2001 From: dhleong Date: Mon, 10 Oct 2016 19:31:10 -0400 Subject: [PATCH 06/17] Add some documentation for g:ycm_quiet_messages --- doc/youcompleteme.txt | 98 +++++++++++++++++++++++++++---------------- 1 file changed, 63 insertions(+), 35 deletions(-) diff --git a/doc/youcompleteme.txt b/doc/youcompleteme.txt index 8d32cb21..ec29023a 100644 --- a/doc/youcompleteme.txt +++ b/doc/youcompleteme.txt @@ -88,41 +88,42 @@ Contents ~ 9. The |g:ycm_warning_symbol| option 10. The |g:ycm_enable_diagnostic_signs| option 11. The |g:ycm_enable_diagnostic_highlighting| option - 12. The |g:ycm_echo_current_diagnostic| option - 13. The |g:ycm_always_populate_location_list| option - 14. The |g:ycm_open_loclist_on_ycm_diags| option - 15. The |g:ycm_allow_changing_updatetime| option - 16. The |g:ycm_complete_in_comments| option - 17. The |g:ycm_complete_in_strings| option - 18. The |g:ycm_collect_identifiers_from_comments_and_strings| option - 19. The |g:ycm_collect_identifiers_from_tags_files| option - 20. The |g:ycm_seed_identifiers_with_syntax| option - 21. The |g:ycm_extra_conf_vim_data| option - 22. The |g:ycm_server_python_interpreter| option - 23. The |g:ycm_server_keep_logfiles| option - 24. The |g:ycm_server_log_level| option - 25. The |g:ycm_auto_start_csharp_server| option - 26. The |g:ycm_auto_stop_csharp_server| option - 27. The |g:ycm_csharp_server_port| option - 28. The |g:ycm_csharp_insert_namespace_expr| option - 29. The |g:ycm_add_preview_to_completeopt| option - 30. The |g:ycm_autoclose_preview_window_after_completion| option - 31. The |g:ycm_autoclose_preview_window_after_insertion| option - 32. The |g:ycm_max_diagnostics_to_display| option - 33. The |g:ycm_key_list_select_completion| option - 34. The |g:ycm_key_list_previous_completion| option - 35. The |g:ycm_key_invoke_completion| option - 36. The |g:ycm_key_detailed_diagnostics| option - 37. The |g:ycm_global_ycm_extra_conf| option - 38. The |g:ycm_confirm_extra_conf| option - 39. The |g:ycm_extra_conf_globlist| option - 40. The |g:ycm_filepath_completion_use_working_dir| option - 41. The |g:ycm_semantic_triggers| option - 42. The |g:ycm_cache_omnifunc| option - 43. The |g:ycm_use_ultisnips_completer| option - 44. The |g:ycm_goto_buffer_command| option - 45. The |g:ycm_disable_for_files_larger_than_kb| option - 46. The |g:ycm_python_binary_path| option + 12. The |g:ycm_quiet_messages| option + 13. The |g:ycm_echo_current_diagnostic| option + 14. The |g:ycm_always_populate_location_list| option + 15. The |g:ycm_open_loclist_on_ycm_diags| option + 16. The |g:ycm_allow_changing_updatetime| option + 17. The |g:ycm_complete_in_comments| option + 18. The |g:ycm_complete_in_strings| option + 19. The |g:ycm_collect_identifiers_from_comments_and_strings| option + 20. The |g:ycm_collect_identifiers_from_tags_files| option + 21. The |g:ycm_seed_identifiers_with_syntax| option + 22. The |g:ycm_extra_conf_vim_data| option + 23. The |g:ycm_server_python_interpreter| option + 24. The |g:ycm_server_keep_logfiles| option + 25. The |g:ycm_server_log_level| option + 26. The |g:ycm_auto_start_csharp_server| option + 27. The |g:ycm_auto_stop_csharp_server| option + 28. The |g:ycm_csharp_server_port| option + 29. The |g:ycm_csharp_insert_namespace_expr| option + 30. The |g:ycm_add_preview_to_completeopt| option + 31. The |g:ycm_autoclose_preview_window_after_completion| option + 32. The |g:ycm_autoclose_preview_window_after_insertion| option + 33. The |g:ycm_max_diagnostics_to_display| option + 34. The |g:ycm_key_list_select_completion| option + 35. The |g:ycm_key_list_previous_completion| option + 36. The |g:ycm_key_invoke_completion| option + 37. The |g:ycm_key_detailed_diagnostics| option + 38. The |g:ycm_global_ycm_extra_conf| option + 39. The |g:ycm_confirm_extra_conf| option + 40. The |g:ycm_extra_conf_globlist| option + 41. The |g:ycm_filepath_completion_use_working_dir| option + 42. The |g:ycm_semantic_triggers| option + 43. The |g:ycm_cache_omnifunc| option + 44. The |g:ycm_use_ultisnips_completer| option + 45. The |g:ycm_goto_buffer_command| option + 46. The |g:ycm_disable_for_files_larger_than_kb| option + 47. The |g:ycm_python_binary_path| option 11. FAQ |youcompleteme-faq| 1. I used to be able to 'import vim' in '.ycm_extra_conf.py', but now can't |import-vim| 2. On very rare occasions Vim crashes when I tab through the completion menu |youcompleteme-on-very-rare-occasions-vim-crashes-when-i-tab-through-completion-menu| @@ -2065,6 +2066,33 @@ Default: '1' let g:ycm_enable_diagnostic_highlighting = 1 < ------------------------------------------------------------------------------- +The *g:ycm_quiet_messages* option + +When this option is set, YCM will suppress any diagnostic items that would +otherwise be displayed that match any of the rules provided. The value is a +dictionary which should be set to something like: +> + let g:ycm_quiet_messages = { + \ "!level": "errors", + \ "regex": '\m\[C03\d\d\]' } +< +This option is part of the Syntastic compatibility layer; if the option is not +set, YCM will fall back to the value of the 'g:syntastic_quiet_messages' +option before using this option's default. + +See |syntastic_quiet_messages| for more details, as this option follows that +one. Currently, only "level" and "regex" rules are supported; unsupported rule +types are ignored. + +As in Syntastic, there are also filetype-specific variants of this option, which +will be used in preference to this if they exist. +They are named |'ycm__quiet_messages'|. + +Default: '{}' +> + let g:ycm_quiet_messages = {} +< +------------------------------------------------------------------------------- The *g:ycm_echo_current_diagnostic* option When this option is set, YCM will echo the text of the diagnostic present on From 4c53e5d006ef6307beb8991e0747f0b131cca9f1 Mon Sep 17 00:00:00 2001 From: dhleong Date: Mon, 10 Oct 2016 19:50:34 -0400 Subject: [PATCH 07/17] Use future.utils iterkeys; fix some more style issues --- python/ycm/diagnostic_filter.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/python/ycm/diagnostic_filter.py b/python/ycm/diagnostic_filter.py index 67ac8c38..4c6a1c4d 100644 --- a/python/ycm/diagnostic_filter.py +++ b/python/ycm/diagnostic_filter.py @@ -23,6 +23,7 @@ from future import standard_library standard_library.install_aliases() from builtins import * # noqa +from future.utils import iterkeys import re @@ -30,13 +31,13 @@ class DiagnosticFilter( object ): def __init__( self, config ): self._filters = [] - for filter_type in config.iterkeys(): + for filter_type in iterkeys( config ): wrapper = _AsIs actual_filter_type = filter_type - if filter_type[0] == '!': + if filter_type[ 0 ] == '!': wrapper = _Not - filter_type = filter_type[1:] + filter_type = filter_type[ 1 : ] compiler = FILTER_COMPILERS.get( filter_type ) if compiler is not None: @@ -96,7 +97,7 @@ def _CompileLevel( level ): # valid kinds are WARNING and ERROR; # expected input levels are `warnings` and `errors` # NB: we don't validate the input... - expected_kind = level.upper()[:-1] + expected_kind = level.upper()[ : -1 ] def FilterLevel( diagnostic ): print( diagnostic, 'matches?', expected_kind ) From 68cf0220e50e1de93d1a5e1397b1b58b28cb0dcb Mon Sep 17 00:00:00 2001 From: dhleong Date: Mon, 10 Oct 2016 19:51:17 -0400 Subject: [PATCH 08/17] Remove unnecessary print() --- python/ycm/diagnostic_filter.py | 1 - 1 file changed, 1 deletion(-) diff --git a/python/ycm/diagnostic_filter.py b/python/ycm/diagnostic_filter.py index 4c6a1c4d..1d312338 100644 --- a/python/ycm/diagnostic_filter.py +++ b/python/ycm/diagnostic_filter.py @@ -100,7 +100,6 @@ def _CompileLevel( level ): expected_kind = level.upper()[ : -1 ] def FilterLevel( diagnostic ): - print( diagnostic, 'matches?', expected_kind ) return diagnostic[ 'kind' ] == expected_kind return FilterLevel From 3869830a650ec48ce15761a242d04a6e5a3b750a Mon Sep 17 00:00:00 2001 From: dhleong Date: Tue, 11 Oct 2016 21:30:35 -0400 Subject: [PATCH 09/17] Rename method for clarity and tweak style --- python/ycm/diagnostic_filter.py | 8 ++++---- python/ycm/diagnostic_interface.py | 2 +- python/ycm/tests/diagnostic_filter_tests.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/python/ycm/diagnostic_filter.py b/python/ycm/diagnostic_filter.py index 1d312338..77de626b 100644 --- a/python/ycm/diagnostic_filter.py +++ b/python/ycm/diagnostic_filter.py @@ -46,8 +46,8 @@ class DiagnosticFilter( object ): self._filters.append( fn ) - def Accept( self, diagnostic ): - # NB: we Accept() the diagnostic ONLY if + def IsAllowed( self, diagnostic ): + # NOTE: a diagnostic IsAllowed() ONLY if # no filters match it for f in self._filters: if f( diagnostic ): @@ -105,5 +105,5 @@ def _CompileLevel( level ): return FilterLevel -FILTER_COMPILERS = { 'regex' : _CompileRegex, - 'level' : _CompileLevel } +FILTER_COMPILERS = { 'regex' : _CompileRegex, + 'level' : _CompileLevel } diff --git a/python/ycm/diagnostic_interface.py b/python/ycm/diagnostic_interface.py index e0e349b3..7866ba23 100644 --- a/python/ycm/diagnostic_interface.py +++ b/python/ycm/diagnostic_interface.py @@ -70,7 +70,7 @@ class DiagnosticInterface( object ): self._user_options, vimsupport.CurrentFiletypes() ) normalized_diags = [ _NormalizeDiagnostic( x ) for x in diags - if diag_filter.Accept(x) ] + if diag_filter.IsAllowed( x ) ] self._buffer_number_to_line_to_diags = _ConvertDiagListToDict( normalized_diags ) diff --git a/python/ycm/tests/diagnostic_filter_tests.py b/python/ycm/tests/diagnostic_filter_tests.py index 4038a175..518a973c 100644 --- a/python/ycm/tests/diagnostic_filter_tests.py +++ b/python/ycm/tests/diagnostic_filter_tests.py @@ -34,7 +34,7 @@ def _assert_accept_equals( filter, text_or_obj, expected ): if not isinstance( text_or_obj, dict ): text_or_obj = { 'text': text_or_obj } - assert_that( filter.Accept( text_or_obj ), equal_to( expected ) ) + assert_that( filter.IsAllowed( text_or_obj ), equal_to( expected ) ) def _assert_accepts( filter, text ): From bb793826b6aef9d01cf2f35407206d6d5c86ed6e Mon Sep 17 00:00:00 2001 From: dhleong Date: Sun, 16 Oct 2016 06:43:58 -1000 Subject: [PATCH 10/17] Refactor filter spec to filter_diagnostics; update tests, README --- README.md | 32 ++++++ plugin/youcompleteme.vim | 4 - python/ycm/diagnostic_filter.py | 47 ++++---- python/ycm/tests/diagnostic_filter_tests.py | 113 ++++++++++---------- 4 files changed, 112 insertions(+), 84 deletions(-) diff --git a/README.md b/README.md index 7feda0c6..7551d863 100644 --- a/README.md +++ b/README.md @@ -1803,6 +1803,38 @@ Default: `1` let g:ycm_echo_current_diagnostic = 1 ``` +### The `g:ycm_filter_diagnostics` option + +This option controls which diagnostics will be rendered by YCM. This option holds +a dictionary of key-values, where the keys are Vim's filetype strings delimited by +commas and values are dictionaries describing the filter. + +A filter is a dictionary of key-values, where the keys are the type of filter, +and the value is a list of arguments to that filter. In the case of just a single +item in the list, you may omit the brackets and just provide the argument directly. +If any filter matches a diagnostic, it will be dropped and YCM will not render it. + +The following filter types are supported: + +- "regex": Accepts a string [regular expression][python-re]. This type matches +when the regex (treated as case-insensitive) is found in the diagnostic text. +- "level": Accepts a string level, either "warning" or "error." This type matches +when the diagnostic has the same level. + +NOTE: The regex syntax is **NOT** Vim's, it's [Python's][python-re]. + +Default: `{}` + +```viml +let g:ycm_filter_diagnostics = { + \ "java": { + \ "regex": [ ".*taco.*", ... ], + \ "level": "error", + \ ... + \ } + \ } +``` + ### The `g:ycm_always_populate_location_list` option When this option is set, YCM will populate the location list automatically every diff --git a/plugin/youcompleteme.vim b/plugin/youcompleteme.vim index ec5399c9..268a7274 100644 --- a/plugin/youcompleteme.vim +++ b/plugin/youcompleteme.vim @@ -121,10 +121,6 @@ let g:ycm_warning_symbol = \ get( g:, 'ycm_warning_symbol', \ get( g:, 'syntastic_warning_symbol', '>>' ) ) -let g:ycm_quiet_messages = - \ get( g:, 'ycm_quiet_messages', - \ get( g:, 'syntastic_quiet_messages', {} ) ) - let g:ycm_goto_buffer_command = \ get( g:, 'ycm_goto_buffer_command', 'same-buffer' ) diff --git a/python/ycm/diagnostic_filter.py b/python/ycm/diagnostic_filter.py index 77de626b..623b9ad0 100644 --- a/python/ycm/diagnostic_filter.py +++ b/python/ycm/diagnostic_filter.py @@ -23,7 +23,7 @@ from future import standard_library standard_library.install_aliases() from builtins import * # noqa -from future.utils import iterkeys +from future.utils import iterkeys, iteritems import re @@ -32,23 +32,18 @@ class DiagnosticFilter( object ): self._filters = [] for filter_type in iterkeys( config ): - wrapper = _AsIs actual_filter_type = filter_type - - if filter_type[ 0 ] == '!': - wrapper = _Not - filter_type = filter_type[ 1 : ] compiler = FILTER_COMPILERS.get( filter_type ) if compiler is not None: for filter_config in _ListOf( config[ actual_filter_type ] ): - fn = wrapper( compiler( filter_config ) ) + fn = compiler( filter_config ) self._filters.append( fn ) def IsAllowed( self, diagnostic ): # NOTE: a diagnostic IsAllowed() ONLY if - # no filters match it + # NO filters match it for f in self._filters: if f( diagnostic ): return False @@ -58,30 +53,34 @@ class DiagnosticFilter( object ): @staticmethod def from_filetype( user_options, filetypes ): - base = dict( user_options.get( 'quiet_messages', {} ) ) + spec = {} + all_filters = dict( user_options.get( 'filter_diagnostics', {} ) ) + for typeSpec, filterValue in iteritems( dict( all_filters ) ): + if typeSpec.find(',') != -1: + for ft in typeSpec.split(','): + all_filters[ ft ] = filterValue for filetype in filetypes: - type_specific = user_options.get( filetype + '_quiet_messages', {} ) - base.update( type_specific ) - return DiagnosticFilter( base ) + type_specific = all_filters.get( filetype, {} ) + spec = _Merge( spec, type_specific ) + return DiagnosticFilter( spec ) def _ListOf( config_entry ): if isinstance( config_entry, list ): return config_entry + if config_entry is None: + return [] + return [ config_entry ] -def _AsIs( fn ): - return fn +def _Merge( into, other ): + for k in iterkeys( other ): + into[k] = _ListOf( into.get( k ) ) + _ListOf( other[ k ] ) - -def _Not( fn ): - def Inverted( diagnostic ): - return not fn( diagnostic ) - - return Inverted + return into def _CompileRegex( raw_regex ): @@ -95,9 +94,9 @@ def _CompileRegex( raw_regex ): def _CompileLevel( level ): # valid kinds are WARNING and ERROR; - # expected input levels are `warnings` and `errors` + # expected input levels are `warning` and `error` # NB: we don't validate the input... - expected_kind = level.upper()[ : -1 ] + expected_kind = level.upper() def FilterLevel( diagnostic ): return diagnostic[ 'kind' ] == expected_kind @@ -105,5 +104,5 @@ def _CompileLevel( level ): return FilterLevel -FILTER_COMPILERS = { 'regex' : _CompileRegex, - 'level' : _CompileLevel } +FILTER_COMPILERS = { 'regex' : _CompileRegex, + 'level' : _CompileLevel } diff --git a/python/ycm/tests/diagnostic_filter_tests.py b/python/ycm/tests/diagnostic_filter_tests.py index 518a973c..3c6109ca 100644 --- a/python/ycm/tests/diagnostic_filter_tests.py +++ b/python/ycm/tests/diagnostic_filter_tests.py @@ -45,45 +45,16 @@ def _assert_rejects( filter, text ): _assert_accept_equals( filter, text, False ) -class ConfigPriority_test(): - - def ConfigPriority_Global_test( self ): - opts = { 'quiet_messages': { 'regex': 'taco' } } - f = DiagnosticFilter.from_filetype( opts, [ 'java' ] ) - - _assert_rejects( f, 'This is a Taco' ) - _assert_accepts( f, 'This is a Burrito' ) +def _JavaFilter( config ): + return { 'filter_diagnostics' : { 'java': config } } - def ConfigPriority_Filetype_test( self ): - opts = { 'quiet_messages' : {}, - 'java_quiet_messages' : { 'regex': 'taco' } } - f = DiagnosticFilter.from_filetype( opts, [ 'java' ] ) +def RegexFilter_test(): + opts = _JavaFilter( { 'regex' : 'taco' } ) + f = DiagnosticFilter.from_filetype( opts, [ 'java' ] ) - _assert_rejects( f, 'This is a Taco' ) - _assert_accepts( f, 'This is a Burrito' ) - - - def ConfigPriority_FiletypeOverridesGlobal_test( self ): - # NB: if the filetype doesn't override the global, - # we would reject burrito and accept taco - opts = { 'quiet_messages' : { 'regex': 'burrito'}, - 'java_quiet_messages' : { 'regex': 'taco' } } - f = DiagnosticFilter.from_filetype( opts, [ 'java' ] ) - - _assert_rejects( f, 'This is a Taco' ) - _assert_accepts( f, 'This is a Burrito' ) - - - def ConfigPriority_FiletypeDisablesGlobal_test( self ): - # NB: if the filetype doesn't override the global, - # we would reject burrito and accept taco - opts = { 'quiet_messages' : { 'regex': 'taco'}, - 'java_quiet_messages' : { 'regex': [] } } - f = DiagnosticFilter.from_filetype( opts, [ 'java' ] ) - - _assert_accepts( f, 'This is a Taco' ) - _assert_accepts( f, 'This is a Burrito' ) + _assert_rejects( f, 'This is a Taco' ) + _assert_accepts( f, 'This is a Burrito' ) class ListOrSingle_test(): @@ -92,7 +63,7 @@ class ListOrSingle_test(): def ListOrSingle_SingleList_test( self ): # NB: if the filetype doesn't override the global, # we would reject burrito and accept taco - opts = { 'quiet_messages' : { 'regex': [ 'taco' ] } } + opts = _JavaFilter( { 'regex' : [ 'taco' ] } ) f = DiagnosticFilter.from_filetype( opts, [ 'java' ] ) _assert_rejects( f, 'This is a Taco' ) @@ -102,38 +73,68 @@ class ListOrSingle_test(): def ListOrSingle_MultiList_test( self ): # NB: if the filetype doesn't override the global, # we would reject burrito and accept taco - opts = { 'quiet_messages' : { 'regex': [ 'taco', 'burrito' ] } } + opts = _JavaFilter( { 'regex' : [ 'taco', 'burrito' ] } ) f = DiagnosticFilter.from_filetype( opts, [ 'java' ] ) _assert_rejects( f, 'This is a Taco' ) _assert_rejects( f, 'This is a Burrito' ) -def Invert_test(): - opts = { 'quiet_messages' : { '!regex': 'taco' } } - f = DiagnosticFilter.from_filetype( opts, [ 'java' ] ) - - _assert_accepts( f, 'This is a Taco' ) - _assert_rejects( f, 'This is a Burrito' ) - - class Level_test(): def Level_warnings_test( self ): - opts = { 'quiet_messages' : { 'level': 'warnings' } } + opts = _JavaFilter( { 'level' : 'warning' } ) f = DiagnosticFilter.from_filetype( opts, [ 'java' ] ) - _assert_rejects( f, { 'text': 'This is an unimportant taco', - 'kind': 'WARNING' } ) - _assert_accepts( f, { 'text': 'This taco will be shown', - 'kind': 'ERROR' } ) + _assert_rejects( f, { 'text' : 'This is an unimportant taco', + 'kind' : 'WARNING' } ) + _assert_accepts( f, { 'text' : 'This taco will be shown', + 'kind' : 'ERROR' } ) def Level_errors_test( self ): - opts = { 'quiet_messages' : { 'level': 'errors' } } + opts = _JavaFilter( { 'level' : 'error' } ) f = DiagnosticFilter.from_filetype( opts, [ 'java' ] ) - _assert_accepts( f, { 'text': 'This is an IMPORTANT taco', - 'kind': 'WARNING' } ) - _assert_rejects( f, { 'text': 'This taco will NOT be shown', - 'kind': 'ERROR' } ) + _assert_accepts( f, { 'text' : 'This is an IMPORTANT taco', + 'kind' : 'WARNING' } ) + _assert_rejects( f, { 'text' : 'This taco will NOT be shown', + 'kind' : 'ERROR' } ) + + +def MultipleFilterTypesTypeTest_test(): + + opts = _JavaFilter( { 'regex' : '.*taco.*', + 'level' : 'warning' } ) + f = DiagnosticFilter.from_filetype( opts, [ 'java' ] ) + + _assert_rejects( f, { 'text' : 'This is an unimportant taco', + 'kind' : 'WARNING' } ) + _assert_rejects( f, { 'text' : 'This taco will NOT be shown', + 'kind' : 'ERROR' } ) + _assert_accepts( f, { 'text' : 'This burrito WILL be shown', + 'kind' : 'ERROR' } ) + + +def MergeMultipleFiletypes_test(): + + opts = { 'filter_diagnostics' : { + 'java' : { 'regex' : '.*taco.*' }, + 'xml' : { 'regex' : '.*burrito.*' } } } + + f = DiagnosticFilter.from_filetype( opts, [ 'java', 'xml' ] ) + + _assert_rejects( f, 'This is a Taco' ) + _assert_rejects( f, 'This is a Burrito' ) + _assert_accepts( f, 'This is some Nachos' ) + + +def CommaSeparatedFiletypes_test(): + + opts = { 'filter_diagnostics' : { + 'java,c,cs' : { 'regex' : '.*taco.*' } } } + + f = DiagnosticFilter.from_filetype( opts, [ 'cs' ] ) + + _assert_rejects( f, 'This is a Taco' ) + _assert_accepts( f, 'This is a Burrito' ) From 18a25d45b308ecce52df7eef0d543ad94588a1ad Mon Sep 17 00:00:00 2001 From: dhleong Date: Sat, 22 Oct 2016 11:58:57 -0400 Subject: [PATCH 11/17] Style refactor and cleanup --- python/ycm/diagnostic_filter.py | 24 ++++----- python/ycm/tests/diagnostic_filter_tests.py | 60 +++++++++------------ 2 files changed, 35 insertions(+), 49 deletions(-) diff --git a/python/ycm/diagnostic_filter.py b/python/ycm/diagnostic_filter.py index 623b9ad0..a69a3e7d 100644 --- a/python/ycm/diagnostic_filter.py +++ b/python/ycm/diagnostic_filter.py @@ -32,20 +32,18 @@ class DiagnosticFilter( object ): self._filters = [] for filter_type in iterkeys( config ): - actual_filter_type = filter_type compiler = FILTER_COMPILERS.get( filter_type ) if compiler is not None: - for filter_config in _ListOf( config[ actual_filter_type ] ): - fn = compiler( filter_config ) - self._filters.append( fn ) + for filter_config in _ListOf( config[ filter_type ] ): + compiledFilter = compiler( filter_config ) + self._filters.append( compiledFilter ) def IsAllowed( self, diagnostic ): - # NOTE: a diagnostic IsAllowed() ONLY if - # NO filters match it - for f in self._filters: - if f( diagnostic ): + # NOTE: a diagnostic IsAllowed() ONLY if NO filters match it + for filterMatches in self._filters: + if filterMatches( diagnostic ): return False return True @@ -57,8 +55,8 @@ class DiagnosticFilter( object ): all_filters = dict( user_options.get( 'filter_diagnostics', {} ) ) for typeSpec, filterValue in iteritems( dict( all_filters ) ): if typeSpec.find(',') != -1: - for ft in typeSpec.split(','): - all_filters[ ft ] = filterValue + for filetype in typeSpec.split(','): + all_filters[ filetype ] = filterValue for filetype in filetypes: type_specific = all_filters.get( filetype, {} ) @@ -77,8 +75,8 @@ def _ListOf( config_entry ): def _Merge( into, other ): - for k in iterkeys( other ): - into[k] = _ListOf( into.get( k ) ) + _ListOf( other[ k ] ) + for key in iterkeys( other ): + into[ key ] = _ListOf( into.get( key ) ) + _ListOf( other[ key ] ) return into @@ -95,7 +93,7 @@ def _CompileRegex( raw_regex ): def _CompileLevel( level ): # valid kinds are WARNING and ERROR; # expected input levels are `warning` and `error` - # NB: we don't validate the input... + # NOTE: we don't validate the input... expected_kind = level.upper() def FilterLevel( diagnostic ): diff --git a/python/ycm/tests/diagnostic_filter_tests.py b/python/ycm/tests/diagnostic_filter_tests.py index 3c6109ca..9741e0c9 100644 --- a/python/ycm/tests/diagnostic_filter_tests.py +++ b/python/ycm/tests/diagnostic_filter_tests.py @@ -23,9 +23,6 @@ from future import standard_library standard_library.install_aliases() from builtins import * # noqa -from ycm.test_utils import MockVimModule -MockVimModule() - from hamcrest import assert_that, equal_to from ycm.diagnostic_filter import DiagnosticFilter @@ -57,49 +54,40 @@ def RegexFilter_test(): _assert_accepts( f, 'This is a Burrito' ) -class ListOrSingle_test(): - # NB: we already test the single config above +def RegexSingleList_test(): + opts = _JavaFilter( { 'regex' : [ 'taco' ] } ) + f = DiagnosticFilter.from_filetype( opts, [ 'java' ] ) - def ListOrSingle_SingleList_test( self ): - # NB: if the filetype doesn't override the global, - # we would reject burrito and accept taco - opts = _JavaFilter( { 'regex' : [ 'taco' ] } ) - f = DiagnosticFilter.from_filetype( opts, [ 'java' ] ) - - _assert_rejects( f, 'This is a Taco' ) - _assert_accepts( f, 'This is a Burrito' ) + _assert_rejects( f, 'This is a Taco' ) + _assert_accepts( f, 'This is a Burrito' ) - def ListOrSingle_MultiList_test( self ): - # NB: if the filetype doesn't override the global, - # we would reject burrito and accept taco - opts = _JavaFilter( { 'regex' : [ 'taco', 'burrito' ] } ) - f = DiagnosticFilter.from_filetype( opts, [ 'java' ] ) +def RegexMultiList_test(): + opts = _JavaFilter( { 'regex' : [ 'taco', 'burrito' ] } ) + f = DiagnosticFilter.from_filetype( opts, [ 'java' ] ) - _assert_rejects( f, 'This is a Taco' ) - _assert_rejects( f, 'This is a Burrito' ) + _assert_rejects( f, 'This is a Taco' ) + _assert_rejects( f, 'This is a Burrito' ) -class Level_test(): +def LevelWarnings_test(): + opts = _JavaFilter( { 'level' : 'warning' } ) + f = DiagnosticFilter.from_filetype( opts, [ 'java' ] ) - def Level_warnings_test( self ): - opts = _JavaFilter( { 'level' : 'warning' } ) - f = DiagnosticFilter.from_filetype( opts, [ 'java' ] ) - - _assert_rejects( f, { 'text' : 'This is an unimportant taco', - 'kind' : 'WARNING' } ) - _assert_accepts( f, { 'text' : 'This taco will be shown', - 'kind' : 'ERROR' } ) + _assert_rejects( f, { 'text' : 'This is an unimportant taco', + 'kind' : 'WARNING' } ) + _assert_accepts( f, { 'text' : 'This taco will be shown', + 'kind' : 'ERROR' } ) - def Level_errors_test( self ): - opts = _JavaFilter( { 'level' : 'error' } ) - f = DiagnosticFilter.from_filetype( opts, [ 'java' ] ) +def LevelErrors_test(): + opts = _JavaFilter( { 'level' : 'error' } ) + f = DiagnosticFilter.from_filetype( opts, [ 'java' ] ) - _assert_accepts( f, { 'text' : 'This is an IMPORTANT taco', - 'kind' : 'WARNING' } ) - _assert_rejects( f, { 'text' : 'This taco will NOT be shown', - 'kind' : 'ERROR' } ) + _assert_accepts( f, { 'text' : 'This is an IMPORTANT taco', + 'kind' : 'WARNING' } ) + _assert_rejects( f, { 'text' : 'This taco will NOT be shown', + 'kind' : 'ERROR' } ) def MultipleFilterTypesTypeTest_test(): From b8280c7b1938486eab1d44bb8765726c93136152 Mon Sep 17 00:00:00 2001 From: dhleong Date: Sat, 22 Oct 2016 12:04:01 -0400 Subject: [PATCH 12/17] Rename diagnostic_filter_tests -> diagnostic_filter_test --- .../{diagnostic_filter_tests.py => diagnostic_filter_test.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename python/ycm/tests/{diagnostic_filter_tests.py => diagnostic_filter_test.py} (100%) diff --git a/python/ycm/tests/diagnostic_filter_tests.py b/python/ycm/tests/diagnostic_filter_test.py similarity index 100% rename from python/ycm/tests/diagnostic_filter_tests.py rename to python/ycm/tests/diagnostic_filter_test.py From 7978215bca88bc58018fcfb379d2418b7b61867b Mon Sep 17 00:00:00 2001 From: dhleong Date: Sat, 22 Oct 2016 18:37:13 -0400 Subject: [PATCH 13/17] Refactor filter to not be initialized every time We eagerly compile all the filters up front, then gather the compiled filters into a DiagnosticFilter lazily, caching the result to avoid garbage lists. --- python/ycm/diagnostic_filter.py | 101 +++++++++++++++------ python/ycm/diagnostic_interface.py | 18 ++-- python/ycm/tests/diagnostic_filter_test.py | 32 +++++-- 3 files changed, 110 insertions(+), 41 deletions(-) diff --git a/python/ycm/diagnostic_filter.py b/python/ycm/diagnostic_filter.py index a69a3e7d..9a68824b 100644 --- a/python/ycm/diagnostic_filter.py +++ b/python/ycm/diagnostic_filter.py @@ -24,20 +24,19 @@ standard_library.install_aliases() from builtins import * # noqa from future.utils import iterkeys, iteritems +from ycm import vimsupport import re class DiagnosticFilter( object ): - def __init__( self, config ): - self._filters = [] + def __init__( self, config_or_filters ): + if isinstance( config_or_filters, list ): + self._filters = config_or_filters + print( 'NewFilter', config_or_filters) - for filter_type in iterkeys( config ): - compiler = FILTER_COMPILERS.get( filter_type ) - - if compiler is not None: - for filter_config in _ListOf( config[ filter_type ] ): - compiledFilter = compiler( filter_config ) - self._filters.append( compiledFilter ) + else: + self._filters = _CompileFilters( config_or_filters ) + print( 'CompileFilters', config_or_filters) def IsAllowed( self, diagnostic ): @@ -49,19 +48,59 @@ class DiagnosticFilter( object ): return True - @staticmethod - def from_filetype( user_options, filetypes ): - spec = {} - all_filters = dict( user_options.get( 'filter_diagnostics', {} ) ) - for typeSpec, filterValue in iteritems( dict( all_filters ) ): - if typeSpec.find(',') != -1: - for filetype in typeSpec.split(','): - all_filters[ filetype ] = filterValue + def SubsetForTypes( self, filetypes ): + """Return a sub-filter limited to the given filetypes""" + # NOTE: actually, this class is already filtered + return self + + @staticmethod + def CreateFromOptions( user_options ): + all_filters = dict( user_options.get( 'filter_diagnostics', {} ) ) + compiled_by_type = {} + for type_spec, filter_value in iteritems( dict( all_filters ) ): + filetypes = [ type_spec ] + if type_spec.find( ',' ) != -1: + filetypes = type_spec.split( ',' ) + for filetype in filetypes: + compiled_by_type[ filetype ] = _CompileFilters( filter_value ) + + return _MasterDiagnosticFilter( compiled_by_type ) + + +class _MasterDiagnosticFilter( object ): + + def __init__( self, all_filters ): + self._all_filters = all_filters + self._cache = {} + + + def IsAllowed( self, diagnostic ): + # NOTE: in this class's implementation, we ask vimsupport for + # the current filetypes and delegate automatically; it is probably, + # more efficient, however, to call SubsetForTypes() and reuse + # the returned DiagnosticFilter if it will be checked repeatedly. + filetypes = vimsupport.CurrentFiletypes() + return self.SubsetForTypes( filetypes ).IsAllowed( diagnostic ) + + + def SubsetForTypes( self, filetypes ): + # check cache + cache_key = ','.join( filetypes ) + cached = self._cache.get( cache_key ) + if cached is not None: + return cached + + # build a new DiagnosticFilter mergin all filters + # for the provided filetypes + spec = [] for filetype in filetypes: - type_specific = all_filters.get( filetype, {} ) - spec = _Merge( spec, type_specific ) - return DiagnosticFilter( spec ) + type_specific = self._all_filters.get( filetype, [] ) + spec.extend( type_specific ) + + new_filter = DiagnosticFilter( spec ) + self._cache[ cache_key ] = new_filter + return new_filter def _ListOf( config_entry ): @@ -74,13 +113,6 @@ def _ListOf( config_entry ): return [ config_entry ] -def _Merge( into, other ): - for key in iterkeys( other ): - into[ key ] = _ListOf( into.get( key ) ) + _ListOf( other[ key ] ) - - return into - - def _CompileRegex( raw_regex ): pattern = re.compile( raw_regex, re.IGNORECASE ) @@ -104,3 +136,18 @@ def _CompileLevel( level ): FILTER_COMPILERS = { 'regex' : _CompileRegex, 'level' : _CompileLevel } + + +def _CompileFilters( config ): + """Given a filter config dictionary, return a list of compiled filters""" + filters = [] + + for filter_type in iterkeys( config ): + compiler = FILTER_COMPILERS.get( filter_type ) + + if compiler is not None: + for filter_config in _ListOf( config[ filter_type ] ): + compiledFilter = compiler( filter_config ) + filters.append( compiledFilter ) + + return filters diff --git a/python/ycm/diagnostic_interface.py b/python/ycm/diagnostic_interface.py index 7866ba23..90385a33 100644 --- a/python/ycm/diagnostic_interface.py +++ b/python/ycm/diagnostic_interface.py @@ -33,6 +33,7 @@ import vim class DiagnosticInterface( object ): def __init__( self, user_options ): self._user_options = user_options + self._diag_filter = DiagnosticFilter.CreateFromOptions( user_options ) # Line and column numbers are 1-based self._buffer_number_to_line_to_diags = defaultdict( lambda: defaultdict( list ) ) @@ -62,15 +63,13 @@ class DiagnosticInterface( object ): def PopulateLocationList( self, diags ): vimsupport.SetLocationList( - vimsupport.ConvertDiagnosticsToQfList( diags ) ) + vimsupport.ConvertDiagnosticsToQfList( + self._ApplyDiagnosticFilter( diags ) ) ) def UpdateWithNewDiagnostics( self, diags ): - diag_filter = DiagnosticFilter.from_filetype( - self._user_options, - vimsupport.CurrentFiletypes() ) - normalized_diags = [ _NormalizeDiagnostic( x ) for x in diags - if diag_filter.IsAllowed( x ) ] + normalized_diags = [ _NormalizeDiagnostic( x ) for x in + self._ApplyDiagnosticFilter( diags ) ] self._buffer_number_to_line_to_diags = _ConvertDiagListToDict( normalized_diags ) @@ -86,6 +85,13 @@ class DiagnosticInterface( object ): if self._user_options[ 'always_populate_location_list' ]: self.PopulateLocationList( normalized_diags ) + + def _ApplyDiagnosticFilter( self, diags ): + filetypes = vimsupport.CurrentFiletypes() + diag_filter = self._diag_filter.SubsetForTypes( filetypes ) + return filter( diag_filter.IsAllowed, diags ) + + def _EchoDiagnosticForLine( self, line_num ): buffer_num = vim.current.buffer.number diags = self._buffer_number_to_line_to_diags[ buffer_num ][ line_num ] diff --git a/python/ycm/tests/diagnostic_filter_test.py b/python/ycm/tests/diagnostic_filter_test.py index 9741e0c9..c73e6127 100644 --- a/python/ycm/tests/diagnostic_filter_test.py +++ b/python/ycm/tests/diagnostic_filter_test.py @@ -24,6 +24,10 @@ standard_library.install_aliases() from builtins import * # noqa from hamcrest import assert_that, equal_to + +from ycm.test_utils import MockVimModule +MockVimModule() + from ycm.diagnostic_filter import DiagnosticFilter @@ -46,9 +50,13 @@ def _JavaFilter( config ): return { 'filter_diagnostics' : { 'java': config } } +def _CreateFilterForTypes( opts, types ): + return DiagnosticFilter.CreateFromOptions( opts ).SubsetForTypes( types ) + + def RegexFilter_test(): opts = _JavaFilter( { 'regex' : 'taco' } ) - f = DiagnosticFilter.from_filetype( opts, [ 'java' ] ) + f = _CreateFilterForTypes( opts, [ 'java' ] ) _assert_rejects( f, 'This is a Taco' ) _assert_accepts( f, 'This is a Burrito' ) @@ -56,7 +64,7 @@ def RegexFilter_test(): def RegexSingleList_test(): opts = _JavaFilter( { 'regex' : [ 'taco' ] } ) - f = DiagnosticFilter.from_filetype( opts, [ 'java' ] ) + f = _CreateFilterForTypes( opts, [ 'java' ] ) _assert_rejects( f, 'This is a Taco' ) _assert_accepts( f, 'This is a Burrito' ) @@ -64,15 +72,23 @@ def RegexSingleList_test(): def RegexMultiList_test(): opts = _JavaFilter( { 'regex' : [ 'taco', 'burrito' ] } ) - f = DiagnosticFilter.from_filetype( opts, [ 'java' ] ) + f = _CreateFilterForTypes( opts, [ 'java' ] ) _assert_rejects( f, 'This is a Taco' ) _assert_rejects( f, 'This is a Burrito' ) +def RegexNotFiltered_test(): + opts = _JavaFilter( { 'regex' : 'taco' } ) + f = _CreateFilterForTypes( opts, [ 'cs' ] ) + + _assert_accepts( f, 'This is a Taco' ) + _assert_accepts( f, 'This is a Burrito' ) + + def LevelWarnings_test(): opts = _JavaFilter( { 'level' : 'warning' } ) - f = DiagnosticFilter.from_filetype( opts, [ 'java' ] ) + f = _CreateFilterForTypes( opts, [ 'java' ] ) _assert_rejects( f, { 'text' : 'This is an unimportant taco', 'kind' : 'WARNING' } ) @@ -82,7 +98,7 @@ def LevelWarnings_test(): def LevelErrors_test(): opts = _JavaFilter( { 'level' : 'error' } ) - f = DiagnosticFilter.from_filetype( opts, [ 'java' ] ) + f = _CreateFilterForTypes( opts, [ 'java' ] ) _assert_accepts( f, { 'text' : 'This is an IMPORTANT taco', 'kind' : 'WARNING' } ) @@ -94,7 +110,7 @@ def MultipleFilterTypesTypeTest_test(): opts = _JavaFilter( { 'regex' : '.*taco.*', 'level' : 'warning' } ) - f = DiagnosticFilter.from_filetype( opts, [ 'java' ] ) + f = _CreateFilterForTypes( opts, [ 'java' ] ) _assert_rejects( f, { 'text' : 'This is an unimportant taco', 'kind' : 'WARNING' } ) @@ -110,7 +126,7 @@ def MergeMultipleFiletypes_test(): 'java' : { 'regex' : '.*taco.*' }, 'xml' : { 'regex' : '.*burrito.*' } } } - f = DiagnosticFilter.from_filetype( opts, [ 'java', 'xml' ] ) + f = _CreateFilterForTypes( opts, [ 'java', 'xml' ] ) _assert_rejects( f, 'This is a Taco' ) _assert_rejects( f, 'This is a Burrito' ) @@ -122,7 +138,7 @@ def CommaSeparatedFiletypes_test(): opts = { 'filter_diagnostics' : { 'java,c,cs' : { 'regex' : '.*taco.*' } } } - f = DiagnosticFilter.from_filetype( opts, [ 'cs' ] ) + f = _CreateFilterForTypes( opts, [ 'cs' ] ) _assert_rejects( f, 'This is a Taco' ) _assert_accepts( f, 'This is a Burrito' ) From e2852a8b2b8419acc453950e9d60e9187dd7d54c Mon Sep 17 00:00:00 2001 From: dhleong Date: Sat, 22 Oct 2016 23:33:43 -0400 Subject: [PATCH 14/17] Update to use appropriate test_utils package after rebase --- python/ycm/tests/diagnostic_filter_test.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/python/ycm/tests/diagnostic_filter_test.py b/python/ycm/tests/diagnostic_filter_test.py index c73e6127..c24e4e0b 100644 --- a/python/ycm/tests/diagnostic_filter_test.py +++ b/python/ycm/tests/diagnostic_filter_test.py @@ -23,11 +23,10 @@ from future import standard_library standard_library.install_aliases() from builtins import * # noqa -from hamcrest import assert_that, equal_to - -from ycm.test_utils import MockVimModule +from ycm.tests.test_utils import MockVimModule MockVimModule() +from hamcrest import assert_that, equal_to from ycm.diagnostic_filter import DiagnosticFilter From 13fda74cc1841d1fb8f29592bacf1ee4abdc7f61 Mon Sep 17 00:00:00 2001 From: dhleong Date: Sun, 23 Oct 2016 10:29:54 -0400 Subject: [PATCH 15/17] Refactor _FilterDiagnostics to rely on _ApplyDiagnosticFilter Also builds the predicates using compilers from diagnostic_filter --- python/ycm/diagnostic_filter.py | 12 +++++------- python/ycm/diagnostic_interface.py | 24 ++++++++++++++---------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/python/ycm/diagnostic_filter.py b/python/ycm/diagnostic_filter.py index 9a68824b..78ab2972 100644 --- a/python/ycm/diagnostic_filter.py +++ b/python/ycm/diagnostic_filter.py @@ -32,11 +32,9 @@ class DiagnosticFilter( object ): def __init__( self, config_or_filters ): if isinstance( config_or_filters, list ): self._filters = config_or_filters - print( 'NewFilter', config_or_filters) else: self._filters = _CompileFilters( config_or_filters ) - print( 'CompileFilters', config_or_filters) def IsAllowed( self, diagnostic ): @@ -91,7 +89,7 @@ class _MasterDiagnosticFilter( object ): if cached is not None: return cached - # build a new DiagnosticFilter mergin all filters + # build a new DiagnosticFilter merging all filters # for the provided filetypes spec = [] for filetype in filetypes: @@ -113,7 +111,7 @@ def _ListOf( config_entry ): return [ config_entry ] -def _CompileRegex( raw_regex ): +def CompileRegex( raw_regex ): pattern = re.compile( raw_regex, re.IGNORECASE ) def FilterRegex( diagnostic ): @@ -122,7 +120,7 @@ def _CompileRegex( raw_regex ): return FilterRegex -def _CompileLevel( level ): +def CompileLevel( level ): # valid kinds are WARNING and ERROR; # expected input levels are `warning` and `error` # NOTE: we don't validate the input... @@ -134,8 +132,8 @@ def _CompileLevel( level ): return FilterLevel -FILTER_COMPILERS = { 'regex' : _CompileRegex, - 'level' : _CompileLevel } +FILTER_COMPILERS = { 'regex' : CompileRegex, + 'level' : CompileLevel } def _CompileFilters( config ): diff --git a/python/ycm/diagnostic_interface.py b/python/ycm/diagnostic_interface.py index 90385a33..e905b6ad 100644 --- a/python/ycm/diagnostic_interface.py +++ b/python/ycm/diagnostic_interface.py @@ -26,7 +26,7 @@ from builtins import * # noqa from future.utils import itervalues, iteritems from collections import defaultdict, namedtuple from ycm import vimsupport -from ycm.diagnostic_filter import DiagnosticFilter +from ycm.diagnostic_filter import DiagnosticFilter, CompileLevel import vim @@ -86,10 +86,17 @@ class DiagnosticInterface( object ): self.PopulateLocationList( normalized_diags ) - def _ApplyDiagnosticFilter( self, diags ): + def _ApplyDiagnosticFilter( self, diags, extra_predicate = None ): filetypes = vimsupport.CurrentFiletypes() diag_filter = self._diag_filter.SubsetForTypes( filetypes ) - return filter( diag_filter.IsAllowed, diags ) + predicate = diag_filter.IsAllowed + if extra_predicate is not None: + def Filter( diag ): + return extra_predicate( diag ) and diag_filter.IsAllowed( diag ) + + predicate = Filter + + return filter( predicate, diags ) def _EchoDiagnosticForLine( self, line_num ): @@ -116,7 +123,8 @@ class DiagnosticInterface( object ): vim.current.buffer.number ] for diags in itervalues( line_to_diags ): - matched_diags.extend( list( filter( predicate, diags ) ) ) + matched_diags.extend( list( + self._ApplyDiagnosticFilter( diags, predicate ) ) ) return matched_diags @@ -247,12 +255,8 @@ def _ConvertDiagListToDict( diag_list ): return buffer_to_line_to_diags -def _DiagnosticIsError( diag ): - return diag[ 'kind' ] == 'ERROR' - - -def _DiagnosticIsWarning( diag ): - return diag[ 'kind' ] == 'WARNING' +_DiagnosticIsError = CompileLevel( 'error' ) +_DiagnosticIsWarning = CompileLevel( 'warning' ) def _NormalizeDiagnostic( diag ): From 04c48e48c40cce3b4261cafee6c51a126ef5f354 Mon Sep 17 00:00:00 2001 From: dhleong Date: Sun, 23 Oct 2016 10:50:58 -0400 Subject: [PATCH 16/17] Rebuild the vim doc --- doc/youcompleteme.txt | 74 +++++++++++++++++++++++-------------------- 1 file changed, 40 insertions(+), 34 deletions(-) diff --git a/doc/youcompleteme.txt b/doc/youcompleteme.txt index ec29023a..4c063e83 100644 --- a/doc/youcompleteme.txt +++ b/doc/youcompleteme.txt @@ -88,8 +88,8 @@ Contents ~ 9. The |g:ycm_warning_symbol| option 10. The |g:ycm_enable_diagnostic_signs| option 11. The |g:ycm_enable_diagnostic_highlighting| option - 12. The |g:ycm_quiet_messages| option - 13. The |g:ycm_echo_current_diagnostic| option + 12. The |g:ycm_echo_current_diagnostic| option + 13. The |g:ycm_filter_diagnostics| option 14. The |g:ycm_always_populate_location_list| option 15. The |g:ycm_open_loclist_on_ycm_diags| option 16. The |g:ycm_allow_changing_updatetime| option @@ -2066,33 +2066,6 @@ Default: '1' let g:ycm_enable_diagnostic_highlighting = 1 < ------------------------------------------------------------------------------- -The *g:ycm_quiet_messages* option - -When this option is set, YCM will suppress any diagnostic items that would -otherwise be displayed that match any of the rules provided. The value is a -dictionary which should be set to something like: -> - let g:ycm_quiet_messages = { - \ "!level": "errors", - \ "regex": '\m\[C03\d\d\]' } -< -This option is part of the Syntastic compatibility layer; if the option is not -set, YCM will fall back to the value of the 'g:syntastic_quiet_messages' -option before using this option's default. - -See |syntastic_quiet_messages| for more details, as this option follows that -one. Currently, only "level" and "regex" rules are supported; unsupported rule -types are ignored. - -As in Syntastic, there are also filetype-specific variants of this option, which -will be used in preference to this if they exist. -They are named |'ycm__quiet_messages'|. - -Default: '{}' -> - let g:ycm_quiet_messages = {} -< -------------------------------------------------------------------------------- The *g:ycm_echo_current_diagnostic* option When this option is set, YCM will echo the text of the diagnostic present on @@ -2108,6 +2081,39 @@ Default: '1' let g:ycm_echo_current_diagnostic = 1 < ------------------------------------------------------------------------------- +The *g:ycm_filter_diagnostics* option + +This option controls which diagnostics will be rendered by YCM. This option +holds a dictionary of key-values, where the keys are Vim's filetype strings +delimited by commas and values are dictionaries describing the filter. + +A filter is a dictionary of key-values, where the keys are the type of filter, +and the value is a list of arguments to that filter. In the case of just a +single item in the list, you may omit the brackets and just provide the +argument directly. If any filter matches a diagnostic, it will be dropped and +YCM will not render it. + +The following filter types are supported: + +- "regex": Accepts a string regular expression [54]. This type matches when + the regex (treated as case-insensitive) is found in the diagnostic text. + +- "level": Accepts a string level, either "warning" or "error." This type + matches when the diagnostic has the same level. + +NOTE: The regex syntax is **NOT** Vim's, it's Python's [54]. + +Default: '{}' +> + let g:ycm_filter_diagnostics = { + \ "java": { + \ "regex": [ ".*taco.*", ... ], + \ "level": "error", + \ ... + \ } + \ } +< +------------------------------------------------------------------------------- The *g:ycm_always_populate_location_list* option When this option is set, YCM will populate the location list automatically @@ -2202,7 +2208,7 @@ from the 'tagfiles()' Vim function which examines the 'tags' Vim option. See YCM will re-index your tags files if it detects that they have been modified. -The only supported tag format is the Exuberant Ctags format [54]. The format +The only supported tag format is the Exuberant Ctags format [55]. The format from "plain" ctags is NOT supported. Ctags needs to be called with the '-- fields=+l' option (that's a lowercase 'L', not a one) because YCM needs the 'language:' field in the tags output. @@ -2569,7 +2575,7 @@ It's also possible to use a regular expression as a trigger. You have to prefix your trigger with 're!' to signify it's a regex trigger. For instance, 're!\w+\.' would only trigger after the '\w+\.' regex matches. -NOTE: The regex syntax is **NOT** Vim's, it's Python's [55]. +NOTE: The regex syntax is **NOT** Vim's, it's Python's [54]. Default: '[see next line]' > @@ -2897,7 +2903,7 @@ YCM does not read identifiers from my tags files ~ First, put 'let g:ycm_collect_identifiers_from_tags_files = 1' in your vimrc. Make sure you are using Exuberant Ctags [57] to produce your tags files since -the only supported tag format is the Exuberant Ctags format [54]. The format +the only supported tag format is the Exuberant Ctags format [55]. The format from "plain" ctags is NOT supported. The output of 'ctags --version' should list "Exuberant Ctags". @@ -3240,8 +3246,8 @@ References ~ [51] https://github.com/Valloric/ycmd/blob/master/ycmd/completers/completer.py [52] https://github.com/Valloric/ListToggle [53] https://github.com/itchyny/lightline.vim -[54] http://ctags.sourceforge.net/FORMAT -[55] https://docs.python.org/2/library/re.html#regular-expression-syntax +[54] https://docs.python.org/2/library/re.html#regular-expression-syntax +[55] http://ctags.sourceforge.net/FORMAT [56] https://github.com/Valloric/YouCompleteMe/issues/18 [57] http://ctags.sourceforge.net/ [58] https://github.com/Raimondi/delimitMate From 7e12c127cf8ef3e10349b89a9aefd682869c4dbd Mon Sep 17 00:00:00 2001 From: dhleong Date: Sun, 23 Oct 2016 12:29:29 -0400 Subject: [PATCH 17/17] Wrap README.md to 80 chars --- README.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 7551d863..1a8687b7 100644 --- a/README.md +++ b/README.md @@ -1805,21 +1805,22 @@ let g:ycm_echo_current_diagnostic = 1 ### The `g:ycm_filter_diagnostics` option -This option controls which diagnostics will be rendered by YCM. This option holds -a dictionary of key-values, where the keys are Vim's filetype strings delimited by -commas and values are dictionaries describing the filter. +This option controls which diagnostics will be rendered by YCM. This option +holds a dictionary of key-values, where the keys are Vim's filetype strings +delimited by commas and values are dictionaries describing the filter. A filter is a dictionary of key-values, where the keys are the type of filter, -and the value is a list of arguments to that filter. In the case of just a single -item in the list, you may omit the brackets and just provide the argument directly. -If any filter matches a diagnostic, it will be dropped and YCM will not render it. +and the value is a list of arguments to that filter. In the case of just a +single item in the list, you may omit the brackets and just provide the argument +directly. If any filter matches a diagnostic, it will be dropped and YCM will +not render it. The following filter types are supported: - "regex": Accepts a string [regular expression][python-re]. This type matches when the regex (treated as case-insensitive) is found in the diagnostic text. -- "level": Accepts a string level, either "warning" or "error." This type matches -when the diagnostic has the same level. +- "level": Accepts a string level, either "warning" or "error." This type +matches when the diagnostic has the same level. NOTE: The regex syntax is **NOT** Vim's, it's [Python's][python-re].