We are writing a plugin to support cmake projects under vim

Today we’ll talk about creating add-ons for VIM.

Recently, I had the idea of ​​screwing support for cmake projects into it for easy file navigation. Of course, NERD Tree will quite cope with this task, but in the latter one cannot exclusively operate on project files.

Achtung: The author of the article first met Vim Script. It does not guarantee that you will not faint after reading the article. Leave any comments regarding the code in the comments.



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

And so, let's begin to implement.

If you dig deep into the bowels of files created with cmake, you can find where it stores a list of dependent source files. Let's do a search for the presence of strings with cppshniki in a half-wise way:

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


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


We find all the DependInfo.cmake files 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 = ;
        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 binding these functions to the plugin, we will examine the directory hierarchy in ~ / .vim.
  • plugin - plugins are placed here, which should be loaded every time VIM starts
  • ftplugin - plugins that run only for certain types of files are placed here
  • autoload - for storing common functions
  • syntax - syntax highlighting


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

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


Create a function to generate a file tree. Functions, as well as variables, can be created with different scopes (you can read about this here ). This function at the beginning creates a new window and buffer called “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 tree of files, declare a variable (with the scope inside the plugin) with the name of the buffer.
  let s:cmake_project_bufname = bufname("%")


Now bind the Perl script to the plugin. We place the script in the ~ / .vim / plugin / cmake-project directory so that use lib can find it. We get the list of files from the cmakeproject :: cmake_project_files function and place it in the Wim's 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


Next, based on these data, we build a tree. There are several data structures in vim: hashes and lists. Therefore, we represent the directory as a key in the 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 in the directory.

The code below converts a string of the form "/home/paranoik/main.cpp" to a structure of the form {'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 a function to display the tree in the buffer. Indentation in the form of spaces is determined depending on the hierarchy level (the function s: 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 . "/") "Выводим в виде "-/"
      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()
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 the cursor moves across the panel, the file should open under the cursor. We create the 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 execution.
function! s:cmake_project_cursor_moved()
  if exists('s:cmake_project_bufname') && bufname('%') == s:cmake_project_bufname
    
  endif
endfunction

Получаем данные текущей строки и выделяем слово под курсором.
    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 . "/"


Определим директорию, в которой находится файл через отступы. Если файл является элементом n-того уровня, то директория, в которой находится файл является ближайщий элемент сверху с отступами n-1-ого уровня.
    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 "формируем путь путем конкатенации элементов


Открываем необходимый файл
    if filereadable(fullpath)
      wincmd l
      exec 'e' fullpath
      setf cpp
    endif
  endif


В итоге получилось:

image

Исходники брать здесь: image

Also popular now: