This week, I’ve had to make some changes to an automation we had setup for a customer in Workato. The original recipe was made by a co-worker with a bunch of javascript nodes. Even though I’m customer facing and generally not considered a technical employee, I knew the engineer who worked on this was swamped, so I decided to jump in.

In order to greatly reduce the number of nodes needed in the single recipe, I created a list of dictionaries in python (with one of the values being another list!) and a quick for loop to grab all the appropriate elements from the various dictionaries. I had thankfully already copied all the JS nodes to a single file for easy reference and retrieval. That one file had over 8,000 lines, and I needed to replace all the javascript elements with python.

Here’s one node of Javascript that I was working with:

// @param input fields supplied in the recipe step, as an object
// @return object matching the output schema
// Eg: Code for returning time zone for an IP address

exports.main = async ({ user_email }) => {
  let all_domains = [
 'domain_one',
 'domain_two',
 'domain_three',
 'domain_four',
 'domain_five',
 'domain_six',
]

  let user_domain = user_email.slice(user_email.indexOf('@'));
  let domain_found = all_domains.indexOf(user_domain) > -1;
  return { domain_found };
}

Most nodes were very similar, all except that their all_domains array had anywhere from 1 to 500+ elements. Thanks to the neovim plugin Telescope Cmdline by Jon Arrien, you can very easily see your past history of Neovim commands. I’ve gone back through and pulled out a few special ones that helped and that I was very surprised how well they worked. I don’t claim that this was the fastest or best way to clean up a large file with repeating functions, but I definitely learned a bunch about searching-and-replacing in Neovim, and any future needs for this will be way faster! One thing that has been really helpful with Neovim is that as you run the first portion of all the commands below, Neovim should highlight what it finds. This is really helpful for real-time debugging of your regex.

Commands and Quick Explanations


Command: :g/\//+2/d

Explanation: The g in the command stands for global (hint, run :h :g in vim!) and will search across the entire buffer you have open. In this command I’m looking for all Javascript single-line comments. Since the forward slash is a special character for neovim, you need to escape it with a backwards slash, hence the odd looking \/. The second forward slash is the next parameter neovim is looking for, and by adding +{num}, you can search for the pattern plus a number of lines. Closing out the command (again, next parameter comes after the forward slash) with d for delete will then delete everything with you searched for, plus the additional number of lines.

Bonus: if you wanted to make sure that you only pulled lines that began with a comment, you’d add a carrot to the beginning of the command. :g/^\//+2/d


Command: :%s/let all_domains = /"domains" : /g

Explanation: Moving on from g to s, the s here stands for substitute. There’s one big difference in using g and s, though. While g will search globally, a singular s will only search on your current line. To search the entire file/buffer, you need to include the percent character before the s. The rest of this is fairly straight forward, I am searching for the declaration of a javascript variable and then substituting it with a python dictionary key. Since both javascript and python declare arrays with square brackets, I wanted to make sure I kept that after the substitution. Lastly, unless you only want the subsitute to change the first occurrence of what it finds, you’ll need to close out the command with the g flag. Neovim’s docs say:

[g] Replace all occurrences in the line.  Without this argument,
 replacement occurs only for the first occurrence in each line.  If the
 'gdefault' option is on, this flag is on by default and the [g]
 argument switches it off.

Command: :%s/^exports.main.*/{ "uuid" :/g

Explanation: Really similar to the above command, but in this case, there are no characters I needed to save post-substitution. We already know what %s does, so the pattern I’m looking for is all lines that start with exports.main. Since I don’t need anything after that start of the line, I included .* which looks for any character until the end of the line. After the pattern I write out what I need to replace it with, which is the first portion of the dictionaries I need. Finally, we close it out with a global change so it changes it everywhere.

Command: :%s/return.*/], Explanation: Finally, I used this one to close out the domains list/array that I’m using.


Bonus Command: %s/"props": \[\(.*\)\]\,/"props" : \1,/g

Explanation: I won’t go over all the characters and regex commands as I’m reusing a lot of them from the above commands. What is different here is that I’ve lumped the wild card in parenthesis like so: (.*\). Now, I can call back what is pulled by using \1. The command above is looking for a dictionary item with a list as the value. I didn’t need the list, just a string, since all values in my list of dictionaries are single values. By searching for "props": [ string ], and using the \1, I’m able to keep the string intact and the result is "props": string,.

This was the milestone command to learn for me and helped unlock so much potential in my brain for other use cases. For all the other commands I’ve gone over above, I’m fairly simply just replacing characters I no longer need that exist either before or after characters I do need. Now that I can keep strings intact for substitutions, I bet I could revisit all the above commands and make them even more efficient.

What I’d like to learn next is if I can use an “or” statement in the regex. Let’s say I have the same situation as above - a list of dictionaries with some dict values being a list. Well, if the lists are all formatted differently, some on new lines, others on a single line, can I select both in a single command? Something like: :%s/"domains": \[\(.* OR \n\)/.

That will be a post for another time! Let me know on Mastodon if you have any other Neovim search and replace tips and tricks that have become invaluable to your workflow. I’m always amazed and impressed with this community and how differently people are able to use the same tool.


By the way, here’s the final output of running the above commands on the sample code above, converted to Python.

mappings = {
  'uuid': "1234-1234-1234-1234",
  'domains': [
     'domain_one',
     'domain_two',
     'domain_three',
     'domain_four',
     'domain_five',
     'domain_six',
    ],
  'props': 'property-mapping',
}

Resources

Here are some various links of resources that I found helpful when learning about the above commands. I’m assuming you’ve already read through anything in Neovim’s Helpdocs (:h), but sometimes you need a different take and explanation.