How to detect where the cursor is?

153 views
Skip to first unread message

Teemu Likonen

unread,
Jul 24, 2008, 9:04:35 AM7/24/08
to vim...@googlegroups.com
I'm doing some personal additions to "mail" filetype plugin. Among some
other things my ~/.vim/after/ftplugin/mail.vim file contains this:

augroup Mail
autocmd!
autocmd! CursorMoved,CursorMovedI <buffer> call <SID>FormatOptions()
augroup END

function! s:FormatOptions()
if getline('.') =~ '\m^\s\{0,3}> '
setlocal fo-=a fo-=w
else
setlocal fo+=aw
endif
endfunction

The idea is this: When cursor is on a line which starts with "> " (i.e.,
a mail quotation line) then I want certain settings for 'formatoptions'
('fo'). When cursor is on other lines I want other kind of 'fo'
settings. This already works.

There is one problem. At the beginning of file there are mail headers
such as From, To, Subject etc. Afters headers there is an empty line and
after that the mail body begins. I'd want to detect if the cursor is on
the header area and have different settings applied (at least fo-=a
fo-=w).

So the question is how can I detect if the cursor is on the mail header
area? More generally: how can I detect if the cursor is on certain area in
the buffer? For example, below the line that matches certain pattern and
probably also above the line that matches certain other pattern.

Thanks

Ivan Tishchenko

unread,
Jul 24, 2008, 9:18:51 AM7/24/08
to vim...@googlegroups.com
You could use line('.') to get line number, and then getline({lnum}) to
inspect nearby lines.

Also, very nice trick can be done with search(). Like this:
search('foo \%#bar', 'Wcbn')
will return you positive (>0) value, if the cursor is positioned at
letter 'b' in 'foo bar'. \%# means 'cursor position'. And all the
power of vim regexp to help you. :)

WBR,
Ivan.

Ivan Tishchenko

unread,
Jul 24, 2008, 9:21:10 AM7/24/08
to vim...@googlegroups.com
Sorry, I've forgotten.

To speed up that trick with search(), use third arg. of search, stopline:
search('foo \%#bar', 'Wcbn', line('.'))

Cyril Slobin

unread,
Jul 24, 2008, 9:23:12 AM7/24/08
to vim...@googlegroups.com
On 7/24/08, Teemu Likonen <tlik...@iki.fi> wrote:

> So the question is how can I detect if the cursor is on the mail header
> area? More generally: how can I detect if the cursor is on certain area in
> the buffer? For example, below the line that matches certain pattern and
> probably also above the line that matches certain other pattern.

One possible solution: enable syntax highlighting and test for the
syntax type of the character under cursor. Highlighter have already
done a good job of classifying different parts of buffer for you.

--
http://slobin.pp.ru/ `When I use a word,' Humpty Dumpty said,
<cy...@slobin.pp.ru> `it means just what I choose it to mean'

Teemu Likonen

unread,
Jul 24, 2008, 10:25:36 AM7/24/08
to vim...@googlegroups.com
Ivan Tishchenko wrote (2008-07-24 17:18 +0400):

> You could use line('.') to get line number, and then getline({lnum})
> to inspect nearby lines.
>
> Also, very nice trick can be done with search(). Like this:


Cyril Slobin wrote (2008-07-24 17:23 +0400):

> One possible solution: enable syntax highlighting and test for the
> syntax type of the character under cursor. Highlighter have already
> done a good job of classifying different parts of buffer for you.


Your ideas sound interesting, thank you. I'd like to ask a bit more
concrete examples, though. I'm a Vimscript newbie and neither can
I construct very complex regexp patterns. So how can I answer the
question "is the cursor on the mail header area?"

I'm not asking anybody to write complete script for me but a bit more
concrete examples (and browsing Vim manuals) will probably help me to
find and understand the solution. Thanks for your patience. :)

Gary Johnson

unread,
Jul 24, 2008, 10:35:57 AM7/24/08
to vim...@googlegroups.com

I wrote a similar script a while back. Here's what I used to
determine the region that the cursor was in:

let syn_name = synIDattr(synID(line("."), col("."), 1), "name")

if syn_name =~ "^mailHeader" || syn_name == "mailEmail" || syn_name == "mailSubject"
let l:msg_part = "header"
let in_body = 0
elseif syn_name == "mailSignature"
let l:msg_part = "signature"
let in_body = 0
else
let l:msg_part = "body"
let in_body = 1
endif

I'm not completely happy with the behavior of the rest of the code,
though, so I won't suggest the rest as a solution.

HTH,
Gary

Christian Brabandt

unread,
Jul 24, 2008, 1:53:55 PM7/24/08
to vim...@googlegroups.com
Hi Teemu!

On Thu, 24 Jul 2008, Teemu Likonen wrote:

> The idea is this: When cursor is on a line which starts with "> " (i.e.,
> a mail quotation line) then I want certain settings for 'formatoptions'
> ('fo'). When cursor is on other lines I want other kind of 'fo'
> settings. This already works.
>
> There is one problem. At the beginning of file there are mail headers
> such as From, To, Subject etc. Afters headers there is an empty line and
> after that the mail body begins. I'd want to detect if the cursor is on
> the header area and have different settings applied (at least fo-=a
> fo-=w).

You could use the information that is provided by the syntax
highlighting. Check the manual for synID() and synIDattr().
In the help for synID() look at the example. You could basically
use this example and check what region the cursor is on.

:h synID()
:h synIDattr()

regards,
Christian
--
:wq

Teemu Likonen

unread,
Jul 24, 2008, 5:13:25 PM7/24/08
to vim...@googlegroups.com
Teemu Likonen wrote (2008-07-24 16:04 +0300):

> I'm doing some personal additions to "mail" filetype plugin.

Thank you everybody! I tried different things and eventually found
a solution I'm really happy with. Indeed the easiest way is to check
synIDattr() but for special kind of areas I needed my own pattern
matching anyway. I ended up using a custom function everywhere because
there was a certain corner case where synIDattr() was not optimal.

For the sake of contributing back, below is my working prototype. I'll
likely tweak it a bit more in the future but the idea is there. This
detects mail headers, quotation and a diff (---/+++) area -- and, of
course, it sets different options for different areas.

The question "if cursor is on certain area" is answered with function
s:CheckArea(start,end). Both parameters are patterns. The First pattern
should match the first line of the wanted text are; the second should
match the last. The search starts upward from the cursor position and
when constructing patterns you should probably think that you are below
the text area in question. The text area detection can be turned on and
off with \ma1 and \ma0, respectively (\ = <LocalLeader>).

Have fun! :)


PS. Even though this does what I need of course I'm still open to any
ideas you might have. No doubt there are many who can do this a lot
better than I.

" ~/.vim/after/ftplugin/mail.vim

let s:defaults = 'setlocal tw=72 ts=8 sts=4 sw=4 fo='.&fo
execute s:defaults

nnoremap <buffer> <LocalLeader>ma1 :call <SID>MailAreaDetect_On()
\ <bar> echo 'MailAreaDetect On'<CR>
nnoremap <buffer> <LocalLeader>ma0 :call <SID>MailAreaDetect_Off()
\ <bar> echo 'MailAreaDetect Off'<CR>

function! s:MailAreaDetect_On()
silent autocmd! MailAreaDetect CursorMoved,CursorMovedI
\ <buffer> call <SID>AreaOptions()
endfunction

function! s:MailAreaDetect_Off()
silent autocmd! MailAreaDetect
execute s:defaults
endfunction

augroup MailAreaDetect
autocmd!
call <SID>MailAreaDetect_On()
augroup END

function! s:AreaOptions()
execute s:defaults
if <SID>CheckArea('\v^From( |: ).*\n','\v^$')
"echo 'Header'
setlocal fo-=a fo-=w fo-=t sts=0 sw=8 noet
elseif getline('.') =~ '\m^\s*>'
"echo 'Quotation'
setlocal fo-=a fo-=w
elseif <SID>CheckArea('\m^--- .*\n^+++ ','\v(^$|\n^-- $)')
"echo 'Patch'
setlocal fo-=a fo-=w fo-=t sts=0 sw=8 noet
else
"echo 'My text'
setlocal fo+=aw et
endif
endfunction

function! s:CheckArea(start, end)
return (search(a:start,'bcnW')-line('.')) >
\ (search(a:end,'bnW')-line('.'))
endfunction

Reply all
Reply to author
Forward
0 new messages