We are writing a plugin to support cmake projects under vim
- Tutorial
- Recovery mode
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:
I found in DependInfo.cmake a variable with this content
We find all the DependInfo.cmake files in the directory and find the full paths to the files using the Perl script.
Full source here .
Before binding these functions to the plugin, we will examine the directory hierarchy in ~ / .vim.
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:
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”.
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.
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.
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.
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).
Bind the s: cmake_project_window () function to the CMakePro command
We also need a command to generate cmake files.
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.
In order for the function to work only with the panel buffer, we check its name before execution.
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
В итоге получилось:
Исходники брать здесь: