c++, cscope, ctags, and vim: Finding classes that inherit from this one
Asked Answered
G

4

6

In a rather large code base with a few layers is there a way in vim or from the command line to find all classes that are derived from a base class? grep is an option but can be slow since grep does not index.

Gynandromorph answered 4/9, 2014 at 0:39 Comment(4)
I don't think so, cscope and ctags are based on the regex but not syntax like IDERoee
Have you considered Ack, Ag the Silver Surfer, or git grep? These are all typically much faster than grep.Miquelmiquela
I'm also interested in this. I currently use Eclipse and SlickEdit to do this. In Eclipse, this functionality is called Type Hierarchy. There is a project called eclim which integrates Eclipse functionality to Vim, this is the closest I've seen to this in Vim, unfortunately, eclim eats a lot of RAM and constantly crashes in Windows, most likely due to Eclipse and requiring Eclipse to fine tune memory settings..Misquote
One day, a wise person will create a CLI tool using libclang that just works for all C++ queries (inheritance / usage): #43461105 That day, the world will be saved.Boatbill
C
4

Neither cscope nor ctags allow us to deal with inheritance directly but it's relatively easy to work around that limitation because derived classes are also indexed.

cscope

In cscope, looking for "C symbol" Foobar usually lists the original class and classes inheriting from it. Since the search is done against a database, it is lightning fast.

Alternatively, you could use cscope's egrep searching capabilities with a pattern like :.*Foobar to list only classes inheriting from Foobar.

So, even if we don't have a dedicated "Find classes inheriting from this class" command, we can get the work done without much effort.

ctags

While ctags allows you to include inheritance information with --fields=+i, that information can't be used directly in Vim. The inherits field is parsed by Vim, though, so it might be possible to build a quick and dirty solution using taglist().

ack, ag

Those two programs work more or less like grep but they are targeted toward searching in source code so they are really faster than grep.

In my Vim config, :grep is set to run the ag program instead of the default grep so, searching for classes derived from the class under the cursor would look like:

:grep :.*<C-r><C-w><CR>

Here are the relevant lines from my ~/.vimrc:

if executable("ag")
  set grepprg=ag\ --nogroup\ --nocolor\ --ignore-case\ --column
  set grepformat=%f:%l:%c:%m,%f:%l:%m
endif
Commission answered 4/9, 2014 at 7:59 Comment(2)
I am noticing some speed up using fugitive and vim with the :Ggrep command. This improvement requires the code be a git repo which was not mentioned in my post but is another alternative.Gynandromorph
thanks for the tips, comes out a better one from yours::Ack! '(public|private|protected).*<C-r><C-w><CR>'Vibratile
S
2

In lh-cpp, I define the command :Children. It relies on a ctags database, and as a consequence, it is quite limited.

It takes two optional parameters: the namespace where to look for (I haven't found a way to avoid that), and the name of the parent class -> :Children [!] {namespace} {parent-class}.

The command tries to cache as much information as possible. Hence, when pertinent information changes in the ctags database, the cache must be updated. It is done by banging the command -> :Children!

Symbolic answered 4/9, 2014 at 7:38 Comment(0)
K
2

If you build your tags files with Exuberant CTags using inheritance information (see the --fields option), then the following script will work. It adds an :Inherits command which takes either the name of a class (e.g. :Inherits Foo) or a regular expression.

Like the :tag command, you indicate that you want the search with a regex by preceding it with a '\' character, e.g. :Inherits \Foo.*.

The results are put into the window's location list, which you browse with :ll, :lne, :lp, etc. VIM doesn't seem to allow scripts to modify the tag list which is what I'd prefer.

If you're wondering why I don't use taglist() for this, it's because taglist() is incredibly slow on large tag files. The original post had a version using taglist(), if you're curious you can browse the edit history.

" Parse an Exuberant Ctags record using the same format as taglist()
"
" Throws CtagsParseErr if there is a general problem parsing the record
function! ParseCtagsRec(record, tag_dir)
    let tag = {}

    " Parse the standard fields
    let sep_pos = stridx(a:record, "\t")
    if sep_pos < 1
        throw 'CtagsParseErr'
    endif
    let tag['name'] = a:record[:sep_pos - 1]
    let tail = a:record[sep_pos + 1:]
    let sep_pos = stridx(tail, "\t")
    if sep_pos < 1
        throw 'CtagsParseErr'
    endif
    " '/' will work as a path separator on most OS's, but there
    " should really be an OS independent way to build paths.
    let tag['filename'] = a:tag_dir.'/'.tail[:sep_pos - 1]
    let tail = tail[sep_pos + 1:]
    let sep_pos = stridx(tail, ";\"\t")
    if sep_pos < 1
        throw 'CtagsParseErr'
    endif
    let tag['cmd'] = tail[:sep_pos - 1]

    " Parse the Exuberant Ctags extension fields
    let extensions = tail[sep_pos + 3:]
    for extension in split(extensions, '\t')
        let sep_pos = stridx(extension, ':')
        if sep_pos < 1
            if has_key(tag, 'kind')
                throw 'CtagsParseErr'
            endif
            let tag['kind'] = extension
        else
            let tag[extension[:sep_pos - 1]] = extension[sep_pos + 1:]
        endif
    endfor

    return tag
endfunction

" Find all classes derived from a given class, or a regex (preceded by a '/')
" The results are placed in the current windows location list.
function! Inherits(cls_or_regex)
    if a:cls_or_regex[0] == '/'
        let regex = a:cls_or_regex[1:]
    else
        let regex = '\<'.a:cls_or_regex.'\>$'
    endif
    let loc_list = []
    let tfiles = tagfiles()
    let tag_count = 0
    let found_count = 0
    for file in tfiles
        let tag_dir = fnamemodify(file, ':p:h')
        try
            for line in readfile(file)
                let tag_count += 1
                if tag_count % 10000 == 0
                    echo tag_count 'tags scanned,' found_count 'matching classes found. Still searching...'
                    redraw
                endif
                if line[0] == '!'
                    continue
                endif

                let tag = ParseCtagsRec(line, tag_dir)

                if has_key(tag, 'inherits')
                    let baselist = split(tag['inherits'], ',\s*')
                    for base in baselist
                        if match(base, regex) != -1
                            let location = {}
                            let location['filename'] = tag['filename']

                            let cmd = tag['cmd']
                            if cmd[0] == '/' || cmd[0] == '?'
                                let location['pattern'] = cmd[1:-2]
                            else
                                let location['lnum'] = str2nr(cmd)
                            endif

                            call add(loc_list, location)
                            let found_count += 1
                        endif
                    endfor
                endif
            endfor
        catch /^OptionErr$/
            echo 'Parsing error: Failed to parse an option.'
            return
        catch /^CtagsParseErr$/
            echo 'Parsing error: Tags files does not appear to be an Exuberant Ctags file.'
            return
        catch
            echo 'Could not read tag file:' file
            return
        endtry
    endfor
    call setloclist(0, loc_list)
    echo tag_count 'tags scanned,' found_count 'matching classes found.'
endfunction

command! -nargs=1 -complete=tag Inherits call Inherits('<args>')
Karen answered 4/2, 2015 at 1:22 Comment(0)
K
1

I don't think vim is the correct tool to list all child classes. Instead, we'd better use the doxygen to generate documentation for the source code. Although the doxygen needs some time, we can use the document/diagrams for all classes, which is clear and fast.

Kutz answered 4/9, 2014 at 8:1 Comment(3)
This assumes though that you are developing using a graphical interface. Sometimes, I have to use minty (cygwins terminal) over ssh which means the dev box would have to have a web server to host the doxygen files.Gynandromorph
Yes, exactly. And a Linux/Unix server, which already provided SSH, is easy to deploy a web server, it is also recommended to enable PHP in the web server that provides a simple search engine inside the doxygen pages.Kutz
Both Eclipse and SlickEdit have a way to list inheritance trees for classes and also a tree list of overridden methods. This is called Type Hierarchy in Eclipse, and is one of the most useful functions in an IDE when coding in an OO language. Doxygen is really useful but is an overkill when you only need to take a quick look at the "Type Hierarchy".Misquote

© 2022 - 2024 — McMap. All rights reserved.