Efficient Refactoring
Whilst watching an episode of Francesc Campoy's just for func series I noticed that there was a major feature of VS Code that was going unused. Multiple cursors. This is one of the biggest productivitiy features of modern text editors.
In this example we are refactoring go code, however this is a language agnostic editor feature.
The problem at hand is to refactor the following code snippet (to remove global variables). Francesc did this by hand and (thankfully) sped up the video. It turns out that by using multiple cursors we can do it even quicker than the full speed video.
From this …
func main() {
// Get flags
flag.StringVar(&urlBase, "url", "http://example.com/v/%d", `The URL you wish to scrape, containing "%d" where the id should be substituted`)
flag.IntVar(&idLow, "from", 0, "The first ID that should be searched in the URL - inclusive.")
flag.IntVar(&idHigh, "to", 1, "The last ID that should be searched in the URL - exclusive")
flag.IntVar(&concurrency, "concurrency", 1, "How many scrapers to run in parallel. (More scrapers are faster, but more prone to rate limiting or bandwith issues)")
flag.StringVar(&outfile, "output", "output.csv", "Filename to export the CSV results")
flag.StringVar(&nameQuery, "nameQuery", ".name", "JQuery-style query for the name element")
flag.StringVar(&addressQuery, "addressQuery", ".address", "JQuery-style query for the address element")
flag.StringVar(&phoneQuery, "phoneQuery", ".phone", "JQuery-style query for the phone element")
flag.StringVar(&emailQuery, "emailQuery", ".email", "JQuery-style query for the email element")
flag.Parse()
// ... snip ...
}
Switch from flag.xxxxVar(&myGlobal...
to myLocal : = flag.xxxx(...
To this …
func main() {
// Get flags
_urlBase := flag.String("url", "http://example.com/v/%d", `The URL you wish to scrape, containing "%d" where the id should be substituted`)
idLow := flag.Int("from", 0, "The first ID that should be searched in the URL - inclusive.")
idHigh := flag.Int("to", 1, "The last ID that should be searched in the URL - exclusive")
concurrency := flag.Int("concurrency", 1, "How many scrapers to run in parallel. (More scrapers are faster, but more prone to rate limiting or bandwith issues)")
outfile := flag.String("output", "output.csv", "Filename to export the CSV results")
nameQuery := flag.String("nameQuery", ".name", "JQuery-style query for the name element")
addressQuery := flag.String("addressQuery", ".address", "JQuery-style query for the address element")
phoneQuery := flag.String("phoneQuery", ".phone", "JQuery-style query for the phone element")
emailQuery := flag.String("emailQuery", ".email", "JQuery-style query for the email element")
flag.Parse()
// ... snip ...
}
TL;DR; commands
- Get a cursor on each interesting line
- Delete the common characters we don't want
Var(&
- Cut the variables out for each line
variableName
- Paste the variables back to the correct place
variableName
- Add new characters so the code complies
:=
Detailed commands
Choosing your line anchors is much like using regular expressions, just without having to remember the syntax. Look for the interesting pattern on all the interesting lines. In our case Var
should do it, except that a lower case var
exists elsewhere in the code. So if we increase the size of the anchor (bigger anchors are more likeley to select what we want). Var(
should get us the interesting lines, we will add the ampersand because we already know we want to delete it.
When working with multiple cursirs you can now perform any cursor relative movement operation;
home
,end
,delete
,backspace
,select-to-end-of-word
,move-to-end-of-word
, etc. Note that if you have multiple cursors on a single line they will become a single cursor if they collide, e.g. press home or end.
- Select one of the instances of
Var(&
- Select all instances
ctrl+shift+l
delete
the selected text, then add in an opening parenthisis “(
”.- Word select the next whole-word,
shift+ctrl+right-arrow
(we can't just right arrow because they words are not all the same length) - Cut the selected text
ctrl+x
- Move to the begginging of the line with
home
- Paste the variables in
ctrl+v
(this is the magic, each cursor has it's own clipboard) - Add the remaining text, which is the same on each line, “
:=
” escape
will end the multi-cursor session.
Alternative selection
One by one
Sometimes select all occurences is too wide so adding one instance at a time is better, use ctrl+d
to select the next instance, to unselect the current and add the next press ctrl+d, ctrl+k
.
Cursor for every line
If, as in our example, all the text is in a block we could have used a basic multi-line selection then add a cursor on every line with shift+alt+i
. I've found that this command works on the selection plus wherever the cursor is so you can get an extra cursor if you are not careful.
Regular Expressions
You can use a regext to get multiple cursors;
ctrl+f
–> Open find widegt.alt+r
–> Turn on regex mode.- Input search text –> Regex text or normal text.
alt+enter
–> Select all matches.
Additional tools
I have also found the change case extension (wmaurer.change-case) for VS Code to be really useful. For example, switch from PascalCase to cammelCase when adding json attributes to Go or C# types.
The align extension (steve8708.Align) is really useful too for text layout with multiple cursors.
Key-binding
You can change the keys to drive this tool, these are the interesting operations.
[
{"key": "ctrl+shift+l",
"command": "editor.action.selectHighlights",
"when": "editorFocus"
},
{"key": "ctrl+d",
"command": "editor.action.addSelectionToNextFindMatch",
"when": "editorFocus"
},
{"key": "ctrl+k ctrl+d",
"command": "editor.action.moveSelectionToNextFindMatch",
"when": "editorFocus"
},
{"key": "escape",
"command": "removeSecondaryCursors",
"when": "editorHasMultipleSelections && editorTextFocus"
},
{"key": "shift+alt+i",
"command": "editor.action.insertCursorAtEndOfEachLineSelected",
"when": "editorTextFocus"
},
]
Links
- Francesc Campoy just for func you tube : https://www.youtube.com/channel/UC_BzFbxG2za3bp5NRRRXJSw
- Project being refactored Philip Thomas
iterscraper
: https://github.com/philipithomas/iterscraper - VS Code (v1.5.3) : http://code.visualstudio.com/
- Regex multi-select: Thanks to https://github.com/Inori see https://github.com/Microsoft/vscode/pull/5715