Editing is a time consuming task, but it’s annoying to make tedious or repetitive changes that a computer could easily do. You might have some notes about a conversation between characters. Having a quick toggle to apply double quotes for the entire conversation would be convenient. Let the computer do computer stuff and save your human brain and fingers for author stuff.
Thanks for your interest
This content is part of a paid plan.
Toggle Quotes around Multiple Paragraphs
Toggle double quotes around each paragraph in the current selection. This is an extension but independent of a previous single-paragraph version. Due to the multiple possible document edits, we further add an undo record to make it easier to reverse the changes, and we allow “sloppy” paragraph selections to make the macro easier to use.
What’s the plan?
We generalize an earlier macro that toggled double quotes around a paragraph. This is a more practical variation since we’ll probably at some time need to toggle quotes for a whole segment of dialog across multiple paragraphs at once.
What are the basic steps?
- Loop over all paragraphs in the current selection, partial or full.
- Run the toggle quotes function on each paragraph range.
We haven’t had much need for loops thus far on this blog, but it’s central to the action of this macro. Let’s work through the details, but we also add some icing on the cake afterward.
Other functions
With this in mind, we leverage the aforementioned previously created a function that toggles double quotes around any document range. That function also uses two double quote functions because the special character for left and right double quotes is different between Word for Mac and Windows as well as a function to trim blank space from a range.
Create macro skeleton
Open the VBA editor (Option+ or Alt+F11) and create two empty macros.
The macro can’t accept any parameters or return a value (it’s a subroutine not a function) since we’re assigning it to a keyboard shortcut.
Set up the loop
There are several ways to loop over macro steps in VBA. This isn’t a loop tutorial, so let’s focus on the one we need.
What are collections?
Collections are a general kind of list which can store any type of document element (or any type of data really). Commonly used examples in Word macros include Characters, Sentences, Paragraphs, etc., but the concept is quite general.
Simple For Each loop
We can loop over any collection of document elements using a For-Each loop. The meaning here is:
A For-Each loop literally cycles through each element in the given collection. We can do anything we want in the loop, but it usually relates somehow to the current element of the loop.
Collection
SomeCollection is the collection you want to loop over and do something with each element.
The In keyword between the element variable name and the collection name is part of the command, so don’t forget it.
Element variable
We give a variable name, SomeElement in this case, which refers to the specific element of the collection in each cycle of the loop. Of course, the variable must match the element type of the collection. Some collections store document ranges. Others store paragraphs, hyperlinks, etc.
Next element
The Next command at the end cycles the loop to the next element in the collection. The loop ends when all elements have been used.
Nested loops
For completeness, if there are “nested loops” (loops within loops), we can add the element identifier after the Next statement.
We could include the identifier in today’s macro also, but we don’t need it since we only have one loop.
Create the paragraphs loop
For clarity, we’ll refer to Paragraphs from now for this macro, but the basic logic applies to any loop variable along with a corresponding collection.
Loop variables
We’ll call our current element MyParagraph. If we want to be extra clear, we can declare this variable explicitly before the loop.
This step isn’t required (unless Option Explicit is used) or you’re passing the variable directly into a function.
As a reminder, MyParagraph changes as the For-Each loops steps through all elements of the collection.
Paragraphs collection
We’re using the Paragraphs collection which is a property of the current Selection.
Fortunately for this macro, this collection includes even partially selected paragraphs. This includes the current paragraph even if there is only an insertion point (no text spanned by a selection). In this case, only one paragraph would be in the collection.
Loop over all Paragraphs
Our loop over the Paragraphs collection is:
Even though we're doing something much more complex than a single command, this loop still reads almost like English. I like how VBA (and Visual Basic in general) tries to make programming look more approachable.
Toggle quotes for current paragraph range
In the loop, we want to toggle the quotes for the current paragraph but be specific with the function argument. For example, this doesn’t work.
This doesn’t work for this function because we wrote the function to use a given range, not a paragraph. We could’ve written that function to work on a given paragraph, but using a range parameter made the function more generally useful.
Instead, we need the range of the current paragraph in the loop. A Paragraph object has a Range property that refers to the paragraph’s spanned content.
So “pass” this range to the ToggleQuotesRange function.
We already wrote the ToggleQuotesRange to exclude paragraph marks because they are also included as part of the paragraph and thus the range.
Gotcha
What could go wrong?
What if no paragraphs exist in the current Selection?
Uhhh … well, that can’t really happen.
If a document is open and active, there must be a Selection in it. Even an empty Selection, called an “insertion point” (the blinking I-bar waiting for you to type text), is associated with the paragraph in which it is located, so it looks like we’re okay here.
In general, a For-Each loop will only start if at least one element is in the given collection, so we would’ve been okay anyhow.
Final Macro
Putting this together, our extended macro to toggle double quotes around any number of selected paragraphs is:
And done.
[Mic drop. The crowd goes wild …]
This is the advantage of functions. We just run it for each paragraph range letting Word do the work of looping over them and applying the function individually to each paragraph in the current Selection. This version even works for a single paragraph since it “loops” over the one paragraph running the toggle function just once.
I’ve assigned my versions of these macros to Command+Q or Control+Q.
However, the error extension below is important. Please don’t skip it.
Improvements
But by now you know I can’t leave it plain and … well I can’t say the above version is boring, but it could be better. I want to improve it three different ways.
- Add an undo record, so all changes are undone together, if desired
- Add an error handler just in case there are problems (mostly for the undo record)
- Check for an empty paragraph and add plain double quotes inside it
This macro definitely benefits from an Undo Record (see previous article) since potentially many changes are made during the macro.
The error handler is to keep us out of trouble if the macro ever crashes while running.
I also add a condition to check for an empty paragraph, so I can tell the macro to automatically place the insertion point between the double quotes. That way, I can just start typing the new dialog.
Add undo record
This function has the potential to make many changes in different paragraphs, so it would definitely benefit from an undo record (see previous article for more explanation).
Start undo record
An undo record stores up all the changes made while it is on and will reverse them all at once on an Undo command usually by pressing Command+Z or Control+Z in Mac or Windows, respectively.
An UndoRecord is a specific type of object which Word uses to keep track of actions under the hood. MyUndo is the current record variable name.
After we use the StartCustomRecord method, all commands will be aggregated together for any potential undo action.
It’s quite handy, and the record will also be labeled in the edit menu list of recent document actions with the name you give it. Nice.
End undo record
Definitely end the undo the custom record at the end of the macro, but don’t omit the error handler below.
Undo record danger points
Microsoft says don’t switch documents while using undo records. Also don’t use breakpoints (during macro testing) or the VBA editor will end the undo record mid-stream. This could cause issues, but you probably shouldn’t be testing your macros on your novels until they’re working correctly anyhow.
More importantly, see the error handler below.
Error handler
I try not to get overly complicated (which is a matter of perspective, I understand) with the presented macros, but undo records can cause significant issues if not properly done (see previous article for more explanation).
Why?
If an error occurs while the loop is running over the paragraphs in the selection, the macro quits immediately, and the undo record may not be properly closed.
I’ve had times where it works fine, but at others …
Bad. Definitely don’t do that.
Based on the terse documentation (as i understand it), VBA is supposed to close the undo record anyhow, but experience says otherwise. To help control this serious possibility, we can add an On Error statement.
This command tells VBA if an error occurs to go to a specific line identified by the name ErrorUndo and continue running the macro from there but skipping all steps in between. That way we can properly end the undo record.
Our “error steps” are as follows. We start the line label.
Then we add any steps that are still run after an error is detected.
Don’t leave this out.
You could lose precious words (and writing time) which is a fearful thing for an author, and yes, I have lost novel content a few times because I wasn’t careful enough.
There are two downsides of using an error handler. One is the macro looks messier and more like real programming. Also, you need to wait to enable it until after you’re sure the macro is running properly. Otherwise, you won’t get an error message like normal since VBA will handle the error automatically because you told it to.
No Exit Sub before error steps?
If the error steps should only be run when an error occurs, it is common to include an Exit Sub statement just before the error line identifier.
For our macro, even if the main macro steps finish properly, we still want to close the undo record, so I omitted an Exit Sub command. The macro just continues on through the ErrorLine: label to run the last step.
Why?
I don’t want to repeat the same command above the error label. It just looks messy to me.
Insert empty double quotes on empty paragraph
I like little boosts of efficiency. Like I said, I don’t like typing double quotes, so I would like the macro to recognize an empty paragraph and position my insertion point in between my new double quotes ready for me to type some amazing dialog.
Conditional statement branch
How do we check for an empty paragraph and do the special case?
We can’t forget the main purpose of this macro, so the ElseIf part adds the double quotes for regular selected paragraphs or no selection (meaning a single paragraph) as normal. See a previous article for a brief introduction to conditional statements.
Empty paragraph condition
Let’s take a short detour to explore some insufficient logic just to give you a flavor of little bugs that could creep into your macros if you’re not careful.
Insufficient case 1
Will testing for an empty Selection work?
We can’t use an empty Selection by itself to detect an empty paragraph since an insertion point can be anywhere in a paragraph even if the paragraph has text.
See a previous article for a brief review of Selection Types.
Insufficient case 2
What about testing for a paragraph mark?
A paragraph mark is included in the Paragraph’s text, so we could check the current paragraph’s text and see if it is just a paragraph mark special character vbCr. This is better, but it still won’t properly detect an empty paragraph by itself.
Why?
What if the user has strangely only selected a paragraph mark in a regular paragraph? Admittedly, this would be odd, but this condition would still be True even if the paragraph isn’t empty. This condition could also be true if the insertion point just happens to be at the last position in any paragraph. This unfortunately second issue is because VBA considers the character immediately after an empty range (via Selection.Range since Selection.Text is just shorthand for Selection.Range.Text) to be the range's Text.
Insufficient case 3
Okay, what about checking the full paragraph text?
We can refer to the first full first paragraph text instead when checking for a paragraph marker.
This one is trickier to see why it doesn’t work, but if the first paragraph of a multi-paragraph selection is empty, the condition is still true.
What condition do we use then?
Better condition
We can avoid each problem case above by validating whether the Selection is empty in addition to verifying the full paragraph text is only a paragraph mark.
It requires an And operator because both conditions must be True before we are sure the paragraph is empty. This condition will even be False if the paragraph contains only spaces since its text is more than just a paragraph mark.
Check for valid selection on paragraphs loop
We should probably validate whether we have a normal selection before trying to loop over any paragraphs.
We check whether the Selection Type indicates a normal selection using wdSelectionNormal, but we also need to allow for no selection (indicating we apply quotes to the current paragraph), so we include a condition for wdSelectionIP. Either condition can be true to loop over the paragraphs, so we use an Or operator.
Our revised conditional statement is:
We do not include an Else part, so nothing happens in the conditional statement if neither of these conditions is True.
Add double quotes and adjust insertion point position
Since we know there is an empty paragraph, we can just add the double quotes manually using the Selection’s InsertBefore method.
These double quote functions are used because the special characters differ between Word for Mac and Windows.
Now move the insertion point between the double quotes, so we can just start writing the dialog.
We start by collapsing the Selection, so it does not move from the end. It seems in this situation the "active" side of the Selection is the End position, so the move begins from there. The collapse ensures the move begins from the left side.
MoveRight has a default Unit of characters and Count of 1 Unit, so we don't need to add any options.
I would probably also add the extra move steps for empty paragraphs in the previous versions of the toggle quotes functions.
Final Macro for Multiple Paragraphs
Adding the above bells and whistles, here is a more complete version of the macro.
I’d like to have one macro that does everything, but this one can’t apply block quotes across multiple paragraphs as a group nor to specific text like the previous versions. It only works on whole paragraphs. There is no way to discern those two other cases from whether the user wants to apply double quotes to each individual paragraph.
I don’t see a need for a similar variation with sentences since a writer would rarely add double quotes around each individual sentence in a selection.
I’ve assigned my version of this macro to Command+Q in Word for Mac and Control+Q Windows.
I have implemented further extensions to account for or remove dialog tags, etc. but this will have to do for now.