I am trying to modify this terrific VIM script however both the original and my modified version have a maddening bug in which sometimes the cursor is shown in the wrong place. The simplest example that I could make is the 71 line text file below. Note that whitespace is important when copying the file.
<?php
/**
* Some silly method
*
* #param string Some silly string
*/
function someFunction()
{
global $class, $cv, $go, $pae;
global $messages, $counters, $ltn;
global $sh, $sub, $temp;
$charsets = array(
'us',
'si',
'pr',
'op',
'co',
'pa',
'av',
'pr',
'al',
'pc',
'pe',
'pi',
'pp',
'su',
'qu',
'de',
'ze',
'xo',
'mo',
'wo',
'de',
'mo',
'de',
'mo',
'dr',
'mo',
'de',
'mo',
'ev',
'pa',
'so',
'ms',
'bu',
'at',
'cu',
'pr',
'de',
'mo',
'nv',
'nl',
'nf',
'ne',
'nq',
'nt'
);
}
This is the relevant .vimrc file with the function:
set cul
hi CursorLine term=none cterm=none ctermbg=20
set nu
set statusline+=%{WhatFunctionAreWeIn()}
set laststatus=2
fun WhatFunctionAreWeIn()
let strList = ["while", "foreach", "ifelse", "if else", "for", "if", "else", "try", "catch", "case"]
let foundcontrol = 1
let position = ""
normal mz
while (foundcontrol)
let foundcontrol = 0
" The idea here is to go back to non-whitespace character before
" the last hanging open { and to check if it is a close paran.
" If so, then go to the matching open paren and search for the
" preceding it.
" If not, then go ahead and check the keyword right there.
normal [{
?\S
let tempchar = getline(".")[col(".") - 1]
if (match(tempchar, ")") >=0 )
normal %
?\S
endif
let tempstring = getline(".")
for item in strList
if( match(tempstring,item) >= 0 )
let position = item . " - " . position
let foundcontrol = 1
break
endif
endfor
if(foundcontrol == 0)
normal `z
return tempstring.position
endif
endwhile
normal `z
return tempstring.position
endfun
Starting from the beginning of the file, press j repeatedly until you get to line 63. Note that the highlighted cursorline stays on the correct line (63) but the cursor is shown on line 55. Jumping directly to line 63 won't trigger the bug, only pressing j repeatedly until you get to that line will.
Why does that happen, and how can I fix it? Note that when the cursor appears to be in the wrong place, pressing ``z` does in fact snap the cursor to the correct location. This is on VIM 7.3.154 on Kubuntu 11.10.
EDIT:
I notice by testing in other installs (Debian, CentOS) that the bug is not determinate, it happens occasionally but not in the same place on every system! You can test this code by pressing j and paying attention to the cursor location in whatever PHP files that you might have strung about. I would say that about one line out of every hundred lines triggers the bug in which the cursor appears to be in the wrong place.
I'm slightly confused by the logic of this function, but I suspect it is the ?\S which is causing the problems. It is searching backwards for a non-whitespace character, and wrapping around to the bottom of the file once it has reached the top.
Try replacing both occurrences of ?\S with
call search('\S','bW')
(Here the b flag searches backwards, and W prevents wrapping around the file.)
EDIT (2nd attempt)
The function also causes lots of jumping around of the view. The root of this is continually setting the mark mz and jumping to and fro. A better approach in vimscripts is to use the following commands to save the current view (instead of normal mz):
let pos=getpos(".") " This saves the cursor position
let view=winsaveview() " This saves the window view
You can then use these to restore the view:
call cursor(pos) " This restores the cursor position to that of "pos"
call winrestview(view) " This restores the window view to that of "view"
So I would use call cursor(pos) instead of `z and call winrestview(view) just before the return commands. This ensures that the function doesn't modify the appearance of the window, and makes for more pleasant usage.
Hope this helps!
Related
I am trying to make a function in vimscript that will activate on every cursor movement in visual mode, i.e. it will automatically highlight all the matches of the current visual selection.
The mapping from https://vi.stackexchange.com/questions/20077/automatically-highlight-all-occurrences-of-the-selected-text-in-visual-mode:
xnoremap <silent> <cr> "*y:silent! let searchTerm = '\V'.substitute(escape(#*, '\/'), "\n", '\\n', "g") <bar> let #/ = searchTerm <bar> echo '/'.#/ <bar> call histadd("search", searchTerm) <bar> set hls<cr>
works perfectly for me but I am failing to implement it into function. This is what I tried:
function! HighlightVisual(mode)
if mode()=~#"^[vV\<C-v>]"
call feedkeys('"*y')
let searchTerm = '\V'.substitute(escape(#*, '\/'), "\n", '\\n', "g")
let #/ = searchTerm
echo '/'.#/
call histadd("search", searchTerm)
set hls
endif
endfunction
autocmd CursorMoved * call HighlightVisual(mode())
However, it is not working.
What am I doing wrong? I think the function is terminated each time a call is invoked but have no idea how to workaround that.
I have made it work. Firstly, I found from https://vi.stackexchange.com/questions/28404/vim-does-not-call-functions-correctly-when-wrapped-in-another-function that "Vim doesn't like updating Screen too often" so redraw was necessary after set hls. And secondly, y was forcing normal mode and gv was moving cursor triggering the autocmd CursorMoved inside the function thus making an infinite loop. I made a workaround by set eventignore=CursorMoved and resetting it back at the end of function. I am saving selected word to register h (as in highlight) but you can choose any by changing "hy and #h. Moreover, I added highlight toggle when not needed. So, if somebody wants to use the feature of automatically highlighting visual matches, here is the code:
function! HighlightVisual(mode)
if mode()=~#"^[vV\<C-v>]"
set eventignore=CursorMoved
normal "hy
normal gv
let searchTerm = '\V'.substitute(escape(#h, '\/'), "\n", '\\n', "g")
let #/ = searchTerm
call histadd("search", searchTerm)
set hls
redraw
set eventignore=""
endif
endfunction
autocmd CursorMoved * :call HighlightVisual(mode())
vnoremap <silent> <ESC> :<C-u>set nohlsearch<CR>
; just copy-paste it in your .vimrc/init.vim.
Big thanks to trusktr as I used his answer in https://vi.stackexchange.com/questions/20077/automatically-highlight-all-occurrences-of-the-selected-text-in-visual-mode to build my function I wanted for sooo long.
Happy coding!
I have a function to count and return the number of matches of some text:
function! count_matches()
redir => matches_cnt
silent! %s/\[\d*\]//gn
redir END
return split(matches_cnt)[0]
endfunction
I created a map to insert the return value of count_matches() at the current position:
noremap <C-A> Go[foo<C-R>=count_matches()<CR>]
However the cursor jumps to the beginning of the line after executing the silent %s/[\d*]//gn command. So when I press control+a vim inserts "[foo", then the function is being executed, the search command resets the cursor position and the return value is inserted at the beginning of the line resulting in "1][foo" instead of "[foo1]".
Can I somehow prevent count from changing the cursor position, or reset the cursor position after counting the matches?
The script also leads to an error, if the pattern is not found. How can I get the function to return 1 without an error for zero matches?
Even better then just to save the cursor position, is to save the complete viewport. (But that only works, if you do not change the window layout)
See :help winsaveview()
let wsv = winsaveview()
MoveTheCursorAround
call winrestview(wsv)
In your particular case, I would take another approach:
inoremap <expr> <f3> len(split(join(getline(1,'$'),"\n"), '\[\d\+\]',1))
Which takes the whole text, and splits it on the pattern \[\d\+\] and then counts how many elements there are. Or if you like to add some text:
inoremap <expr> <f3> '['.len(split(join(getline(1,'$'),"\n"), '\[\d\+\]',1)).']'
This will add the [ in front and ] after the number. Adjust the mapping key and text to your personal taste. (Note, you do not need the winsaveview() function, cause the cursor won't move).
It is perhaps not such a good idea to use that function on a multi MB text size. ;)
This is the same function, reworked to return 1 when there's no match:
function! count_matches()
redir => matches_cnt
try
silent! %s/\[\d*\]//gn
catch
echo 1
endtry
redir END
return split(matches_cnt)[0]
endfunction
See :help getpos()
let save_cursor = getpos(".")
MoveTheCursorAround
call setpos('.', save_cursor)
My solution:
function! CountWithCursorKeep(...)
let currentCursor = getcurpos()
let pattern = expand('<cword>')
if a:0 > 0 | let pattern = a:1 | endif
execute(':%s#' . pattern . '##gn')
call setpos('.', currentCursor)
endfunction
nmap ,ns :call CountWithCursorKeep(<C-R>/)<cr>
nmap ,nw :call CountWithCursorKeep(expand('<cword>'))<cr>
nmap ,nW :call CountWithCursorKeep(expand('<cWORD>'))<cr>
command! -nargs=? -bar -complete=tag CountMatch call CountWithCursorKeep(<f-args>)
You can use :CountMatch pattern to get how many times pattern occurs in current file.
I made a simple macro to increment a number in a json object like this:
{
image: 'images/2.jpg',
thumb: 'images/2-thumb.jpg',
big: 'images/2.jpg',
title: '',
description: '',
link: 'images/2.jpg'
},
with:
q, n, shift-v, down-till-end, p, move-to-numbers, c-a, return-to-top, q, 150#n
(Sorry if that's not the appropriate syntax to post vim macros here in SE)
And it works, but it makes the increment just until the 9th. What am I missing?
Thanks in advance.
EDIT:
I'm trying to reach something like this:
{
image: 'images/3.jpg',
thumb: 'images/3-thumb.jpg',
big: 'images/3.jpg',
title: '',
description: '',
link: 'images/3.jpg'
},
{
image: 'images/4.jpg',
thumb: 'images/4-thumb.jpg',
big: 'images/4.jpg',
title: '',
description: '',
link: 'images/4.jpg'
},
... until *nth* value
Assuming your cursor is on the first opening bracket, here is one way to do it:
qn " start recording in register n
V% " select from here to the closing bracket, linewise
y " yank the selection
% " jump to the closing bracket
p " paste after the current line
:'[,']norm <C-v><C-a> " executing the normal mode command <C-a>(1) on all the lines that we just pasted
q " stop recording
then do 150#n.
(1) <C-v><C-a> is used to insert a literal ^A.
Try this:
Enter visual mode and select the lines to be included in the macro execution an type:
:normal #n
Then, when you hit enter, the macro will be applied to selected lines
I gave it a try:
qqv%:s/\d\+/\=submatch(0)+1/^M[[yGGp
short explanation
qq "recording to register q
v% "select things between { and }
:s/\d\+/\=submatch(0)+1/^M "just do +1 to all numbers (selected range)
[[ "back to begin {
yG "yank till the end
Gp "paste at the end
then do 150#q
if you record the same macro, type ^M simply by Enter
if you assign the macro to #q type ^M by <c-v><enter>
btw, this won't win golf, since the function name submatch(0) is too long...:)
With my UnconditionalPaste plugin, once you have yanked the original block into a register, you can paste [N] auto-incremented blocks simply with [N]gPp (paste linewise with all numbers incremented).
The plugin also allows several other manipulations of the way the text is pasted.
This is a basic question, but I am having a hard time with it.
Basically, I have a callback-function assigned to the choices in a pop-up menu on a GUI. The code is as follows:
uicontrol(mainfigure, 'Style', 'popup',...
'String', 'A|B|C',...
'Position',[850 190 200 30],...
'Callback', #blockset);
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function [block] = blockset(hObj,evnt) %#ok<INUSD>
blockval = get(hObj,'Value');
if blockval == 1
block = 'A';
elseif blockval == 2
block = 'B';
elseif blockval == 3
block = 'C';
end
end
As you can see, it is simply assigning a string value to the different choices in the pop-up menu. I want to use these strings as input values to another function later in the script (which is also embedded in a uicontrol callback):
uicontrol(mainscreen, 'Style', 'pushbutton',...
'Position',[855 300 150 50],...
'String', 'START',...
'FontSize',10,'FontWeight','bold',...
'BackgroundColor', [.9 .9 .9],...
'CallBack', {#START_Callback, block});
The code as is doesn't work. But I can't figure out how to define the outputs for a uicontrol callback. I already defined "block" as the output for the blockset function, so how do I get the START_Callback to recognize it as input? Every time I try, it just tells me that "block" is an undefined function or variable.
Is there something I need to do with the " 'Callback', #blockset " line of code to get it to recognize the output from the function?
EDIT: Some cursory internet searching indicates that I probably have to use something like setappdata/getappdata, or another workaround method. However, I don't entirely understand the documentation on those. How do I use them in this situation?
the variable block would have to exist in the workspace when you do
uicontrol(mainscreen, 'Style', 'pushbutton',...
'Position',[855 300 150 50],...
'String', 'START',...
'FontSize',10,'FontWeight','bold',...
'BackgroundColor', [.9 .9 .9],...
'CallBack', {#START_Callback, block});
But it's the return value from your popup menu callback, so you can't do that, hence your matlab error.
To use setappdata and getappdata, you would need to store your popup menu's callback function's 'block' variable some figure's appdata property that would be visible to both callback functions, or if you want to be lazy, to the root figure.
e.g.
function [block] = blockset(hObj,evnt) %#ok<INUSD>
blockval = get(hObj,'Value');
if blockval == 1
block = 'A';
elseif blockval == 2
block = 'B';
elseif blockval == 3
block = 'C';
end
setappdata(0, 'block', block);
end
This will have stored the block variable to the root figure (that is, the main MATLAB window, denoted by 0), which really isn't a good thing to do as anything can change it as well. Instead you should try storing it to some handle graphics object that will be visible to both callbacks, such as your GUI figure. However, there's not enough information in your question for me to infer what you can use, so I'm using the root figure for illustrative purposes.
If you set the tag properties of your GUI objects, you can lookup their handles based on that, e.g. using h = findobj('Tag','my_tag') will give you the handle to the graphics object with the tag 'my_tag', which you can then set the appdata for via setappdata(h, 'var_name', var);. I would recommend using this instead of the root figure handle, as with the root figure you have no encapsulation.
With that said, then in your START_Callback function, instead of taking block as an input parameter, you'd use block = getappdata(0, 'block'); to get the root figure's block variable which you set in your blockset callback function. So your pushbutton declaration would become
uicontrol(mainscreen, 'Style', 'pushbutton',...
'Position',[855 300 150 50],...
'String', 'START',...
'FontSize',10,'FontWeight','bold',...
'BackgroundColor', [.9 .9 .9],...
'CallBack', #START_Callback);
and inside START_Callback:
function START_Callback(hObj,evnt)
block = getappdata(0, 'block');
%... other stuff
end
I've been trying to write a bit of Vimscript to call a function when I insert the same character twice, in my particular case I wanted that if you inserted semi colon twice for it to actually move the semi colon to the end of the line.
command! Semi call Semi()
inoremap ; <C-O>:Semi2<CR>
function! Semi()
let x = getpos(".")
" If we are in the last column..
if col(".")+1 == col("$")
let insert_semi = getline(".") . ";"
call setline(".", insert_semi)
let x[2] += 1
call setpos(".", x)
return
endif
let char = getline(".")[x[2] - 2]
if char == ";"
" if prev char was a semicolon also, remove and append to the end
else
" insert semicolon normally...
endif
endfunction
The problem I am having is when calling this function on the last column, you have to exit insert mode to call this function the cursor will go into normal mode and move the cursor to the last column. Is there any way to tell whether the cursor was appending to the end of the line or inserting before the last column and when function call is finished return it to the same position?
I am well aware that I could use an insert mapping on ;; however I dislike this behaviour, where Vim goes into a waiting for next key mode and does not display what you have written. This issue is not only to do with my problem listed but a more general problem which also occurs in the first column.
If your function does not use insert mode to append the ';' -- e.g. by pasting from a buffer -- you can use the gicommand to return to the place, where you exited insert mode.
I would advise against using i_CTRL-O, it triggers InsertLeave and InsertEnter events, which may affect other plugins. I would use :inoremap <expr> ; here. See :help :map-expr. Inside that expression (i.e. your function), record the current cursor position and compare it with the last recorded one. If it's next to it, return the keys to undo the inserts and redo at the end (<BS><BS><End>;), else just return ;.
You don't need a function for that:
inoremap ;; ;;<Esc>h"_xxm`$p``a
or cleaner:
inoremap ;; <Esc>m`A;<Esc>``a