Best Vim alternative to VSCode's Ctrl+D

Viewed 2138

Lets say we are editing this totally made up JSON file:

[{
  "id_4f7xg4egb": "<some_random_guid",
  // ... other fields
}, {
  "id_h34k3": "<another_id>",
  // ... different fields than prev object potentially
  "nested": {
    "id_j3h": "<nested_obj_id>",
    // ... nested obj fields
  }
},
// ...
]

It contains N objects (including the nested ones), and we'd like to replace the id value with the string coming appended to the field key itself. The result would be something like:

[{
  "id": "4f7xg4egb",
  // ... other fields
}, {
  "id": "h34k3",
  // ... different fields than prev object potentially
  "nested": {
    "id": "j3h",
    // ... nested obj fields
  }
},
// ...
]

Now, here is what I would do in VSCode using multiple cursors:

With VSCode Ctrl+d

  • Select first occurrence of "id_ with Shift+→x4
  • Select (and create a cursor on) every occurrence of the remaining N-1 "id_ with Ctrl+dx(N-1)
  • Select the id string(s) following "id_ with →,← (to deselect), Ctrl+Shift+→
  • Cut them with Ctrl+x (yes, each cursor gets its own "clipboard")
  • Delete the _ with Backspace
  • Select the portion we want to replace ("<every_diff_id>") with →x4,Shift+End, ←x2
  • Replace the value with Ctrl+v and Esc to get rid of the extra cursors

So we are talking about 5+N+5+2+1+8+3=N+24=o(N) keystrokes (taking into account the fact that, for example, the Ctrl key is counted only once and then held down for the rest of the N-1 Ctrl+d commands).

With the Power of Vim

And... my question is: How to accomplish the same result using Vim (in =< # of operations, ofc)?! I'm a noob, one week old vimmer, and I'm loving it so far! I've been using . and basic macros for similar tasks, but I'm not sure what would be the most efficient way to tackle this one :(. I'd also prefer a solution NOT using plugins or involving adding some complicated mappings/functions to my .vimrc. After all, the VSCode solution is vanilla VSCode ;).

Thanks!

4 Answers

I would do it in two intuitive passes and not worry about counting keystrokes or beating the wall clock.

First pass:

:g/id/norm f_s": "
  • Use :global to execute a command on every line matching a pattern.
  • The pattern is id.
  • The command is :normal followed by a normal mode macro.
  • The macro…
    • moves the cursor to the first _ on the line with f_,
    • and substitutes it with ": ".

See :help :global, :help :normal, :help f, and :help s.

Second pass:

:g//norm f:;C,
  • Use :global to execute a command on every line matching a pattern.
  • The pattern is the same so it can be omitted.
  • The command is :normal followed by a normal mode macro.
  • The macro…
    • moves the cursor to the first : on the line with f:,
    • then repeats the move to get to the second : with ;,
    • replaces the rest of the line with a ,.

See :help ; and :help C.

I would probably just do something like this:

:% s/"id_\([^"]*\)": .*,/"id": "\1"/

That's a global search-and-replace, matching the "id_<something>": <stuff>, and replacing it with "id": "<something>". I think of that as a single "operation", but not sure if that's what you were looking for.

By recording a macro and replaying it N times.

For example:

qa/"id_/e<CR>s": "<Esc>f:dt,q2@a

That's a total of 25 keystrokes.

Breaking it down:

  • qa: Start recording macro @a.
  • /"id_/e: Search for pattern id_ and place cursor at the end of the pattern. The <CR> executes the search.
  • s: Replace the character under the cursor (the _), starting insert mode. Then use ": " as the replacement text (produces "id": "... and the <Esc> leaves insert mode.
  • f: Move forward to the : after the field. dt,: Delete until the comma (preserving it.)
  • q: Stop recording the macro.
  • 2@a: Execute macro @a two times. (If there were more id's to replace, execute it more times. Use 999@a to execute up to 999 times, or until there's an error, whatever comes first.)

I agree that multiple cursors are pretty visual and might be easier to grasp... Vim macros can be somewhat abstract and require you to think of what these operations would do to the other positions you apply the macro to...

For this particular situation, perhaps using a :s command would be easier. But that's a good thing of Vim, there are often multiple ways to solve a problem and you can choose the one you prefer.

Using global command becomes:

:g/"id/exe 'norm! f_xdt"f<vi"p'

where we find "id
normal mode
f_  .................... jump to underscore
x ...................... cut
dt" .................... deletes until " (goes to unnamed register)
f<  .................... jumps to <
vi" .................... visual select inner "
p ...................... paste default register

I have posted this solution but I think filbranden's is better.

Related