Vi and Vim Asked on November 20, 2021
I would like to define a new command for C++ files/buffers (ft=cpp
).
I shall explain the requirements through the example below. The newly defined command :stripdbg
should strip the surrounding dbg()
text and should accept an address range. e.g :10,20stripdbg
, :.,$stripdbg
, :%stripdbg
etc should be possible and work as expected.
#include "dbg.h"
#include <vector>
int factorial(int n) {
if (dbg(n <= 1)) {
return dbg(1);
} else {
return dbg(n * factorial(n - 1));
}
}
int main() {
std::string message = "hello";
dbg(message); // [example.cpp:15 (main)] message = "hello" (std::string)
const int a = 2;
const int b = dbg(3 * a) + 1; // [example.cpp:18 (main)] 3 * a = 6 (int)
std::vector<int> numbers{b, 13, 42};
dbg(numbers); // [example.cpp:21 (main)] numbers = {7, 13, 42} (size: 3)
// (std::vector<int>)
dbg("this line is executed"); // [example.cpp:23 (main)] this line is executed
factorial(4);
return 0;
}
How can we achieve this through .vimrc
settings?
%StripDbg()
The dbg.h
project is extremely helpful for quick debugging without the need for setting up/configuring and stepping through a command-line gdb
process for a C++ codebase. For instance, this proves to be extremely helpful for one-off debugging in the ‘coding test’ round of an interview for a software development position. Quite a few tech companies use Codility for timed coding tasks. For such a use, offline development and quick debugging using dbg
works perfectly. When the code works as expected, then we just need to strip out the dbg()
calls and copy it into the browser, so that the online compiler in codility does not error out during grading.
So, my expected output after running :%StripDbg
shall be,
#include "dbg.h"
#include <vector>
int factorial(int n) {
if (n <= 1) { // __stripped debug__
return 1; // __stripped debug__
} else {
return n * factorial(n - 1); // __stripped debug__
}
}
int main() {
std::string message = "hello";
message; // __stripped debug__
const int a = 2;
const int b = 3 * a + 1; // __stripped debug__
std::vector<int> numbers{b, 13, 42};
numbers; // __stripped debug__
dbg("this line is executed"); // __Ideally, this line must be deleted__ This might be hard.
factorial(4);
return 0;
}
The only hard to tackle case might be dbg("this line is executed");
which should ideally be deleted , but if that is not possible, should be left untouched. I don’t wish to consider nested dbg()
cases.
In summary, I am looking for the opposite of surround.vim
for the specific dbg()
regex sequence, but available as a standard ex
command.
Unlike Maxin's answer, I'd recommend to use a ftplugin. They're really made to store the definition of filetype related mappings, commands, abbreviations, options, variables...
While we shall use <buffer>
with mappings and abbreviations, it's -b
with commands.
Now the problem is that you want to implement is quite complex. Vim main tool is regex. And regex don't support recursive patterns. Unfortunately, in your case, we need them to handle a variety of situations like dbg(f(x, g(y, z))) + dbg(u+v)
: we have parenthesis within calls to dbg
, and several uses of dbg
on a same line.
I'd say at best we could start from tricks like this one: https://old.reddit.com/r/vim/comments/5nf4pb/some_help_needed_about_a_vim_regex_for_a_plugin/dcbhavc/
If we simplify it (a little), it becomes.
" ~/.vimrc
filetype plugin on " at least!
" ~/.vim/ftplugin/cpp/StripDebug.vim
command! -bang -buffer -range StripDbg <line1>,<line2>call s:StripDbg("<bang>")
let s:SINGLEQUOTED = '''%(\.|[^\'']+)*'''
let s:DOUBLEQUOTED = '"%(\.|[^\"]+)*"'
let s:NEUTRAL = '[^''"[](){}]+'
let s:TERMINAL = s:NEUTRAL . '|' . s:SINGLEQUOTED . '|' . s:DOUBLEQUOTED
let s:BALANCED = '%(' . s:TERMINAL . ')*'
for n in range(5)
let s:PARENTHESIZED = '(' . s:BALANCED . ')'
let s:BALANCED = '%(' . s:TERMINAL . '|' . s:PARENTHESIZED . ')*'
endfor
function! s:StripDbg(bang) range abort
let confirm = a:bang == '!' ? '' : 'c'
exe a:firstline.','.a:lastline.'s/<dbg(('.s:BALANCED.'))/1/g'.confirm
endfunction
Unfortunately, it doesn't handle cases where you use gdb
to log variables. I guess you'd need to write a refactoring plugin for clang-tidy & co in order to detect that the expression returned by gdb
is used elsewhere.
I'm not sure whether the solution should not be to use macro at code level. Something like
template <typename T>
inline
decltype(auto) dbg(T && v) { // not sure about the best parameters and return type to use
#if ! defined(DEBUG)
spdlog::debug("DGB: {}", v);
#endif
return std::forward<T>(v);
}
Answered by Luc Hermitte on November 20, 2021
:Stripdbg
or :StripDbg
-buffer
parameter:augroup cpp_group | au!
au FileType cpp command! -buffer StripDbg call StripDbg()
augroup end
StripDbg()
function to do actual stripping-range
parameter and prepend <line1>,<line2>
range to a function call:augroup cpp_group | au!
au FileType cpp command! -buffer -range StripDbg <line1>,<line2>call StripDbg()
augroup end
a:firstline
, a:lastline
variables to go through the range and use help of vim-surround plugin (you have to install it).func! StripDbg() range
for lnr in range(a:firstline, a:lastline)
if getline(lnr) =~ 'dbgs*('
call search('dbgs*(', "W", line(lnr))
" delete dbg leaving cursor on a (
" here we use normal with ! to call default mappings
normal! dw
" use vim-surround mapping to remove ( )
" note, here we use normal without !, to call mapping of vim-surround
normal dsb
endif
endfor
endfunc
The result of :%StripDbg
is:
#include "dbg.h"
#include <vector>
int factorial(int n) {
if (n <= 1) { // __stripped debug__
return 1; // __stripped debug__
} else {
return n * factorial(n - 1); // __stripped debug__
}
}
int main() {
std::string message = "hello";
message; // __stripped debug__
const int a = 2;
const int b = 3 * a + 1; // __stripped debug__
std::vector<int> numbers{b, 13, 42};
numbers; // __stripped debug__
"this line is executed"; // __Ideally, this line must be deleted__ This might be hard.
factorial(4);
return 0;
}
NOTE, it doesn't take into account dbg("this line is executed")
should be removed. But you can add this kind of checks to the function yourself.
Also, if there are more then 1 dbg
calls on the same line, only first one would be stripped.
UPDATE
Simpler solution using help of the :global
command:
func! StripDbg()
" place cursor on dbg
call search('dbgs*(', "W")
" delete dbg with normal vim dw
" here we use normal with ! to call default mappings
normal! dw
" use vim-surround mapping to remove ( )
" note, here we use normal without !, to call mapping of vim-surround
normal dsb
endfunc
" Search every line that has dbg( or dbg ( and exectute StripDbg() function on that line
command! -buffer -range StripDbg <line1>,<line2>g/dbgs*(/call StripDbg()
Answered by Maxim Kim on November 20, 2021
Get help from others!
Recent Answers
Recent Questions
© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP