Vim substitutes empty string when exception is thrown inside sub-replace expression - vim

Update
Submitted the issue, has been fixed in Vim 8.1.1061.
I'm writing a short script for adding delay to .srt files in Vim.
The core functionality could practically be a one-liner substitute command searching for timecodes and referring to a helper function which converts its argument to millisecs, do the math, then converts the result back to timecode format.
However, I'd like to do an additional check - if any of the new values are negative, then throw an exception, which I'd like to handle. Given a well-formed subtitle file with the timecode lines in the right order, we can assume the exception will be thrown right at the very first match, if at all, so no state change to worry about - at least that's what I thought.
fun! s:DelayTimecodes(delay)
try
let saved_view = winsaveview()
let timecode = '\v\d{2}:\d{2}:\d{2},\d{3}'
exe 'keepjumps keeppatterns %s/' . timecode . '/'
\ . '\=s:DelayedTimecode(submatch(0), a:delay)/g'
catch 'illegal timecode value'
redraw | echo 'Cannot apply: the given delay time would result'
\ . ' in negative timecode value(s)'
finally
call winrestview(saved_view)
endtry
endfun
But when an exception is thrown by DelayedTimecode inside the \= expression, the matches on the first matching line are still replaced, by an empty string. As a temporary solution, I've written a loop, doing the substitution line by line, creating the whole delayed line first (that might throw an exception), and passing that to the substitute command. This works fine, but feels ridiculously over-engineered.
What is the reason behind this? Is there any way to circumvent this behavior, without undoing the substitution command & clearing the history, which is kinda messy?

Exceptions don't work well with sub-replace-expression. You could raise an issue at Vim's bug tracker, or directly discuss this on the vim_dev mailing list, but chances are this is classified as implementation-specific behavior, and put on the back burner.
The easiest workaround would be to just return the original match (submatch(0)) instead of throwing the exception (making the :substitute a no-op), and setting an internal flag that makes the expression do the same for each following invocation of the expression within the :substitute. Then, afterwards, you can check that flag and print the error message, just as you're now doing in :catch.
If you want to avoid any buffer modification (also marking it as modified), you'd have to :call search() to locate the first timecode instance, extract it (getline(), matchstr()), and do the check before the actual :substitute.

Related

Vim: report error on a single line

When an error is raised inside a Vimscript function, Vim reports an error in the following format:
Error detected while processing function <SNR>My_Function:
line X:
EXXX: Error Message Here
This is very distracting. Ideally, I would like this to be formatted in some way to fit on a single line, but if that's not possible, then I just want the last EXXX line with the actual error. Is it possible to change this?
I don't like that behavior neither, but it is how it is, and cannot be changed.
Your functions need to :catch any errors, and convert this into the desired one-line error message:
function! s:My_Function()
try
" commands here
catch /^Vim\%((\a\+)\)\=:/
echohl ErrorMsg
echomsg substitute(v:exception, '^\CVim\%((\a\+)\)\=:', '', '')
echohl None
endtry
endfunction
This is how most plugins handle it. The behavior isn't completely like that of built-in commands, though: The error is effectively "swallowed", and subsequent commands (e.g. in a sequence cmd1 | call <SID>My_Function() | cmd3) will be executed despite the error. For most uses this is okay, and most users probably are even unaware of this.
better alternative
To make the custom command behave like a built-in one, the error message needs to be returned to the custom command or mapping, and :echoerr'd there. In my ingo-library plugin, I have corresponding helper functions:
function! s:My_Function()
try
" commands here
return 1 " Success
catch /^Vim\%((\a\+)\)\=:/
call ingo#err#SetVimException()
return 0 " Failure, use stored error message.
endif
endfunction
if ! <SID>My_Function() | echoerr ingo#err#Get() | endif
There is another approach to the problem: error messages can be decoded.
In my lh-vim-lib, I've defined a function that decodes the last error message and displays the result in the qf-window.
Given lh-vim-lib is installed, you've to add something like
command! WTF call lh#exception#say_what()
to your .vimrc.
The feature by itself has been inspired from https://github.com/tweekmonster/exception.vim, the difference is that I've used stuff I've already implemented in my library plugin in order to:
support localized messages
support autoloaded functions, even when # isn't in &isk (which is possible depending on the settings associated to the current filetype/buffer)
have as few loops as possible
factorize code with my logging, and unit testing, and DbC frameworks.
Near the end of the screencast I've recorded to demonstrate my Dbc framework, I've used :WTF to display the last error in a human friendly manner.

Stacked IF NOTs

dd = (re.findall(r'">County<(.*?)</td>', lmth2))
if not dd:
dd = (re.findall(r'<small>Location in <a(.*?)</td>',lmth2)
if not dd:
county.append("")
fin.append(name[o]+';'+address[o]+';'+city[o]+';'+stateorg[o]+';'+county[o]+';'+phone[o]+';'+website[o])
continue
else:
ee = (re.findall(r'title="(.*?) County', dd[0]))
county.append(ee[0])
fin.append(name[o]+';'+address[o]+';'+city[o]+';'+stateorg[o]+';'+county[o]+';'+phone[o]+';'+website[o])
I'm trying to stack IF NOTs together to find the find result. If
If dd does not come up as a match then I want to try the second scenario. If that doesn't come up with a match then I want, for right now, for it to show it as nothing comes up and to set up the line to be saved to the file. If it does come up with a match than I need it continue on find the second level of the search with ee = (re.findall...)
Until I found the second possible scenario to search for everything had been working find but then I found another possible thing I need to look for so I'm trying to add it into the program and I keep getting an Invalid Syntax coming back on the : on the second
if not dd:
This is one that is WAY beyond me. I'm not use to having this trouble with stacked if's when I use to use VB6. Python seems to be handling things a bit differently.
Indentation is syntactically significant in Python. Unlike languages where blocks are determined by tokens like begin and end or { and }, in Python blocks are determined by indents and dedents.
As such, you cannot arbitrarily indent Python code. Whenever Python encounters a line that is indented further than the line above it, it expects that to be the first line of a new block. The issue you have is that inside the first if statement you have already established the indentation level of that block with the line dd = ..., and then you've indented the next if statement even further, while it should be at the same indentation level.
If you remove the extra indent on the second if not dd: line, it should no longer have a syntax error.

What's the meaning of some advanced patterns in vim errorformat? (%s, %+, %\\#=)

I tried reading :help errorformat and googling (mostly stackoverflow), but can't understand some of the patterns mentioned there:
%s - "specifies the text to search for to locate the error line. [...]"
um, first of all, trying to understand the sentence at all, where do I put the "text to search", after the %s? before it? or, I don't know, does it maybe taint the whole pattern? WTF?
secondly, what does this pattern actually do, how does it differ from regular text in a pattern, like some kinda set efm+=,foobar? the "foobar" here is for me also "text to search for"... :/
%+ - e.g. I I've seen something like that used in one question: %+C%.%#
does it mean the whole line will be appended to a %m used in an earlier/later multiline pattern? if yes, then what if there was not %.%# (== regexp .*), but, let's say, %+Ccont.: %.%# - would something like that work to capture only stuff after a cont.: string into the %m?
also, what's the difference between %C%.%# and %+C%.%# and %+G?
also, what's the difference between %A and %+A, or %E vs. %+E?
finally, an example for Python in :help errorformat-multi-line ends with the following characters: %\\#=%m -- WTF does the %\\#= mean?
I'd be very grateful for some help understanding this stuff.
Ah, errorformat, the feature everybody loves to hate. :)
Some meta first.
Some Vim commands (such as :make and :cgetexpr) take the output of a compiler and parse it into a quickfix list. errorformat is a string that describes how this parsing is done. It's a list of patterns, each pattern being a sort of hybrid between a regexp and a scanf(3) format. Some of these patterns match single lines in the compiler's output, others try to match multiple lines (%E, %A, %C etc.), others keep various states (%D, %X), others change the way parsing proceeds (%>), while yet others simply produce messages in the qflist (%G), or ignore lines in the input (%-G). Not all combinations make sense, and it's quite likely you won't figure out all details until you look at Vim' sources. shrug
You probably want to write errorformats using let &erf='...' rather than set erf=.... The syntax is much more human-friendly.
You can experiment with errorformat using cgetexpr. cgetexpr expects a list, which it interprets as the lines in the compiler's output. The result is a qflist (or a syntax error).
qflists are lists of errors, each error being a Vim "dictionary". See :help getqflist() for the (simplified) format.
Errors can identify a place in a file, they can be simple messages (if essential data that identifies a place is missing), and they can be valid or invalid (the invalid ones are essentially the leftovers from parsing).
You can display the current qflist with something like :echomsg string(getqflist()), or you can see it in a nice window with :copen (some important details are not shown in the window though). :cc will take you to the place of the first error (assuming the first error in qflist actually refers to an error in a file).
Now to answer your questions.
um, first of all, trying to understand the sentence at all, where do I put the "text to search", after the %s? before it?
You don't. %s reads a line from the compiler's output and translates it to pattern in the qflist. That's all it does. To see it at work, create a file efm.vim with this content:
let &errorformat ='%f:%s:%m'
cgetexpr ['efm.vim:" bar:baz']
echomsg string(getqflist())
copen
cc
" bar baz
" bar
" foo bar
Then run :so%, and try to understand what's going on. %f:%s:%m looks for three fields: a filename, the %s thing, and the message. The input line is efm.vim:" bar:baz, which is parsed into filename efm.vim (that is, current file), pattern ^\V" bar\$, and message baz. When you run :cc Vim tries to find a line matching ^\V" bar\$, and sends you there. That's the next-to-last line in the current file.
secondly, what does this pattern actually do, how does it differ from regular text in a pattern, like some kinda set efm+=,foobar?
set efm+=foobar %m will look for a line in the compiler's output starting with foobar, then assign the rest of the line to the message field in the corresponding error.
%s reads a line from the compiler's output and translates it to a pattern field in the corresponding error.
%+ - e.g. I I've seen something like that used in one question: %+C%.%#
does it mean the whole line will be appended to a %m used in an earlier/later multiline pattern?
Yes, it appends the content of the line matched by %+C to the message produced by an earlier (not later) multiline pattern (%A, %E, %W, or %I).
if yes, then what if there was not %.%# (== regexp .*), but, let's say, %+Ccont.: %.%# - would something like that work to capture only stuff after a cont.: string into the %m?
No. With %+Ccont.: %.%# only the lines matching the regexp ^cont\.: .*$ are considered, the lines not matching it are ignored. Then the entire line is appended to the previous %m, not just the part that follows cont.:.
also, what's the difference between %C%.%# and %+C%.%# and %+G?
%Chead %m trail matches ^head .* trail$, then appends only the middle part to the previous %m (it discards head and trail).
%+Chead %m trail matches ^head .* trail$, then appends the entire line to the previous %m (including head and trail).
%+Gfoo matches a line starting with foo and simply adds the entire line as a message in the qflist (that is, an error that only has a message field).
also, what's the difference between %A and %+A, or %E vs. %+E?
%A and %E start multiline patterns. %+ seems to mean "add the entire line being parsed to message, regardless of the position of %m".
finally, an example for Python in :help errorformat-multi-line ends with the following characters: %\\#=%m -- WTF does the %\\#= mean?
%\\#= translates to the regexp qualifier \#=, "matches preceding atom with zero width".

vim script exec pastes unformated text

In all honesty the title is bad. Consider the following 5 lines:
function Example()
let ## = "-_-"
execute "normal! ]P"
call cursor(line('.'), col('.')-1)
endfunction
When this function is called, I expect to get -_- as output and the cursor should be moved to the left, meaning that it is at the third character, so if I press a key, like I for example I will get -_i-
What happens in reality is quite different (and to some degree interesting)
The output the first time this is called is - _- and after that it's _--
I assume that "cursor" shifts the position of the word under the cursor.
Basically: Why is it happening? How can I get the desired effect ?
Very important edit:
Apperantly the problem isn't in the plugins. When I go for:
call Example()
It works flawlessly. Thing is it is supposed to be triggered by a key. I have currently bound it like so:
inoremap ' <C-O>: call Example()<CR>
So now I am thinking that something in the mapping is broken...
I cannot reproduce your strange behavior. I get ----_-_-_-_- on repeated invocations, as expected. I again suspect there are plugins at work. Try with vim -N -u NONE. As this is a paste, there's little that could influence this function, though. You could try to workaround via :noautocmd call Example(), but I'd rather try to find the root cause of this disconcerting strangeness.
The "-_-" is not a full line, so the ]P (paste with adapted indent) has no effect here. You could just as well have used P.
To move the cursor one left, rather use :normal! h. The subtraction from col('.') again only works for single-byte ASCII characters.

Vim errorformat: include part of expression in message string

With vim's errorformat syntax, is there any way to use part of the message in filtering results?
As an example, some linker errors don't have anything explicit to distinguish them as an error on the line, other than the error itself:
/path/to/foo.cpp:42: undefined reference to 'UnimplementedFunction'
or
/path/to/foo.cpp:43: multiple definition of 'MultiplyDefinedFunction'
Using an errorformat of:
set efm=%f:%l:\ %m
would catch and display both of these correctly, but will falsely match many other cases (any line that starts with "[string]:[number]: ").
Or, explicitly specifying them both:
set efm=
set efm+=%f:%l:\ undefined\ reference\ to\ %m
set efm+=%f:%l:\ multiple\ definition\ of\ %m
removes the false positives, but the 'message' becomes far less useful -- the actual error is no longer included (just whatever is after it).
Is there anything in the syntax I'm missing to deal with this situation?
Ideally I'd like to be able to say something along the lines of:
set efm+=%f:%l:\ %{StartMessage}undefined\ reference\ to\ %*\\S%{EndMessage}
set efm+=%f:%l:\ %{StartMessage}multiple\ definition\ of\ %*\\S%{EndMessage}
... where everything matched between StartMessage and EndMessage is used as the error's message.
The errorformat can also use vim's regular expression syntax (albeit in a rather awkward way) which gives us a solution to the problem. We can use a non-capturing group and a zero-width assertion to require the presence of these signaling phrases without consuming them. This then allows the %m to pick them up. As plain regular expression syntax this zero-width assertion looks like:
\%(undefined reference\|multiple definition\)\#=
But in order to use it in efm we need to replace \ by %\ and % by %% and for use in a :set line we need to escape the backslashes, spaces and vertical bar so we finally have:
:set efm=%f:%l:\ %\\%%(undefined\ reference%\\\|multiple\ definitions%\\)%\\#=%m
With that the error file
/path/to/foo.cpp:42: undefined reference to 'UnimplementedFunction'
/path/to/foo.cpp:43: multiple definition of 'MultiplyDefinedFunction'
notafile:123: just some other text
comes out as the following in :copen:
/path/to/foo.cpp|42| undefined reference to 'UnimplementedFunction'
/path/to/foo.cpp|43| multiple definition of 'MultiplyDefinedFunction'
|| notafile:123: just some other text
I've been using sed to rewrite the output in cases like this where I want to get some arbitrary output that's not nessicarily homogenous into the quickfix window.
You could write make.sh that fires off make (or whatever your're using to build) and trims off stuff you're not concerned with:
make | sed '/undefined reference\|multiple definition/!d'
(Deletes lines not containing 'undefined reference' or 'multiple definition')
If that's going to get too unweildly because of the number of error strings you care about, you could do the inverse and just kill stuff you don't care about:
make | sed 's/some garbage\|other useless message//'
then :set makeprg=make.sh in vim

Resources