1. abudden
  2. TagHighlight

Commits

abudden  committed 24747b1

Added option to support limiting scope of keywords by file.

  • Participants
  • Parent commits 45fc029
  • Branches default

Comments (0)

Files changed (7)

File autoload/TagHighlight/ReadTypes.vim

View file
  • Ignore whitespace
 " Tag Highlighter:
 "   Author:  A. S. Budden <abudden _at_ gmail _dot_ com>
-" Copyright: Copyright (C) 2009-2012 A. S. Budden
+" Copyright: Copyright (C) 2009-2013 A. S. Budden
 "            Permission is hereby granted to use and distribute this code,
 "            with or without modifications, provided that this copyright
 "            notice is copied with it. Like anything else that's free,
 	let type_files = TagHighlight#ReadTypes#FindTypeFiles(a:suffix)
 	for fname in type_files
 		call TagHLDebug("Loading type highlighter file " . fname, 'Information')
+		let types_path = fnamemodify(fname, ':p')
+		let old_dir = getcwd()
+		let b:TagHighlightPrivate['NormalisedPath'] = substitute(
+					\ fnamemodify(file, ':p:.'),
+					\ '\\', '/', 'g')
+		exe 'cd' old_dir
+
 		exe 'so' fname
 		let b:TagHighlightLoadedLibraries +=
 					\ [{

File doc/TagHighlight.txt

View file
  • Ignore whitespace
 		SkipVimKeywords                  Link:|TagHL-SkipVimKeywords|
 			Don't include vim keywords in highlighter.
 
+		IgnoreFileScope                  Link:|TagHL-IgnoreFileScope|
+			Highlight everything everywhere, ignoring file-scope.
+
 	Customisation:
 
 		PreReadHooks                     Link:|TagHL-PreReadHooks|
 		Option Type: List
 		Default: [] (empty list)
 
+	IgnoreFileScope                      *TagHL-IgnoreFileScope*
+		Where ctags detects that a variable is limited in scope to only be
+		valid in one source file (for example functions declared with the
+		static keyword in C), TagHighlight will normally only highlight those
+		keywords in the source file in which they're defined.  Setting this
+		option to True or 1 will disable this feature and the keywords will be
+		highlighted in all files in the project.
+
+		Option Type: Boolean
+		Default: False
+
 	IncludeLocals                        *TagHL-IncludeLocals*
 		If set to True or 1, local variables will be included in the types
 		highlighter file.  Note that ctags provides no context for these
 	  generating types files.  Initially the reserved keywords have been
 	  recorded for C++, Python, Java and C#.  Thanks to Alexey Radkov and Alan
 	  Warren for the suggestions.
+	* File-scope variables are only highlighted in the file in which their
+	  scope is valid (thanks to Kévin Brodsky for the suggestion).  An option
+	  called IgnoreFileScope disables this feature.
 	
 	Bug fixes:
 	

File plugin/TagHighlight/data/options.txt

View file
  • Ignore whitespace
 	Default:False
 	Help:Print the time with each debug message in the log
 
+ignore_file_scope:
+	CommandLineSwitches:--ignore-file-scope
+	VimOptionMap:IgnoreFileScope
+	Type:bool
+	Default:False
+	Help:Ignore file-scope specified in tags file
+
 source_root:
 	CommandLineSwitches:-d,--source-root
 	VimOptionMap:SourceDir

File plugin/TagHighlight/module/ctags_interface.py

View file
  • Ignore whitespace
 #!/usr/bin/env python
 # Tag Highlighter:
 #   Author:  A. S. Budden <abudden _at_ gmail _dot_ com>
-# Copyright: Copyright (C) 2009-2012 A. S. Budden
+# Copyright: Copyright (C) 2009-2013 A. S. Budden
 #            Permission is hereby granted to use and distribute this code,
 #            with or without modifications, provided that this copyright
 #            notice is copied with it. Like anything else that's free,
 import os
 import re
 import glob
-from .utilities import DictDict, rglob
+from .utilities import TagDB, FileTagDB, rglob
 from .languages import Languages
 from .debug import Debug
 
     ^                 # Start of the line
     (?P<keyword>.*?)  # Capture the first field: everything up to the first tab
     \t                # Field separator: a tab character
-    .*?               # Second field (uncaptured): everything up to the next tab
+    (?P<filename>.*?) # Second field (filename): everything up to the next tab
     \t                # Field separator: a tab character
     (?P<search>.*?)   # Any character at all, but as few as necessary (i.e. catch everything up to the ;")
     ;"                # The end of the search specifier (see http://ctags.sourceforge.net/FORMAT)
                       # Also catch the tab character from the previous line as there MUST be a tab before the field
     (kind:)?          # This is the "kind" field; "kind:" is optional
     (?P<kind>\w)      # The kind is a single character: catch it
-    (\t|$)            # It must be followed either by a tab or by the end of the line
-    .*                # If it is followed by a tab, soak up the rest of the line; replace with the syntax keyword line
+    (?=\t|$)          # It must be followed either by a tab or by the end of the line (but don't include that in the match)
+    (?P<other>        # Catch anything in between the kind and the scope indicator
+        \t            # Each block is a tab, followed by
+        (?!file:)     # NOT file: (as this is the scope indicator)
+        [^\t]+        # One or more non-tab characters
+    )*                # This block can repeat
+    (?P<scope>        # Look for a file-scope indicator
+        \t            # Preceded by a tab character
+        file:         # This is the file-scope indicator
+        (?=\t|$)      # Must be followed by a tab character or the end of line (but don't include it in the match)
+    )?                # The file-scope identifier is optional
+    .*                # Soak up the rest of the line
 ''', re.VERBOSE)
 
 field_const = re.compile(r'\bconst\b')
     kind_list = languages.GetKindList()
 
     # Language: {Type: set([keyword, keyword, keyword])}
-    ctags_entries = DictDict()
+    ctags_entries = TagDB()
+    # Language: {File: {Type: set([keyword, keyword, keyword])}}
+    file_entries = FileTagDB()
 
     lineMatchers = {}
     for key in languages.GetAllLanguages():
                 m = field_processor.match(line.strip())
                 if m is not None:
                     try:
+                        new_entry = None
                         short_kind = 'ctags_' + m.group('kind')
                         kind = kind_list[key][short_kind]
                         keyword = m.group('keyword')
                                 kind = 'CTagsConstant'
                         if key in options['language_tag_types']:
                             if m.group('kind') in options['language_tag_types'][key]:
-                                ctags_entries[key][kind].add(keyword)
+                                new_entry = keyword
                         elif m.group('kind') not in languages.GetLanguageHandler(key)['SkipList']:
-                            ctags_entries[key][kind].add(keyword)
+                            new_entry = keyword
+
+                        if new_entry is None:
+                            continue
+
+                        if m.group('scope') is None:
+                            if new_entry.startswith('AMS'):
+                                Debug("No scope: " + new_entry, "Information")
+                            ctags_entries[key][kind].add(new_entry)
+                        else:
+                            if m.group('filename') not in file_entries:
+                                file_entries[m.group('filename')] = TagDB()
+                            file_entries[key][m.group('filename')][kind].add(new_entry)
+
                     except KeyError:
                         Debug("Unrecognised kind '{kind}' for language {language}".format(kind=m.group('kind'), language=key), "Error")
     p.close()
 
-    return ctags_entries
+    return ctags_entries, file_entries
 
 def ExuberantGetCommandArgs(options):
     args = []

File plugin/TagHighlight/module/generation.py

View file
  • Ignore whitespace
 #!/usr/bin/env python
 # Tag Highlighter:
 #   Author:  A. S. Budden <abudden _at_ gmail _dot_ com>
-# Copyright: Copyright (C) 2009-2011 A. S. Budden
+# Copyright: Copyright (C) 2009-2013 A. S. Budden
 #            Permission is hereby granted to use and distribute this code,
 #            with or without modifications, provided that this copyright
 #            notice is copied with it. Like anything else that's free,
         'skipempty'
         ]
 
-def CreateTypesFile(options, language, tags):
-    tag_types = list(tags.keys())
-    tag_types.sort()
-
+def CreateTypesFile(options, language, unscoped_tags, file_tags):
     Debug("Writing types file", "Information")
 
-    language_handler = options['language_handler'].GetLanguageHandler(language)
+    entry_sets = {}
 
-    if options['check_keywords']:
-        iskeyword = GenerateValidKeywordRange(language_handler['IsKeyword'])
-        Debug("Is Keyword is {0!r}".format(iskeyword), "Information")
+    for source_file in [None] + file_tags.keys():
+        if source_file is None:
+            tags = unscoped_tags
+        else:
+            tags = file_tags[source_file]
 
-    matchEntries = set()
-    vimtypes_entries = []
+        tag_types = list(tags.keys())
+        tag_types.sort()
 
+        language_handler = options['language_handler'].GetLanguageHandler(language)
 
-    typesUsedByLanguage = list(options['language_handler'].GetKindList(language).values())
-    # TODO: This may be included elsewhere, but we'll leave it in for now
-    #clear_string = 'silent! syn clear ' + " ".join(typesUsedByLanguage)
+        if options['check_keywords']:
+            iskeyword = GenerateValidKeywordRange(language_handler['IsKeyword'])
+            Debug("Is Keyword is {0!r}".format(iskeyword), "Information")
 
-    vimtypes_entries = []
-    #vimtypes_entries.append(clear_string)
+        matchEntries = set()
+        vimtypes_entries = []
 
-    # Get the priority list from the language handler
-    priority = language_handler['Priority'][:]
-    # Reverse the priority such that highest priority
-    # is last.
-    priority.reverse()
+        typesUsedByLanguage = list(options['language_handler'].GetKindList(language).values())
+        # TODO: This may be included elsewhere, but we'll leave it in for now
+        #clear_string = 'silent! syn clear ' + " ".join(typesUsedByLanguage)
 
-    fullTypeList = list(reversed(sorted(tags.keys())))
-    # Reorder type list according to priority sort order
-    allTypes = []
-    for thisType in priority:
-        if thisType in fullTypeList:
-            allTypes.append(thisType)
-            fullTypeList.remove(thisType)
-    # Add the ones not specified in priority
-    allTypes = fullTypeList + allTypes
+        vimtypes_entries = []
+        #vimtypes_entries.append(clear_string)
 
-    Debug("Type priority list: " + repr(allTypes), "Information")
+        # Get the priority list from the language handler
+        priority = language_handler['Priority'][:]
+        # Reverse the priority such that highest priority
+        # is last.
+        priority.reverse()
 
-    patternREs = []
-    for pattern in options['skip_patterns']:
-        patternREs.append(re.compile(pattern))
+        fullTypeList = list(reversed(sorted(tags.keys())))
+        # Reorder type list according to priority sort order
+        allTypes = []
+        for thisType in priority:
+            if thisType in fullTypeList:
+                allTypes.append(thisType)
+                fullTypeList.remove(thisType)
+        # Add the ones not specified in priority
+        allTypes = fullTypeList + allTypes
 
-    for thisType in allTypes:
-        keystarter = 'syn keyword ' + thisType
-        keycommand = keystarter
-        for keyword in tags[thisType]:
-            skip_this = False
-            if options['skip_reserved_keywords']:
-                if keyword in language_handler['ReservedKeywords']:
-                    Debug('Skipping reserved word ' + keyword, 'Information')
-                    # Ignore this keyword
-                    continue
-            for pattern in patternREs:
-                if pattern.search(keyword) != None:
-                    skip_this = True
-                    break
-            if skip_this:
-                continue
+        Debug("Type priority list: " + repr(allTypes), "Information")
 
-            if options['check_keywords']:
-                # In here we should check that the keyword only matches
-                # vim's \k parameter (which will be different for different
-                # languages).  This is quite slow so is turned off by
-                # default; however, it is useful for some things where the
-                # default generated file contains a lot of rubbish.  It may
-                # be worth optimising IsValidKeyword at some point.
-                if not IsValidKeyword(keyword, iskeyword):
-                    matchDone = False
-                    if options['include_matches']:
+        patternREs = []
+        for pattern in options['skip_patterns']:
+            patternREs.append(re.compile(pattern))
 
-                        patternCharacters = "/@#':"
-                        charactersToEscape = '\\' + '~[]*.$^'
-
-                        for patChar in patternCharacters:
-                            if keyword.find(patChar) == -1:
-                                escapedKeyword = keyword
-                                for ch in charactersToEscape:
-                                    escapedKeyword = escapedKeyword.replace(ch, '\\' + ch)
-                                if options['include_matches']:
-                                    matchEntries.add('syn match ' + thisType + ' ' + patChar + escapedKeyword + patChar)
-                                matchDone = True
-                                break
-
-                    if not matchDone:
-                        Debug("Skipping keyword '" + keyword + "'", "Information")
-
+        for thisType in allTypes:
+            keystarter = 'syn keyword ' + thisType
+            keycommand = keystarter
+            for keyword in tags[thisType]:
+                skip_this = False
+                if options['skip_reserved_keywords']:
+                    if keyword in language_handler['ReservedKeywords']:
+                        Debug('Skipping reserved word ' + keyword, 'Information')
+                        # Ignore this keyword
+                        continue
+                for pattern in patternREs:
+                    if pattern.search(keyword) != None:
+                        skip_this = True
+                        break
+                if skip_this:
                     continue
 
+                if options['check_keywords']:
+                    # In here we should check that the keyword only matches
+                    # vim's \k parameter (which will be different for different
+                    # languages).  This is quite slow so is turned off by
+                    # default; however, it is useful for some things where the
+                    # default generated file contains a lot of rubbish.  It may
+                    # be worth optimising IsValidKeyword at some point.
+                    if not IsValidKeyword(keyword, iskeyword):
+                        matchDone = False
+                        if options['include_matches']:
 
-            if keyword.lower() in vim_synkeyword_arguments:
-                if not options['skip_vimkeywords']:
-                    matchEntries.add('syn match ' + thisType + ' /' + keyword + '/')
-                continue
+                            patternCharacters = "/@#':"
+                            charactersToEscape = '\\' + '~[]*.$^'
 
-            temp = keycommand + " " + keyword
-            if len(temp) >= 512:
+                            for patChar in patternCharacters:
+                                if keyword.find(patChar) == -1:
+                                    escapedKeyword = keyword
+                                    for ch in charactersToEscape:
+                                        escapedKeyword = escapedKeyword.replace(ch, '\\' + ch)
+                                    if options['include_matches']:
+                                        matchEntries.add('syn match ' + thisType + ' ' + patChar + escapedKeyword + patChar)
+                                    matchDone = True
+                                    break
+
+                        if not matchDone:
+                            Debug("Skipping keyword '" + keyword + "'", "Information")
+
+                        continue
+
+
+                if keyword.lower() in vim_synkeyword_arguments:
+                    if not options['skip_vimkeywords']:
+                        matchEntries.add('syn match ' + thisType + ' /' + keyword + '/')
+                    continue
+
+                temp = keycommand + " " + keyword
+                if len(temp) >= 512:
+                    vimtypes_entries.append(keycommand)
+                    keycommand = keystarter
+                keycommand = keycommand + " " + keyword
+            if keycommand != keystarter:
                 vimtypes_entries.append(keycommand)
-                keycommand = keystarter
-            keycommand = keycommand + " " + keyword
-        if keycommand != keystarter:
-            vimtypes_entries.append(keycommand)
 
-    # Sort the matches
-    matchEntries = sorted(list(matchEntries))
+        # Sort the matches
+        matchEntries = sorted(list(matchEntries))
 
-    if (len(matchEntries) + len(vimtypes_entries)) == 0:
-        # All keywords have been filtered out, give up
-        return
+        if (len(matchEntries) + len(vimtypes_entries)) == 0:
+            # All keywords have been filtered out, give up
+            return
 
-    vimtypes_entries.append('')
-    vimtypes_entries += matchEntries
+        vimtypes_entries.append('')
+        vimtypes_entries += matchEntries
+
+        if source_file is None:
+            unscoped_entries = vimtypes_entries[:]
+        else:
+            entry_sets[source_file] = vimtypes_entries[:]
 
     if options['include_locals']:
         LocalTagType = ',CTagsLocalVariable'
         sys.exit(1)
 
     try:
-        for line in vimtypes_entries:
-            try:
-                fh.write(line.encode('ascii'))
-            except UnicodeDecodeError:
-                Debug("Error decoding line '{0!r}'".format(line), "Error")
-                fh.write('echoerr "Types generation error"\n'.encode('ascii'))
-            fh.write('\n'.encode('ascii'))
+        for source_file in [None] + entry_sets.keys():
+            if source_file is None:
+                vimtypes_entries = unscoped_entries
+                prefix = ''
+            else:
+                prefix = '\t'
+                vimtypes_entries = entry_sets[source_file]
+
+            if source_file is not None and not options['ignore_file_scope']:
+                formatted_file = os.path.normpath(source_file).replace(os.path.sep, '/')
+                fh.write('" Matches for file %s:\n' % source_file)
+                fh.write('if b:TagHighlightPrivate["NormalisedPath"] == "%s"\n' % formatted_file)
+            for line in vimtypes_entries:
+                try:
+                    fh.write(prefix + line.encode('ascii'))
+                except UnicodeDecodeError:
+                    Debug("Error decoding line '{0!r}'".format(line), "Error")
+                    fh.write('echoerr "Types generation error"\n'.encode('ascii'))
+                fh.write('\n'.encode('ascii'))
+            if source_file is not None and not options['ignore_file_scope']:
+                fh.write('endif\n')
     except IOError:
         Debug("ERROR: Couldn't write {file} contents\n".format(file=outfile), "Error")
         sys.exit(1)

File plugin/TagHighlight/module/utilities.py

View file
  • Ignore whitespace
 #!/usr/bin/env python
 # Tag Highlighter:
 #   Author:  A. S. Budden <abudden _at_ gmail _dot_ com>
-# Copyright: Copyright (C) 2009-2012 A. S. Budden
+# Copyright: Copyright (C) 2009-2013 A. S. Budden
 #            Permission is hereby granted to use and distribute this code,
 #            with or without modifications, provided that this copyright
 #            notice is copied with it. Like anything else that's free,
         else:
             super(SetDict, self).__setitem__(key, set([value]))
 
-class DictDict(dict):
+class TagDB(dict):
     """Customised version of a dictionary that auto-creates non-existent keys as SetDicts."""
     def __getitem__(self, key):
         if key not in self:
             self[key] = SetDict()
-        return super(DictDict, self).__getitem__(key)
+        return super(TagDB, self).__getitem__(key)
 
     def __setitem__(self, key, value):
         if isinstance(value, SetDict):
-            super(DictDict, self).__setitem__(key, value)
+            super(TagDB, self).__setitem__(key, value)
+        else:
+            raise NotImplementedError
+
+class FileTagDB(dict):
+    """Customised version of a dictionary that auto-creates non-existent keys as TagDBs."""
+    def __getitem__(self, key):
+        if key not in self:
+            self[key] = TagDB()
+        return super(FileTagDB, self).__getitem__(key)
+
+    def __setitem__(self, key, value):
+        if isinstance(value, TagDB):
+            super(FileTagDB, self).__setitem__(key, value)
         else:
             raise NotImplementedError
 

File plugin/TagHighlight/module/worker.py

View file
  • Ignore whitespace
 #!/usr/bin/env python
 # Tag Highlighter:
 #   Author:  A. S. Budden <abudden _at_ gmail _dot_ com>
-# Copyright: Copyright (C) 2009-2011 A. S. Budden
+# Copyright: Copyright (C) 2009-2013 A. S. Budden
 #            Permission is hereby granted to use and distribute this code,
 #            with or without modifications, provided that this copyright
 #            notice is copied with it. Like anything else that's free,
     if not config['use_existing_tagfile']:
         Debug("Generating tag file", "Information")
         GenerateTags(config)
-    tag_db = ParseTags(config)
+    tag_db, file_tag_db = ParseTags(config)
 
     for language in config['language_list']:
         if language in tag_db:
-            CreateTypesFile(config, language, tag_db[language])
+            CreateTypesFile(config, language, tag_db[language], file_tag_db[language])
 
     os.chdir(start_directory)