Loading ...
Sorry, an error occurred while loading the content.
 

Making expr-mappings using getchar() work inside a macro

Expand Messages
  • Benjamin Fritz
    I have remapped f, F, t, T, , , and ; to also highlight the character they match, using mappings so that they work well in not only normal mode, but
    Message 1 of 3 , Jun 21, 2010
      I have remapped f, F, t, T, ',', and ; to also highlight the character
      they match, using <expr> mappings so that they work well in not only
      normal mode, but also visual and operator-pending modes.

      These mappings use getchar() to get the character to jump to, inspired
      by the example given in :help getchar().

      To my dismay, I have discovered that these mappings did not work when
      called while running a recorded macro with the @ command. They would
      just hang waiting for a character. For a while, I resorted to manually
      editing the register to use ':normal! f' for example, instead of just
      'f'.

      I recently decided to fix the problem, and believe I have tracked down
      the root cause. :help :map-<expr> says:

      > - You can use getchar(), but the existing typeahead isn't seen and new
      > typeahead is discarded.

      With this in mind, I set up mappings for q and @, that will keep track
      of whether a macro is running:

      " keep track of whether we're running a macro or not
      let g:running_macro=0
      nnoremap <expr> @ ':<C-U>let g:running_macro+=v:count1<CR>'.v:count1.'@'
      nnoremap q :call EndRecording()<CR>

      " if we're running a macro, this should be a no-op, otherwise we're recording
      " one (or starting to record) and we just need to use the normal functionality
      " of q. Unfortunately q doesn't work inside mappings so we need to use
      " feedkeys()
      function! EndRecording()
      if g:running_macro > 0
      let g:running_macro -= 1
      else
      call feedkeys('q', 'n')
      endif
      endfunction

      I then test for g:running_macro > 0 in my mappings for f, F, t, and T,
      and do not use getchar() at all in these cases (and therefore skip the
      highlight as well).

      I'm not fully satisfied with this solution, because I find it ugly to
      hijack q and @ in this way, and additionally it doesn't work when the
      macro is terminated by an error (for example, when running a recursive
      macro, or a macro with a very large count as a lazy way to execute on
      an entire file), because the final 'q' in the mapping that would set
      g:running_macro to zero never gets executed.

      For now, I have a workaround of a third mapping to just set
      g:running_macro to zero explicitly, but I'd like to find a better way
      to do this.

      Any ideas? Am I right about the root cause of the problem?

      Full set of mappings attached. This was tricky enough, I'll probably
      submit them as a tip on the wiki or a plugin when it's all done.

      --
      You received this message from the "vim_use" maillist.
      Do not top-post! Type your reply below the text you are replying to.
      For more information, visit http://www.vim.org/maillist.php
    • Ben Fritz
      ... The mappings were used initially, because for whatever reason, :normal! f... and friends acts as an exclusive motion, when my mappings need to be
      Message 2 of 3 , Jun 22, 2010
        On Jun 21, 3:41 pm, Benjamin Fritz <fritzophre...@...> wrote:
        > I have remapped f, F, t, T, ',', and ; to also highlight the character
        > they match, using <expr> mappings so that they work well in not only
        > normal mode, but also visual and operator-pending modes.
        >
        > These mappings use getchar() to get the character to jump to, inspired
        > by the example given in :help getchar().
        >

        The <expr> mappings were used initially, because for whatever
        reason, :normal! f... and friends acts as an exclusive motion, when my
        mappings need to be inclusive just like normal-mode f, F, t, and T.

        I have just discovered :help o_v which should allow me to dispense
        with the <expr> mapping for normal and operator-pending mode, by using
        "normal! v..." instead of "return ..." in operator-pending mode
        (normal mode works fine without the <expr> mapping). However, I'm
        still stumped on how to do this with visual mode. I'd rather not get
        into a complicated solution of dropping out of visual mode, finding
        the character, going back to the *same* visual mode, and jumping to
        the beginning with `<. Can anyone think of a good visual-mode
        solution? I suppose I might be able to make something work by
        appending l/h to the end of the command in visual mode...but that's
        another ugly hack I'd rather avoid.

        --
        You received this message from the "vim_use" maillist.
        Do not top-post! Type your reply below the text you are replying to.
        For more information, visit http://www.vim.org/maillist.php
      • Ben Fritz
        ... I m not sure what I used to be doing in visual mode that didn t work. Here s what I settled on, for posterity (though it doesn t answer my original
        Message 3 of 3 , Jun 23, 2010
          On Jun 22, 12:01 pm, Ben Fritz <fritzophre...@...> wrote:
          >
          > The <expr> mappings were used initially, because for whatever
          > reason, :normal! f... and friends acts as an exclusive motion, when my
          > mappings need to be inclusive just like normal-mode f, F, t, and T.
          >

          I'm not sure what I used to be doing in visual mode that didn't work.
          Here's what I settled on, for posterity (though it doesn't answer my
          original question):

          " Highlight characters found by f, F, t, and T.
          " Unhighlights on a cursorhold.
          "
          " All versions with matchadd also have <expr> mappings so no need to
          check for
          " both (matchadd is needed to highlight the char, expr to get the ,
          and ;
          " mappings to play nicely with visual and operator pending mode).
          "
          " Also map <leader>f to show the highlight.
          if exists('*matchadd')
          nnoremap f :<C-U>call FindChar('f', v:count1)<CR>
          nnoremap F :<C-U>call FindChar('F', v:count1)<CR>
          nnoremap t :<C-U>call FindChar('t', v:count1)<CR>
          nnoremap T :<C-U>call FindChar('T', v:count1)<CR>
          onoremap f :<C-U>call FindChar('f', v:count1, 'v')<CR>
          onoremap F :<C-U>call FindChar('F', v:count1, 'v')<CR>
          onoremap t :<C-U>call FindChar('t', v:count1, 'v')<CR>
          onoremap T :<C-U>call FindChar('T', v:count1, 'v')<CR>
          xnoremap f :<C-U>call FindChar('f', v:count1, 'gv')<CR>
          xnoremap F :<C-U>call FindChar('F', v:count1, 'gv')<CR>
          xnoremap t :<C-U>call FindChar('t', v:count1, 'gv')<CR>
          xnoremap T :<C-U>call FindChar('T', v:count1, 'gv')<CR>

          nnoremap <expr> , FindLastChar(',')
          nnoremap <expr> ; FindLastChar(';')
          onoremap <expr> , FindLastChar(',')
          onoremap <expr> ; FindLastChar(';')
          xnoremap <expr> , FindLastChar(',')
          xnoremap <expr> ; FindLastChar(';')

          " <C-U> in normal mode to remove any count, in visual mode to remove
          range
          " Do not provide this command in op-pending mode, it doesn't do
          anything
          nnoremap <Leader>f :<C-U>call HighlightFoundChar()<CR>
          xnoremap <Leader>f :<C-U>call HighlightFoundChar()<CR>gv

          " highlight the last found character
          function! HighlightFoundChar()
          if &hlsearch
          if exists('w:fFtT_command_highlight')
          call matchdelete(w:fFtT_command_highlight)
          endif
          let w:fFtT_command_highlight = matchadd(
          \'Search',
          \'\V\%' . line('.') . "l".escape(g:last_found_char,'/\'),
          \11)
          endif
          endfunction

          " Set the "last found character" and highlight it.
          function! FindChar(op, count, ...)
          echo "Enter character to find"
          let g:last_found_char = nr2char(getchar())
          call HighlightFoundChar()
          " clear the echo
          echo ''
          let cmdprefix=''
          if a:0
          let cmdprefix=a:1
          endif
          exec 'normal! ' . cmdprefix . a:count . a:op . g:last_found_char
          endfunction

          " Highlight the "last found character" if it exists and pass on the
          input
          " operation which should be either , or ;
          function! FindLastChar(op)
          if exists('g:last_found_char') && g:last_found_char != ''
          call HighlightFoundChar()
          " clear the echo
          echo ''
          endif
          return a:op
          endfunction
          endif

          --
          You received this message from the "vim_use" maillist.
          Do not top-post! Type your reply below the text you are replying to.
          For more information, visit http://www.vim.org/maillist.php
        Your message has been successfully submitted and would be delivered to recipients shortly.