📜 ⬆️ ⬇️

Writing a plugin to support cmake projects under vim

Today let's talk about creating add-ons for VIM.

Recently, I had an idea to screw in cmake support for projects for easy navigation through files. Of course, NERD Tree will certainly cope with this task, but in the latter it is impossible to operate exclusively with project files.

Achtung: The author of the article first met Vim Script. He does not guarantee that you will not faint after reading the article. Any wishes regarding the code leave in the comments.
')


The cmake project management plugin should use the cmake command to create the necessary files for the build in the “build” folder and display the file tree pane, by clicking on the elements of which you can easily reach the source files.

And so, we will begin to implement.

If you go deep into the depths of files created with cmake, you can find out where it stores the list of dependent source files. Make a search for the presence of lines with cpps semi-support way:

grep ".*\.cpp" -R build/ 


I found a variable in DependInfo.cmake with this content
 SET(CMAKE_DEPENDS_CHECK_CXX "/home/..../brushcombo.cpp" "/home/.../build/CMakeFiles/kdots.dir/brushcombo.o" ... ) 


Find all the files DependInfo.cmake in the directory and find the full paths to the files using the Perl script.
 sub cmake_project_files { my $dir = shift; my @dependencies = File::Find::Rule->file() ->name("DependInfo.cmake") ->in($dir); my @accum = (); foreach my $filename(@dependencies) { open(FILE, $filename); my @data = <FILE>; push (@accum, src_files(\@data)); close(FILE); } return @accum; } sub src_files { my @result = (); foreach my $line(@{(shift)}) { if ($line =~ m/\s*\"(([a-zA-Z_\/]+)\/([a-zA-Z_]+\.(cpp|cc))).*/) { push(@result, $1); } } return @result; } 


Full source here .

Before we bind these functions to a plugin, let's look at the directory hierarchy in ~ / .vim.


Since our plugin should load every time the editor is started, put it in ~ / .vim / plugin. Name the file as cmake-project.vim.

Check for perl interpreter:
 if !has('perl') echo "Error: perl not found" finish endif 


Create a function to generate a tree of files. Functions, as well as variables, can be created with different scopes (you can read about it here ). At the beginning, this function creates a new window and a buffer named “CMakeProject”.
 function! s:cmake_project_window() vnew badd CMakeProject buffer CMakeProject " ,    ,   , VIM     setlocal buftype=nofile ... 


To determine whether our current buffer is a panel with a file tree, we will declare a variable (with a scope inside the plugin) with the name of the buffer.
  let s:cmake_project_bufname = bufname("%") 


And now let's tie the Perl script to the plugin. Place the script in the ~ / .vim / plugin / cmake-project directory so that use lib can find it. Get the list of files from the cmakeproject :: cmake_project_files function and put it in the Wim list.

 perl << EOF "     Python   Ruby use lib "$ENV{'HOME'}/.vim/plugin/cmake-project"; use cmakeproject; my $dir = VIM::Eval('g:cmake_project_build_dir'); my @result = cmakeproject::cmake_project_files($dir); VIM::DoCommand("let s:cmake_project_files = []"); foreach $filename(@result) { if (-e $filename) { VIM::DoCommand("call insert(s:cmake_project_files, \'$filename\')"); } } EOF 


Further on the basis of this data we build a tree. There are several data structures in vim: hashes and lists. Therefore, we represent the directory as a key in a hash. If the hash key points to something (not 1), then this is a directory, and if it is 1, then this is a file located in a directory.

The code below converts a string like "/home/paranoik/main.cpp" into a structure like {'home': {'paranoik': {'main.cpp': 1}}, where {key: value} is a hash with 1 key-value pair.

  let s:cmake_project_file_tree = {} for fullpath in s:cmake_project_files let current_tree = s:cmake_project_file_tree let cmake_project_args = split(fullpath, '\/') let filename = remove(cmake_project_args, -1) for path in cmake_project_args if !has_key(current_tree, path) let current_tree[path] = {} "   endif let current_tree = current_tree[path] endfor let current_tree[filename] = 1 endfor call s:cmake_project_print_bar(s:cmake_project_file_tree, 0) 


Now we define the function to display the tree in the buffer. Depending on the level of the hierarchy, indents are defined in the form of spaces (the s function: cmake_project_indent is responsible for this).
 function! s:cmake_project_print_bar(tree, level) for pair in items(a:tree) if type(pair[1]) == type({}) "   let name = s:cmake_project_indent(a:level) . "-" . pair[0] call append(line('$'), name . "/") "   "-<dir>/" let newlevel = a:level + 1 call s:cmake_project_print_bar(pair[1], newlevel) "      . else "   let name = s:cmake_project_indent(a:level) . pair[0] call append(line('$'), name) endif endfor endfunction 


Bind the s: cmake_project_window () function to the CMakePro command
 command -nargs=0 -bar CMakePro call s:cmake_project_window() 


We also need a command to generate cmake files.
 command -nargs=1 -bar CMake call s:cmake_project_cmake(<f-args>) function! s:cmake_project_cmake(srcdir) if !isdirectory(a:srcdir) echo "This directory not exists!" . a:srcdir return endif let s:cmake_project_dir = a:srcdir exec "cd" a:srcdir if !isdirectory(g:cmake_project_build_dir) call mkdir(g:cmake_project_build_dir) endif cd build exec "!cmake" "../" cd .. call s:cmake_project_window() endfunction 


When moving the cursor on the panel, the file should open under the cursor. Create a s: cmake_project_cursor_moved () function and bind it to the CursorMoved signal.
 autocmd CursorMoved * call s:cmake_project_cursor_moved() 

In order for the function to work only with the panel buffer, we check its name before executing.
 function! s:cmake_project_cursor_moved() if exists('s:cmake_project_bufname') && bufname('%') == s:cmake_project_bufname <code> endif endfunction 

Get the data of the current line and select the word under the cursor.
  let cmake_project_filename = getline('.') let fullpath = s:cmake_project_var(cmake_project_filename) let highlight_pattern = substitute(fullpath, '[.]', '\\.', '') let highlight_pattern = substitute(highlight_pattern, '[/]', '\\/', '') exec "match" "ErrorMsg /" . highlight_pattern . "/" 


Define the directory in which the file is located by indentation. If the file is an element of the n-th level, then the directory in which the file is located is the nearest element at the top with indents of the n-1st level.
  let level = s:cmake_project_level(cmake_project_filename) let level -= 1 let finding_line = s:cmake_project_find_parent(level, line('.')) while level > -1 let path = s:cmake_project_var(getline(finding_line)) let fullpath = path . fullpath let level -= 1 let finding_line = s:cmake_project_find_parent(level, finding_line) endwhile let fullpath = "/" . fullpath "     


Open the required file
  if filereadable(fullpath) wincmd l exec 'e' fullpath setf cpp endif endif 


The result was:

image

Source codes to take here: image

Source: https://habr.com/ru/post/153873/


All Articles