Heading

This is some text inside of a div block.
This is some text inside of a div block.
This is some text inside of a div block.
min read

Go to an empty or incomplete paragraph

Word • Macros • Navigation
Peter Ronhovde
39
min read

Writing and editing a novel is a slow process, so any tools to speed things along are a useful addition to our toolbox. With this goal in mind, we create a pair of macros to jump to a nearby empty or incomplete paragraph, so we can start typing immediately.

Thanks for your interest

This content is part of a paid plan.

Go to an empty or incomplete paragraph

Bouncing around our documents writing, editing, inserting or deleting notes is a time-consuming process. Perhaps you balk at the "bouncing" analogy. Maybe you're more like a farmer behind a plow facing an empty field when at the computer. The furrows are laid out in your mind all perfectly straight, so it's just a matter of plowing them and planting the seed. Congratulations if so, but my own writing and editing process is much less linear than I would like.

Any macros that can help me navigate a document faster, especially for clearly defined targets, are a welcome addition. More specifically, these macros allow us to jump to a nearby empty or incomplete paragraph. I initially created them after some minor frustration at having to repeatedly locate my in-progress scene to continue writing, but I also find them useful in notes documents at times.

Here is a constructed example, but my typical uses include longer range jumps.

Example of jumping to the next empty paragraph
Example of jumping to the next empty paragraph

If you're more interested in just jumping to an empty paragraph, its simpler, little brother macro was covered in a separate article.

Why would we jump to an empty paragraph?

Why jump to an empty or unfinished paragraph?

It's just super-fast when you need to get back to where you were writing.

Using the mouse to navigate a document has its place—they've stuck around for over five decades for a reason—but it's generally slower than it feels compared to using the keyboard. In essence, use the mouse when you need it (and voice recognition sometimes if you have it) but the keyboard the rest of the time.

These macros may be more applicable to my writing style where I regularly add text snippets and notes into a tentative novel outline. Rather than write it down on paper or in a separate notes document, I'll often just move to that location to tweak the text or add a note to the budding scene. It may be a dialog snippet or just a note to remember when I write the full scene text. Getting back to where I was working could be a clicky-tappy thing, or … I could tap a quick shortcut and jump immediately back to the paragraph I was working.

When I say outline, I mean it in a growing sense, more like a branching tree that needs to be filled in and occasionally trimmed. I develop the scenes and the novel text as the ideas flow, here a little … there a little and often not in order.

Ughhh, but I still enjoy the process, and I actively try to keep it fun.

I eventually need to fill in connecting scenes, but as much as I want to voraciously and thoroughly outline. I just can't. This fluid process is how the words flow from my brain through the keyboard (and occasionally voice dictation also) into my document.

What about the “goto last edit” shortcut?

Yeah, Word has a "GoTo last edit" feature, and it's useful sometimes … except for most of the time when it's not.

For any unaware or otherwise forgetful, the Shift+F5 (or Control+Alt+Z in Windows or Command+Option+Z on Mac, if you like that combination better) shortcut cycles between last several edit locations in the document without undoing any changes. The idea is wonderful in principle, but in practice, it's only useful in specific circumstances because it often treats individual characters as distinct “edits.” Any significant text changes, such as typing a single word, before trying to jump back to the previous edit location will likely confuse it.

We can’t improve this feature's behavior since only Word tracks real-time changes to a document for the entirety of the process. Macros only know the state of the document or any changes for the fraction of a second they're running, but we can create some targeted tools to help us navigate our documents faster.

Create the empty macro

Open the VBA editor (Alt+F11 in Windows or Option+F11 on Mac) and create this pair of empty macros. If you prefer the longer but prettier route, a previous post covers creating an empty macro by either recording a blank macro or creating it through the macro dialog. Either way, you'll end up in the VBA editor with two macro skeletons something like the following:

Sub GotoNextIncompleteParagraph()
' Jump to the next empty or incomplete paragraph in the document

End Sub
Sub GotoPreviousIncompleteParagraph()
' Jump to the previous empty or incomplete paragraph in the document

End Sub

The first jumps to the next and the other jumps to the previous empty or incomplete paragraph in the document. We need two macros since we cannot assign Word shortcuts to any macros that include parameters. This constraint is extra disappointing for this pair of macros because they literally only differ in one assigned value.

The single quote tells VBA the rest of the text on that line is a comment meant for human readers. It's a common practice to include a description at the top of the macro briefly explaining what the macro does along with any special considerations. Our macro steps begin on the empty line.

Searching for text in VBA

Let's take a moment to better understand how Find works.

What is a Range?

A Range (with a capital R) is VBAs internal representation of a literal range of document content. We can define variables to track a span of document content and then use the associated data (called "properties") and actions (called "methods") of the Range variable to manipulate the content. Changes to a document are represented on the screen, but the Range variable itself is invisible to the user unless we specifically select it. We can also create more than one range variable as needed.

What is Find?

Find (with a capital F) is an object defined to control VBAs version of a manual text search in Word. Find has properties and methods relevant to text searches. Multiple search options exist corresponding to anything we can do manually in the Advanced Find dialog. Most checkbox features of the Advanced Find dialog are represented as True or False (Boolean) values, but some others require input such as the search text.

A less-obvious quirk for searches is the initial document position marked by the range variable (or the Selection) only affects the starting point of the search. More specifically, the initial spanned range does not otherwise constrain the searched content.

Selection and Find

When Find is used with the Selection, we specify any search parameter changes along with the search text. If a match is found, it is immediately selected and scrolled it into view.

Range variables and Find

Using Find with a valid Range variable is almost identical using the Selection except the result is not immediately visible on the screen. A range variable will simply be set to span the found text range. To display the result, the range must be selected.

Find with wildcard characters

An important Find property for this macro is MatchWildcards. A classic, simple example of a wildcard character is an asterisk * which matches any character after its position in the search text. Although we will not use an asterisk in this article, searching for plate* would match plate, plates, plated, and any other character after the "e".

Rather than delve into more detail here, our wildcard search below will be different and a little more complex due to the varied conditions that define an incomplete paragraph.

Define a working range variable

Directly using the Selection in this macro would make three (small) changes to the document, so it's a little better if we use a range variable to search the document and just select the final result, if applicable.

Dim rSearch As Range

We "declare" variables in VBA with the Dim keyword which is an abbreviation for "Dimension". It basically sets aside a certain amount of memory for the variable. Declaring variables is recommended but not required, in general, in VBA. We need to indicate a data type which is "As Range" here.

I generally precede range variable names with an "r" to remind myself what kind of data it stores. Some character restrictions exist, but VBA does not care about naming preferences as long as we do not try to use one of its keywords. When only one range variable exists, I will often just call it "r", but "rSearch" is more descriptive for this macro.

Assign the working range variable

The intention is to search the document starting from the initial selection or position, so we will set the initial range using the Range property of the current document Selection.

Set rSearch = Selection.Range

As in other macros, we need Set because a Range object is more than a plain value like a number. We needed the Range of the Selection since the Selection is like a Range but more. After this step, rSearch spans the same document content as the initial selection, but we can use it independently without directly changing the Selection (until we wish to do so).

Using Find to search the document

We will focus on incomplete paragraphs at first but easily tweak it at the end to also include empty paragraphs. How do we find the next incomplete paragraph?

We begin with the Find method of the working Range variable rSearch.

rSearch.Find ' Not done ...

Find is listed as a property of a Range object, but it acts more like a method that carries out an action, and it works in direct analogy to the manual Advanced Find dialog in Word.

Defining the search parameters

In this macro, we're being more rigorous, so we include multiple search options to help avoid any later issues.

Search parameters gotcha

Fortunately or unfortunately depending on your perspective, Find search parameters are sticky between different searches even for independently run macros. Changed settings even show up or carry through from the manual Advanced Find dialog.

Hmmm.

It's more of a design decision than being right or wrong, but we need to work around this feature. It's why we include multiple search options for every search almost to the point of bloating the macro. Some of the assignments are superfluous at times, but it's difficult to know which ones are needed when, and our macro need to work for all cases.

Relevant search parameters or methods

With so many search options available, we'll assign any which could possibly affect our current search. The list below only covers our specific options or methods used.

Search methods

Some methods perform an action related to the search.

  • ClearFormatting → Removes any formatting information from the search
  • Execute → Perform the search based on the current search
True or False search options

Many options are "Boolean" properties that require True (yes) or False (no) values.

  • MatchWholeWord → Require a complete word as the match (do not match part of a word)
  • MatchCase → Match the case of the search text exactly (i.e., "Word" is different than "word")
  • Format → Include formatting with the search
  • IgnoreSpace → Ignore any spaces, tabs, or paragraph marks (called "whitespace") within the document for purposes of determining a match
  • MatchPhrase → Ignore all whitespace and control characters (includes some more content break characters) between the words when matching
More important search options for this macro

These Boolean options are particularly important for our macro today which we'll address in more detail below.

  • MatchWildCards → Allow wildcards to be used in the search (more complex but more powerful)
  • Forward → Search forward (or backward if False) in the document
Search options that require input

These options require more specific input.

  • Wrap → Search wraps around and continues from the other side in the same direction after reaching the beginning or ending of the document (from the short WdFindWrap constants table)
  • Style → Restrict a match to a specified style (optional in this macro since it may fail for multiple paragraphs; see gotcha below for more information)
  • Text → Set the search text as a string (plain text)

Unfortunately, some of the options can behave a little unpredictably at times, so test any macros using Find with several anticipated variations and pay extra attention for a while in the early stages of using it.

Search options in VBA

Given the number of search options (and this is a shortened list), it's convenient to have a condensed notation.

Condensing similar steps using With

With statements group a set of related assignments or commands using the same object. Inside the statement, we can omit the full reference and just refer to the sub-property or method we need starting with the dot . reference.

With SomeObject.SomeMethodOrProperty
' Insert related statements here ...
' Steps inside here are usually part of SomeObject where we use just a
' dot . to reference them
.AnotherProperty = SomeValue ' Assign Somevalue to AnotherProperty
.AnotherMethod ' Run an action
End With

It's difficult to be completely general with the above notation without obscuring how easy it is to use, but the With block allows a nice shorthand for repeated property or method references. These two commands inside the With statement are equivalent to using:

' Equivalent commands to the above with statement
SomeObject.SomeMethodOrProperty.AnotherProperty = SomeValue
SomeObject.SomeMethodOrProperty.AnotherMethod

The separate commands are just messier especially if we have a long sequence of them. In this example, including just two lines in a With statement is wordy, so it's used more often when multiple steps are needed.

The With statement is ever so slightly faster than separately including all the individual steps, but in practice, you would almost never notice the time difference. The real advantages are better organization and presentation of the steps since we don’t have to repeat the SomeObject.SomeMethodOrProperty reference over and over.

With blocks can include other commands without restriction, but it's generally better for organization and clarity to limit any commands or assignments unrelated to the referenced object. An exception would be when a logical step naturally occurs between some of the shortened references.

Using With with Find

Each of the above Find search options are part of the rSearch.Find object. We appropriately run each method or assign each property inside the With statement. Additional comments are included for clarification.

With rSearch.Find
' Setup search options
.ClearFormatting ' Safer but not commonly needed
.Format = False ' Safer but not commonly needed
.MatchWholeWord = False ' Probably not necessary
.MatchCase = False ' Probably not necessary
.IgnoreSpace = False ' Probably extra
.MatchPhrase = False ' Probably extra
.Wrap = wdFindStop ' Just a preference

' More important search options for this macro
.MatchWildcards = True ' Required for this macro
.Forward = True ' Default is forward
End With

Being clear about the search options requires a long list of steps, and I even left some options out, but it's better to be clear because Find options are sticky between different searches.

Each reference of a property must start with the dot . just as if you had typed as a separate command starting with rSearch.Find.

Including any formatting or style restrictions is a more complicated search. We include the style property later, but the general treatment is outside the scope of this article. On the Wrap option, I sometimes just stop the search with the wdFindStop constant as used above, but I've also used wdFindContinue at times to continue the search.

What are we searching for?

We're not searching for a plain text word like "excellent macro" this time. Our search text is more obscure.

Define search text

To make things easier to read, we'll declare a plain text (string) variable for our search text.

Dim SearchText As String

A String stores plain text like "abc". Strings are usually enclosed in double quotes except when special characters are needed. I tend to follow pattern where string variables start with an "S" where a capital "S" implies the variable value will not change inside the macro.

How do we find an empty paragraph?

The previous little sibling macro focused on empty paragraphs, but let's recap the required search text as a starting point. What marks an empty paragraph?

On paper, we drop down to the next blank line and start writing. In a word processor, we tap Return to create a new virtual line. Word marks this paragraph break with a special paragraph mark character.

In the VBA editor, we can't tap Return to put a paragraph mark inside double quotes as in "abc" since the editor would literally insert a new line between the double quotes. One workaround is to use the Word constant vbCr as defined in a miscellaneous Word constants table. An alternative approach allows "^p" or "^13" to specify a text version of a paragraph mark character but see the gotchas below for an issue when using either one with wildcard characters enabled for the search.

However, we actually need to find two paragraph markers side by side in the document. Otherwise, the paragraph would contain some text. Searching for two of them together would look like:

SearchText = vbCr + vbCr ' Previous macro search text for an empty paragraph

Since vbCr is a text character, we can just use a plus + sign to "concatenate" them together and define the desired search text. The works in analogy with how we use a plus sign to add numbers like 1 + 2 = 3, but for text strings, it literally just combines the two strings end to end together to make a new, longer string.

For an empty paragraph, setting the IgnoreSpace Find property to True (ignore any spaces or tabs between the paragraph marks) would be convenient and intuitive since it would automatically catch an empty paragraph that includes any spaces. However, the current macro searching for incomplete paragraphs already includes this case.

How do we find an incomplete paragraph?

What is an "incomplete" paragraph? A working definition is a paragraph that contains at least one phrase or sentence and does not end in a finished sentence. As such, we exclude any typical sentence-ending punctuation marks:

  • Period .
  • Question mark ?
  • Exclamation mark !
  • Colon :

For an incomplete paragraph, typical last characters before the paragraph mark include:

  • Uppercase letter → "A-Z"
  • Lowercase letter → "a-z"
  • Number (as text 0 through 9) → "0-9"
  • Space → " "
  • Comma → ","
  • A few others depending on your preferences …

We are not interested in whether the paragraph ends with 145 or xyz since the last character determines whether the paragraph if unfinished (but see the last gotcha below for more). Lower and uppercase letters are separate matches even with the MatchCase property set to False due to how searches with character sets work (see below).

If we wanted to be more thorough, we could add some other punctuation marks like an em-dash, semicolon, etc. but it becomes excessive at some point. The above characters probably include 99.934% of all use cases when writing a novel or any associated notes.

What's the test?

How do we match these possible characters for our incomplete paragraph search?

  • Any of the characters are a valid match as the last plain text character of the paragraph.
  • Last character must be followed by a paragraph marker.
  • MatchWildCards property should be True, so we can test against a set of characters.
  • MatchCase property will be irrelevant for the search when using a character set, so we need to specify upper and lowercase letters separately.
  • Character matches are specific, so we do not allow the IgnoreSpace or MatchPhrase properties.
Matching using a character set

How can we specify a set of characters when using the Find object? Let's back up from the macro for a bit and just try to understand matching searches using character sets. We will work only with lowercase characters for this explanation.

When using wildcards, we can specify a character set using square brackets in double quotes "[]". A character set allows us to match a single character at a position within the search text from a set. For example, the search text "a[bcd]e" requires the following criteria for any valid matches:

  • Must begin with an "a"
  • Second character must be either a "b", "c", or a "d"
  • Last (third) character must be an "e"

So, sequences of "abe", "ace", or "ade" would match; but "age", "ale", "axe", etc. would not match. The MatchWholeWord property mentioned earlier set to True would further constrain the valid matches to be a single word or allow the valid sequences to be matched inside any longer words.

We can also specify ranges of characters with a hyphen. For example, the search text "a[b-y]z" would have the following match criteria:

  • Must begin with an "a"
  • Second character must be any character of the alphabet between "b" to "y"
  • Last (third) character must be a "z"

Character sets are a subset of what we can do with wildcard searches using the VBA Find object (or even manually the Advanced Find dialog in Word). Word allows much more specific wildcard searches which can become complex and difficult to read (more like text hieroglyphics). If you need even more robust searching in VBA, try its implementation of regular expressions (see our brief introduction for them in Word VBA).

Putting the search text together

How does all this relate to our incomplete paragraph search?

  • Any uppercase letter is the character set "[A-Z]"
  • Any lowercase letter is the character set "[a-z]"
  • Any single number is the character set "[0-9]"
  • A space is literally "[ ]"
  • A comma is obviously "[,]"
  • We don't worry about text case since both are included in character set search.

The numbers correspond to the on-screen text not the numerical values. After merging all the above into one character set (), we assign it to the SearchText variable.

SearchText = "[a-zA-Z0-9 ,]" ' Not done ...

As defined, the first character of the search text must include one of the characters in the bracket. Some general points for this character set include:

  • Literally just list all optional characters into one set of square brackets.
  • A comma is its own matched character, so we do not separate the individual characters or character ranges with commas or any other character.
  • We excluded sentence punctuations marks such as a period, question mark, exclamation point, and a colon since any of them could end a completed paragraph.
  • Upper and lowercase characters are included separately with character sets. The MatchCase property does not affect the character set part of the search text.
  • Don't neglect the space in the double quotes since it is a possible last character (most plain text is defined with double quotes).
  • Special characters are an exception which we will need next.

Of course, include any other characters you wish in your search text, but they would need to be included inside the square brackets for this part of the search.

Ending with a paragraph mark

We need to ensure the next character is a paragraph mark, so we know we've found text at the end of a paragraph. We literally add (concatenate) a paragraph mark character to the end. Our tentative final search text is:

SearchText = "[a-zA-Z0-9 ,]" + vbCr

When used as the search text, this matches any single character in the set followed only by a paragraph mark. This variable must be assigned to the Text search property before executing the search.

Assign the search text

We reference the Selection's Find method like so:

rSearch.Find ' Not done ...

We assign the SearchText variable to the Text property of Find.

rSearch.Find.Text = SearchText

The equals = symbol is all we need to assign the value since it is just plain text (as opposed to an object).

When used inside the above With statement, it shortens to:

.Text = SearchText ' Place inside the With statement

Storing the search text is an extra step, but not doing so is a little messier. Messy code is harder to read, and expending a little extra effort now to keep it clear can make returning to it easier. VBA doesn’t care though as long as the steps work, but your tolerance for what is “messy” or "concise" will improve with experience.

Execute the search

The find operation is performed by referencing the Execute method.

' Now do the search with the above search options
rSearch.Find.Execute

Since this command is still referring to the rSearch.Find object, we can absorb it into the above With statement.

' Now do the search with the above search options
.Execute ' Use this version inside the With statement

However, executing the search must be done after all the search properties have been set.

Completed With statement

Now that we've defined everything, we need for the search. The revised With statement is:

With rSearch.Find
' Setup search options
.ClearFormatting
.Format = False
.MatchWholeWord = False
.MatchCase = False
.IgnoreSpace = False
.MatchPhrase = False
.Wrap = wdFindStop ' Alternative is wdFindContinue
' More important search options for this macro
.MatchWildcards = True
.Forward = True
.Text = SearchText

' Now do the search with the above search options
.Execute
End With

This finishes the search task, but we still need to move the insertion point to the incomplete paragraph.

Move to the incomplete paragraph

When we search for text using the application's Find dialog, Word automatically selects the next occurrence of the text if it is found. It works the same way in VBA if we're using the Selection for the search, but moving a range variable around the document is invisible to the user. We want to finish the macro with the insertion point (the blinking I-bar) at the end of the newly identified incomplete paragraph, so we need a few extra steps.

Collapse the range

If the search text is found, the range is reset to span the matched text (one ending character followed by a required paragraph mark). In our use case today, we only want to move the insertion point to the paragraph when the macro is finished.

To avoid selecting the adjacent characters (since the range currently spans both of them), we need to collapse the range using the Collapse method.

rSearch.Collapse ' But we have a problem ...

Collapse does exactly what the name implies. The range's End position is set equal to its Start position, so it spans no document content. (The status of the spanned text is actually a little more complicated, but we do not need the specifics now.) Any selected text is excluded (not deleted) from the range leaving the empty range positioned by default on the left side of the found text.

Reposition the range

Collapsing the range will leave it positioned just to the left of the first character found (of the two). We need to move over one character next to the paragraph marker using the Move method.

rSearch.Move

This command would be a little different between moving the Selection or a Range variable. With the Selection, we would conveniently use its MoveRight method, but a range variable does not have all the same move methods. We must use more general Move method and give the appropriate move unit (from a table of Word Unit constants). Fortunately for our macro, the default for the Move method is by one character to the right which is exactly what we need.

Even if we chose to collapse our range toward the end, it would be positioned at the beginning of the next paragraph after the second character. The correct finishing position is between the two characters, so an extra move step would still be needed.

Select the range

Since the range location is invisible, we need to select it to actually position the insertion point at the end of the incomplete paragraph. The method is appropriately called Select.

rSearch.Select ' Now we can see it as the insertion point

Select makes whatever text the range spans (none in this case) the active document selection. In this macro, the insertion point will be placed at that location, and it will be scrolled into view if it was initially off screen.

What if it doesn’t find anything?

If the search doesn’t find an incomplete paragraph, it will not reposition the range during the Execute step. This is almost okay since we're working with an invisible range variable for most of the macro, but …

We eventually move the range into place and select it all while assuming we found a match, but the last three steps run regardless of whether the search was successful.

Ughhh. Don't get all downtrodden. We can fix it.

We only want to select the range if the search was successful, so we need to test this condition via a conditional statement and only select the range if so. Even though the range collapse and move steps are invisible to the user, it's logically consistent to include them underneath the conditional statement.

Was the search successful?

The macro should only collapse the Selection and move down a line if an incomplete paragraph is found. We can check for this using the Found property of Find.

rSearch.Find.Found

This property has a True or False (Boolean) value based on whether the latest search with that range was successful or not. As a Boolean value, we can use it directly in a conditional statement to make a decision about whether to run any additional commands.

Conditional check for the search

A rough conditional statement for validating a successful search looks something like:

If the search found the search text Then
' Move the range to the incomplete paragraph and select it
Otherise
' Do not do anything else (just finish the macro)
End the search check

We do not do anything if a search is not successful, we can just omit the "otherwise" part. Convert this into a VBA equivalent (see a previous article for more explanation of VBA conditional statements) using the Found condition.

If rSearch.Find.Found Then
' Search was successful, so move to the incomplete paragraph
rSearch.Collapse ' Since the search text is spanned by default
rSearch.Move ' Move between the two characters
rSearch.Select
End If

With this conditional statement, we only move and select the range if the search step was successful.

Combine the two searches

It's consistent with the intent of the macro to just combine the empty and incomplete paragraph searches. We just need to add an extra paragraph mark inside the character set (the square brackets). A paragraph mark is vbCr, but … we need the character to be inside the square brackets, so it works like any of the other optional first characters. It looks messy, but the most consistent way to do this with how we've been working is to insert another paragraph mark character in between the square brackets.

SearchText = "[a-zA-Z0-9 ," + vbCr + "]" + vbCr

The right square bracket is to the right of the newly inserted paragraph mark character. We used plus + signs to concatenate the strings together into one. If this is too cumbersome, an alternative messier, but less clumsy, notation uses "^13" for a paragraph mark character (but see the gotcha below).

SearchText = "[a-zA-Z0-9 ,^13]^13" ' Alternative, so just use one

The second vbCr was also swapped out for a "^13" character for consistency, but the second is still placed outside the square bracket as before.

Fortunately, the move logic after a successful search still works for an empty paragraph without any changes.

What about searching backward?

Sometimes, we want to search backward in the document. Find searches forward by default, but it has a Forward property to control the direction.

' Search backward in the document
.Forward = False ' Change to False inside the With statement

The Forward option defaults to True, of course, to search forward in the document, so we only need to assign a value of False to it to search backward instead.

Don't over complicate things

It's worth taking a teaching moment here. Don't create unnecessarily complex solutions to simple problems. For example, when correcting the final position in an early version of this macro, I originally included a nested conditional statement something like the following (not exactly, but this is the gist of it):

' This nested conditional statement is more complicated than necessary
If rSearch.Find.Found Then
rSearch.Select ' Select the found range

' Check for type of paragraph found
If Selection.Characters.First.Text = vbCr Then
' Found an empty paragraph so collapse Selection and move down
Selection.Collapse
Selection.MoveDown
Else
' Found an incomplete paragraph so collapse Selection and move
' right past the first character
Selection.Collapse
Selection.MoveRight
End If
End If

This makes sense, and it solves the final position problem. However, when I added the MoveRight step at the bottom, I realized it would work for both conditions making it a better solution overall. The final implementation ended up using the Move method of the working range, but the idea is the same.

Alternative command to execute the search

If burying the Execute command inside the With statement feels a little too concise, it can be extracted on its own outside the statement.

' Now do the search with the previously defined search text
rSearch.Find.Execute FindText:=SearchText ' Alternative (only use one)

This version includes the FindText property (making the Text property inside the With statement redundant). It's mostly a matter preference which one you use, but I would favor this version if I were performing multiple searches with different search texts.

Any gotchas?

As always, we should consider what could go wrong when the macro runs.

Search settings are sticky

Search settings carry over between independent uses of the macros or even the manual Advanced Find dialog in Word. We largely solved this issue by specifying a long list of relevant search options covering most use cases. If you encounter any special cases for your situation, just add the additional fixed search parameters inside the With statement.

What about the first paragraph of the document?

An empty first paragraph of the document is an exception to the above search condition since it will not have a preceding paragraph marker. We'll ignore this rare special case to keep things simpler. Also, a separate standard Word shortcut already exists (Command+Home in Word for Mac or Control+Home in Windows) to jump to the beginning of the document anyhow.

What about paragraph styles?

The above searches look only for plain text matches because we explicitly excluded any formatting or style constraints.

A more complete implementation of the current macros would also detect and include or exclude certain paragraph style(s). The Find search parameters include a Style option, but it does not quite work for these macros when searching for an empty paragraph. Ignoring any spaces, detecting an empty paragraph requires two adjacent paragraph marks, so the Style property may be ambiguous if the two paragraphs have different styles.

A full solution is more complicated than it might appear at first glance, so it is outside the scope of this article. Understanding this limitation, if you still wish to add a basic paragraph style restriction, simply assign the desired style name to the Style property.

' Restrict search to the following style (may fail for empty paragraphs)
.Style = "Normal" ' Include inside the above With statement

The style property can actually accept more values (see the Word built-in style constants table), but it's usually more convenient to just refer to the desired style name.

Restricting any matches to a single style is limiting (see improvement ideas below for more explanation), but it works most of the time if you use the same style when writing any novel body text.

Trouble with wildcard paragraph searches

Unless you need the nitty gritty details, you can probably skip this warning, but I should mention it for completeness. If you've searched elsewhere on the internet, you may have noticed a character "^p" representing a paragraph for some searches in Word.

I might prefer this representation over vbCr, but "^p" does not work when using wildcard characters (which is a little odd). We instead need to messily refer to a paragraph mark as "^13" when searching for paragraphs in the Advanced Find dialog. In macros, the vbCr paragraph mark constant also works, so we're okay, but why "^p" is disallowed as a paragraph character when using wildcards but "^13" works is a mystery.

Final macros

The final macros are almost identical since the move steps after a successful search don't change.

Even if you're skeptical of their practical use, give them a try. I would not categorize them as my most productive macros, but I like making an edit elsewhere in the document and just jumping back to where I was working. Once I got used to them, I would occasionally leave an empty paragraph where I’m working on purpose if I know I'll be back there within a few minutes.

Searching forward in the document

Collecting the above commands, the macro to look for an empty or incomplete paragraph in the forward direction is:

Sub GotoNextIncompleteParagraph()
' Jump to the next empty or incomplete paragraph in the document

' Set the working range to the selection
Dim rSearch As Range
Set rSearch = Selection.Range

' Define search text to include an empty or incomplete paragraph
Dim SearchText As String
SearchText = "[a-zA-Z0-9 ," + vbCr + "]" + vbCr

' Define the search options
With rSearch.Find
' Setup search options
.ClearFormatting
.Format = False
.IgnoreSpace = False
.MatchPhrase = False
.Wrap = wdFindStop ' Alternative is wdFindContinue
' These options are redundant with the character set search
.MatchWholeWord = False
.MatchCase = False
' Restrict search to a style (may fail for empty paragraphs)
'.Style = "Normal" ' Remove comment to enable
' More important search options for this macro
.MatchWildcards = True
.Forward = True
.Text = SearchText

' Now do the search with the above search options
.Execute
End With

' Move to the end of the paragraph if it was found
If rSearch.Find.Found Then
' Search was successful, so move to the incomplete paragraph
rSearch.Collapse ' Since the text is spanned by default
rSearch.Move
rSearch.Select
End If
End Sub

If you've come straight to the final macros, the default Forward option value is set to True because search options are saved between searches. We do not want a later backward search to mess up the forward version or vice versa. I generally assign this macro to Command+Control+Space in Word for Mac and Control+Space in Windows.

Searching backward in the document

For the backward search, the only change is setting Forward option value to False, but Word requires a separate macro before we can assign it to a shortcut.

Sub GotoPreviousIncompleteParagraph()
' Jump back to the previous empty or incomplete paragraph in the document

' Set the working range to the selection
Dim rSearch As Range
Set rSearch = Selection.Range

' Define search text to include an empty or incomplete paragraph
Dim SearchText As String
SearchText = "[a-zA-Z0-9 ," + vbCr + "]" + vbCr

' Define the search options
With rSearch.Find
' Setup search options
.ClearFormatting
.Format = False
.IgnoreSpace = False
.MatchPhrase = False
.Wrap = wdFindStop ' Alternative is wdFindContinue
' These options are redundant with the character set search
.MatchWholeWord = False
.MatchCase = False
' Restrict search to a style (may fail for empty paragraphs)
'.Style = "Normal" ' Remove comment to enable
' More important search options for this macro
.MatchWildcards = True
.Forward = False ' Search backward
.Text = SearchText

' Now do the search with the above search options
.Execute
End With

' Move to the end of the paragraph if it was found
If rSearch.Find.Found Then
' Search was successful, so move to the incomplete paragraph
rSearch.Collapse ' Since the text is spanned by default
rSearch.Move
rSearch.Select
End If
End Sub

I assign this macro to Option+Control+Space in Word for Mac and Alt+Space in Windows.

Improvements

Even with the increased utility of allowing incomplete paragraphs, we could still improve a few things.

Extract the common steps

It's almost embarrassing how many steps are the same between these two macros, so one of the first changes would be to extract those steps and make a function out of them. The streamlined search macros would then just call that function.

With this example, it’s easy to see why programmers invented functions and subroutines decades ago. It would make the main macro easier to read, and any changes to the search would only need to be made in one place. Using a function is not strictly necessary, and doing so is more complex than this article permits, so it is omitted for presentation purposes.

Target paragraph styles

I prefer to restrict the search to relevant paragraph styles such as "Body Text" (or variations) in my novels or "Normal" or "Note" (not headings) in my note documents.

Find has a Style property to restrict the found text to within a given paragraph or character style.

' Place inside the earlier With statement
.Style = "Some Style"

It would be a nice addition because certain paragraph styles like headings usually don’t have ending punctuation marks. They would match the search text resulting in logically correct but nevertheless undesired match. For my own writing approach, I also insert inline notes into my novels with distinct paragraph styles, and I usually want to skip them for purposes of this macro.

Unfortunately, including a style restriction would only work for an incomplete paragraph search. It won’t work for any strictly empty paragraph searches.

Why?

The empty paragraph search (without spaces) spans two paragraph marks meaning the two paragraph styles could be different. The Style option may not properly match the indicated paragraph style since the style of the range would be ambiguous. The search would not count it as a match leaving the search to either continue or fail if no other possibilities exist.

It would be convenient to allow multiple paragraph styles for a valid match, but the Style property of Find does not allow it.

Further, the targeted styles will likely also vary by document type (notes, novels, etc.), so a full implementation would require additional logic to detect the document type and pick the desired target style. Together, the changes are outside the scope of this article.

Limited search range

My version also limits the search range to a match within few pages since I tend not to like the idea of jumping to a random, distant location in my novel where I forgot to finish a line. I suspect this behavior is a preference that may not be shared by some readers, so I won’t go through the details in article form unless significant interest is expressed.

Fix valid paragraph side effect

The above macros would detect the following paragraph as incomplete (double quotes are added for clarity):

"This little piggy went to market. "

Why?

If it were dialog, the space is obvious and annoying, so we would likely remove it.

Most people work with spaces not visible, so without the double quotes, human eyes would see it as one-sentence paragraph with no other obvious text afterward. Word sees the space at the end. Of course, we would likely trim these excess spaces before publication, but they are inevitable in a working novel with literally thousands of lines.

Is it a big deal?

Not really.

These extraneous spaces could just be deleted as they are found, but this misidentification of an incomplete paragraph could be corrected inside the macro to avoid the annoyance.

On the plus side, the necessary macro changes to fix it coincide with what is required to implement the most general style restriction improvement above. On the negative side, sometimes implementing further improvements becomes a matter of how much effort we're willing to expend compared to the payoff, but it's outside the scope of this article regardless.

Affiliate Links

If you're interested in using Word or another tool related to the article, check out these affiliate links. I may make a small commission if you purchase when using them, but there is no increase in cost for you, and it helps to support this site and associated content.

I've been using Microsoft for Business for commercial use (that's us writers) on one of the lower pricing tiers for years. I get to use my macros, have online storage, and don't have to worry about software updates.