7

I sometimes want to type an expression like this:

table[row][col] = std::max(table[row][col], other_table[row+1][col+1])

(this is C++ code. Unfortunately there's no way in C++ to eliminate the duplication except by writing a helper function like void update_max(auto& a, auto const& b) { a = std::max(a, b); } but that possibility won't be considered here)

How can I type the repeated part (table[row][col]) quickly?

For simplicity, you may assume the expression is "simple" (you can match it with some simple regex such as .*;(.\{-})=), but solution for the general case is okay too. (the problem is that C++ grammar is very complex)

  • 1
    C and C++ both allow macros, so something like this would also work in both: #define replacePairMax(t,r,c,o1,o2) (t)[r][c] = std::max((t)[r][c], (t)[(r)+(o1)][(c)+(o2)]), where the flurry of parens is to ensure complex argument expressions do not interact unexpectedly with order of operations. Usage here: replacePairMax(table, row, col, 1, 1). – Eric Towers Dec 16 '19 at 14:15
  • 2
    @EricTowers Suggesting macros should probably come with a disclaimer about how any good modern optimizing compiler will usually produce the same code if you just turn the macro into a function. I'd advise only using function-like macros in C if you need to insert type names or pieces of C code into very repetitive and hard-to-get-and-keep-right code, or to implement a "language feature" like libdill or coroutine.h do - unless compiler and performance limitations force it. – mtraceur Dec 16 '19 at 20:55
  • 1
    @EricTowers The point of trying to do this in Vim is to avoid making the code unidiomatic C/C++ by using unfamiliar construction. (In this case update_max is not that bad, actually) – user202729 Dec 17 '19 at 11:23
  • 2
    @EricTowers, macros are unidiomatic C++ (hint: std::), of course we could have used other tricks (references, template functions, lambdas...). I understand OP's point: sometimes we have lhs = expression(lhs), and lhs is not that simple. Instead of augmenting tracing by introducing a new function/macro that may not be used anywhere else, we just duplicate the lhs part. Vim can be good at doing this, so, let's see how! – Luc Hermitte Dec 17 '19 at 22:36
  • 1
    @EricTowers, I see a question about "typing", not about programming. :) – Luc Hermitte Dec 18 '19 at 0:22
11

You can repeat the last inserted text in insert mode using CTRL-R . or CTRL-A

But you need to leave insert mode to complete the change. Enter insert mode and type

table[row][col]

Then leave insert mode and re-enter using Esca and type the rest of the line before the repeated part

table[row][col] = std::max(

Finally, type CTRL-R . (or CTRL-A)

table[row][col] = std::max(table[row][col]

and finish the line.

  • 6
    <C-A> instead of <C-R>. saves one keystroke. – Matt Dec 16 '19 at 14:23
  • 1
    CTRL-@ also inserts previously inserted text and additionally leaves the insert mode. – Miłosz Łakomy Dec 20 '19 at 8:45
3

It's possible to use a mapping:

inoremap <silent> sf <c-r><c-r>=getline('.')[:col('.')-2]->substitute('.*;','','')->substitute('=.*','','')->trim()<cr>

Or an abbreviation (if the keychain consists of only "normal" keys, it's preferred to mapping, as a mapping may conflict with a part of another word, such as transform):

abbr <expr> sf getline('.')[:col('.')-2]->substitute('.*;','','')->substitute('=.*','','')->trim()

(it's necessary to have -2 because col() returns the 1-indexing index of the next character on the cursor, while [:] takes the 0-indexing byte index of the last character to be included)

This uses function or "method" chaining, a feature new to Vim 8.2. For older version where such method calls are not available it's possible to instead use:

abbr <expr> sf trim(substitute(substitute(getline('.')[:col('.')-2],'.*;','',''),'=.*','',''))

So you only need to type

table[row][col] = std::max(sf, other_table[row+1][col+1])

The sf typed will expand to table[row][col] in this case.

  • 3
    You might want to point out that -> (function chaining) is only available in Vim 8.2 (which most people probably haven't yet installed). – B Layer Dec 15 '19 at 15:56
  • I took the liberty of adding links to relevant help docs. – B Layer Dec 16 '19 at 6:20
  • Nice idea. Is there a reason for not using the simple getline('.')[:col('.')-1] – Luc Hermitte Dec 16 '19 at 15:05
  • My mistake, it should be col('.')-2 which also work with multibytes characters and tab before or under the cursor when used in getline('.'). strcharpart() has its use-cases, but it tends to complicate code and make it less portable with older flavours of vim. – Luc Hermitte Dec 16 '19 at 15:34
  • EDIT: Beside on <tab>abcdéfgh, we can observe with inoremap µ <c-o>:echo getline('.')[:col('.')-2] . " -- " . strcharpart(getline('.'), 0, getpos('.')[2]-1)<cr> that strcharpart() solution does not return the correct substring. – Luc Hermitte Dec 16 '19 at 16:48
1

For the given example, I would first type

table[row][col]

and then

<Esc>yBA

before continuing the statement, then leaving insert mode and pressing p to paste the expression when needed.

<Esc> " Leave insert mode
yB    " Yank the preceding WORD (moving the cursor)
A     " Move cursor to end of line and enter insert mode 

I do think Mass's similar answer is better, if you can recall it. I wasn't aware of it before reading.

  • 1
    It is nice that your answer keeps things simple. I also often use YPJ, which duplicates the line and leaves the cursor on a space added in the middle. YPJx is the same, without adding the space in the middle. YPgJ is the same as YPJx, but keeps the original line in the " register. Dpp duplicates the suffix of the current line starting at the cursor. – Miłosz Łakomy Dec 20 '19 at 8:56
1

As a "vi veteran" (30+ years) I may appear "old fashioned" but I prefer solutions working everywhere and always (without preparing much in a .vimrc file), even if they require a tiny amount of planning ahead:

First insert the line as: @ = std::max(@, other_table[row+1][col+1]) (+ <Esc> of course)

Then type: :s/@/table[row][col]/g (+ <Enter> of course)

For the latter, if you need it again some time later, q: may be your friend … though this requires vim … but it is one vim's features I consider to be priceless.

1

I couldn't resist golfing this one, and came up with the following:

3itable[row][col]<Esc>i+1<Esc>F].;a, other_<Esc>3;a = std::max(<Esc>A)<Esc>

This works by inserting three copies of table[row][col] by using a [count] with the i command and then jumping backwards to fill in the missing parts of the statement — each of which is conveniently adjacent to a ] character — with the F and ; commands.

It improves on Mass's (actually far more appropriate) solution by 5 "keystrokes" (55 vs 60), but would only actually be quicker to use in anger if you are exceedingly good at eyeballing f motion targets.

0

A simplified solution:

inoremap <expr><C-G> get(split(getline('.'), '[[:blank:],;()]\+'), nr2char(getchar()) - 1, '')

Now pressing <C-G>1 while editing the line will insert the first "word" of it. "The word" is anything bounded by blanks, commas, parentheses and semicolons. Feel free to edit the regex as you wish.

  • Why not use the \<\w\+\>? – D. Ben Knoble Dec 17 '19 at 2:30
  • @D.BenKnoble I suppose you mean \W\+, as this is a regex for separators, not for the words. That could work for text file, but for C program such regex would split table[row][col] into "table", "row", and "col" which is not the OP asks for. To cover the general case this should depend, at least, on &filetype and &iskeyword (\k). – Matt Dec 17 '19 at 5:57
  • Well, i really meant capture the right text rather than split on non-words, but sure. Yeah iskeyword is better – D. Ben Knoble Dec 17 '19 at 5:58
  • @D.BenKnoble But [] is not keyword in C. – Matt Dec 17 '19 at 5:59
0

Try vim-eython, it's used to grab complicate expression (such as table[1][2][3], name.foo<type>(...) ) from current file:

eython

It has with a motion, a text object, and code complete:

map  <silent> ,e         <plug>eython_motion
vmap <silent> ie         <plug>eython_textobject_ie
omap <silent> ie         <plug>eython_textobject_ie
imap <silent> <c-x><c-n> <plug>eython_complete_next
imap <silent> <c-x><c-p> <plug>eython_complete_prev
0

Consider using snipmate and defining snippets for these operations.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.