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

Extend heading selection

Word • Macros • Editing • Selections
Peter Ronhovde
36
min read

Making selections is a fundamental aspect of editing in a word processor, so anything that can expedite the process can help us edit faster. Tools to manipulate headings are limited in VBA compared to those available for paragraphs or words. Toward this end, we’ll create another tool for our toolbox in the form of a macro that quickly extends a heading selection up or down in the document over successive headings.

Thanks for your interest

This content is part of a paid plan.

Extend Heading Selection

In Microsoft Word, headings are basically a document range more like sentences or words rather than more fundamental document elements such as paragraphs or sections. We have some out-of-the-box tools to manipulate headings in the application and with the corresponding tools in Visual Basic for Applications (VBA). Both are rather limited compared to what we can do with other document elements like paragraphs or words (see previous article for more explanation). Mostly, we can only control heading paragraph styles and navigate to standard headings using the GoTo tab in the Advanced Find dialog.

Thus, we need to machine our own headings tools to facilitate our writing and editing processes. In this article, we focus on extending a heading selection to quickly select a range of chapters or scenes in a document which we can then move or delete as needed.

This macro leans toward my personal editing method where I outline a novel by chapter and scene as it grows to better track the story progression. If that’s not your style (no pun intended), it’s also useful in my novel and blog notes documents, so it should be general enough for anyone to benefit from it.

If you prefer to shortcut around the commentary, skip ahead to the macro explanation … otherwise, allow me to briefly sketch out my process along with some additional motivation for this macro.

My evolving novel outline

I usually maintain an overview of the evolving story in my head, but sometimes the details escape me. As a result, I began inserting chapter and scene headings as I write. I use the obvious story divisions including Acts, Chapters, and Scenes; but I eventually added Subscenes also since I discovered I wanted a more detailed view of how the scenes developed.

These styles are derived from the standard Word styles Heading 1 through 4, respectively, with unique paragraph styles to make them obvious at a glance. The fact they are “derived” heading styles makes creating macros a little more trouble than it might appear at first but see this previous article for more explanation.

Not a “plotter” outline

While I desperately want to plot my novels in detail, it just doesn’t work for me. My novel outline isn’t about nurturing or enforcing any specific plot or story structure. It’s much more about tracking the novel’s development as it grows and evolves, so I don’t get lost in my own story.

Extracting scenes is easier

Word’s expanding and collapsing heading levels are tedious to use for a novel in my opinion. When the details or even just the overall flow of the story become muddled in my mind, I use another macro to extract the outline and any relevant notes (see separate article on my novel notes macros) into a separate document. I use it as an overview to refresh the story progression in my mind. We’ll work through that extraction macro another day.

Reordering and reorganizing text

Incorporating a novel outline using headings also makes reordering content easier as the writing and the editing progresses. I prefer to keep the whole novel in a single document, at least until it becomes overwhelming, which allows me to move text between and adjust chapter and scene boundaries more easily. In Word for Windows, we can also drag and drop headings in the Navigation Pane although occasional formatting issues may occur.

Stripping novel notes and headings

If you’re concerned about all the extra fluff text “cluttering” your novel, another macro easily strips all headings and novel notes when I get ready to publish. I lose essentially nothing while maintaining an annotated and structured novel to guide me as we write and edit. We may cover this macro to strip headings and notes in a future article depending on the interest level.

Building “next” heading extension version from scratch

Except for an optional pair of functions to toggle screen updates, we will not rely on any previous functions for the essential features of today’s macros. We’ll instead work out all steps from scratch. Normally, this would be counterproductive since part of the point of creating functions is to save time and effort later, but we’ll explain more about why as we proceed.

Heading terminology

Let’s establish some common terminology. When I refer to a “heading paragraph”, I mean the first paragraph of a heading. The “heading content” is all the text including any subheadings below it. A “heading” is all the above.

"Derived" headings are headings that are based on one of the "standard" Word Heading 1 through 9 paragraph styles.

Macro skeleton

We have two macros today, one to extend the heading selection up and another for down. Our macro skeletons are:

Sub ExtendHeadingSelectionNext()
' Extend current heading selection forward in the document

End Sub
Sub ExtendHeadingSelectionPrevious()
' Extend current heading selection backward in the document

End Sub

The differences between the macros make combining into one messy (more like just jamming them together), so we’ll keep them separated. The internal steps of the two macros are similar, but the “previous” version requires extra steps to properly extend the selection backward in the document.

Macro naming “system”

The macro names may sound a little awkward, but it’s done with intention.

Naming pattern

I prefer a naming pattern including a general category usually consisting of an “action” and a “what”. The category is followed by any necessary details about the task. For example in these macros, the category action is “Extend”. The category what is “HeadingSelection” with two specific variations “Next” and “Previous”.

Capitalization

The word capitalization scheme is called “Pascal Case” (first letters of words are capitalized, and all words are jammed together). It just makes the name easier to read while including more details about what it does.

Do I always follow this naming pattern?

Well … my macro editing “library” has expanded over time while I learned more about VBA and my own editing process, so not really. All this is optional, but I try to create newer macros with better names that make them easier to find.

Don’t overthink it

The naming scheme is meant to help you organize your macros and better understand what they do at a glance. You’ll probably be surprised how fast your list of editing macros grows. Ignore all this if it’s confusing, but you’ll thank me if you use something like it earlier rather than later because sometimes I still need to scroll, scroll, scroll to find my own older macros.

Subroutines required for Word keyboard shortcuts

Since we plan to assign these macros to keyboard shortcuts in Microsoft Word, they must be subroutines, abbreviated “Sub” in VBA, without any parameters. If you happen to own Dragon Professional, custom voice command scripts that run Word macros aren’t restricted in the same way (see this previous article if you’re interested in learning more).

Set working ranges

We’ll need two range variable(s) to work with the document content.

  • A range variable to track the current heading around the user’s starting position or selection. This content may or may not be selected when the macro starts. We’ll name variable this rCurrent.
  • A range variable to locate and span the next heading range. We’re working forward in the document, so we’ll name this second range variable rNext. We’ll use rPrevious in the second macro.

I use rCurrent and rNext just to keep the names a little shorter than being even more descriptive like rCurrentHeading and rNextHeading since we already know we’re working with heading ranges. This is a balancing act of using names that mean something but not making them so long that they are tedious to use.

We can define both range variables on one line if we separate them by a comma.

Dim rCurrent As Range, rNext As Range

I like preceding range variable names with an “r” to clearly indicate the type of variable within the macro, but this isn’t required.

Once each is properly identified, the intention is to merge the two ranges to complete the macro task.

What is a Range?

A Range variable is akin to an invisible Selection or insertion point (i.e., where the blinking I-bar is waiting for you to type text in the document). They share many features, such as being able to span text or move around the document, but range variables are not visible to the user unless document changes are made, and no information about them is saved when the macro ends.

Among other properties, Ranges have Start and End positions literally corresponding to character positions as counted from the beginning of the document. We can reference these positions as with any other property, and we’ll need them later.

What is the Selection then?

The Selection is the persistent object in VBA that tracks the user’s actual selection or position in the active document (the ActiveDocument is also a Word object in VBA). This applies whether the current document selection includes any spanned text or if it is just an empty insertion point. The Selection has additional properties (data) and methods (actions) associated with it allowing us to manipulate it in the document.

Any changes to the Selection are updated on the screen in real time while a macro runs. Rapid updates sometimes causes the screen to “flicker” which is why it’s usually better to avoid using the Selection for document manipulation in anything but the shortest macros.

If we can’t avoid using the Selection (as is the case for today’s macros), we can turn off screen updates (as well as document pagination) to hide any Selection changes while a macro runs (see previous article).

Sneaky Variant types

As an aside, both variable declarations above need to include the “As Range”, or the missing one will actually be a generic Variant data type. For example, if we left As Range off the first variable, it would look like:

' rCurrent will be a Variant type not a Range!
Dim rCurrent, rNext As Range

On the other hand, a Variant variable can be anything, so this would still work in many macros as long as we’re not trying to pass the heading range to a macro or function requiring a range variable parameter (they are picky about specific types).

Get the current heading range

In our macro today, we need to get the heading range around the current user position in the document.

What is a bookmark (brief)?

At the application level, a bookmark is just a convenient way to mark and later jump to a specific position or content in a document. In VBA, a bookmark is like a range since it can span document content or mark a document position, but it contains more information defining what a bookmark is internally to Word.

Differences include Bookmarks can have names whereas ranges cannot other than what we name the variable. Bookmarks are also persistent in the document even after the macro finishes running. We've utilized this second feature in previous macros to quickly navigate a document.

Word also keeps an internal list of hidden, pre-defined bookmarks …

HeadingLevel bookmark

We access the current heading range using a pre-defined Word bookmark named \HeadingLevel (yes, the backslash \ is required at the beginning of the name). Since it’s a plain text name, we need to include it in double quotes when referencing it in the bookmarks collection.

Selection.Bookmarks("\HeadingLevel")

This bookmark is invisible to the user in Word even in the GoTo tab of the Advanced Find dialog, but we can reference it in VBA using the Bookmarks collection of the Selection or the ActiveDocument. The (big) caveat is it only works for the heading range around the current Selection.

Required for derived heading ranges

This bookmark is the only reasonable way we can manipulate derived headings in Word VBA. Given its quirks, this is the main reason we’re using it in this macro since all my novel chapter and scene headings are derived from the standard Word headings 1 through 4.

Get the bookmark range

Bookmarks are like a range, but they’re not exactly a range. If we want to store the range corresponding to the \HeadingLevel bookmark, we need to reference its Range property.

Selection.Bookmarks("\HeadingLevel").Range
Store the current heading range

We store the current heading range in the earlier range variable rCurrent.

Set rCurrent = Selection.Bookmarks("\HeadingLevel").Range

Our range variable rCurrent is now assigned to the entire heading range around the initial position or selection in the document. Ranges (and other objects in VBA) require us to use Set because they contain more information than a typical value like a number.

Need the Start position

We technically only need the Start position of the current heading in this macro to properly extend the selected heading range later. Our available tools in VBA to navigate between derived heading styles are limited, so the easiest way to determine the Start position is to just span the entire heading range.

Current heading range choice

The correct choice for this range assignment is more subtle than it may seem at first glance. For instance, we could not assign Selection.Range to our rCurrent heading variable.

Set rCurrent = Selection.Range ' May not span entire heading

This range is correct if the user has already selected a heading range (such as occurs with repeated uses of this macro), but it may not span the whole initial heading in other circumstances. We would still need to access the range of the \HeadingLevel bookmark range to be sure rCurrent spanned the entire first heading under consideration which makes this assignment redundant.

An independent range

Our heading range rCurrent is independent of the Selection or even our other range variable unless specific changes are made to the document content inside the range. Using range variables in macros allows us to manipulate the content more easily.

Get the next heading range

We need to identify the tentative “next” heading range in preparation to merge it with the current range later. Next in our context implies the heading range following the last heading of the user’s initial selection. The idea behind this macro is to select successive headings, so in principle, the initial selection could span more than one heading.

Start with the current Selection

We initially set our next heading range rNext to the current user’s selection. We’ve done this before, so we need to reference the Range property of the Selection.

Set rNext = Selection.Range

This is just a starting point.

Initial next heading range choice

The correct range assignment here is more subtle than it may seem at first. Using the initial selection as a starting point for rNext is essential to making the extension work correctly if it is to naturally extend over successive heading ranges.

Using initial heading only works for first heading extension

For example, we could not assign rCurrent as the initial rNext range.

Set rNext = rCurrent ' Does not work in general

This seems like an obvious choice, but it doesn’t work as a starting point for rNext when the user’s initial selection spans more than one heading range. rCurrent only spans the current (first) heading range, so the steps below would only identify the heading immediately after rCurrent as the “next” one. We want our extension to successively include more headings if we run the macro multiple times.

Initial partial heading selections

Another advantage of using Selection.Range as the starting point for rNext is it also works well when the initial selection spans only part of one or more headings. In either case, the following steps would just naturally extend our selection over the entirety of all of the headings.

Move to the next heading using Collapse

We need to move the rNext range from spanning the current user-selected text to the next heading range. Unfortunately, the regular Move methods of range variables do not have Unit constants referring to headings (see previous article for more explanation), but we can still effectively make the move by collapsing the selection toward the End position.

rNext.Collapse Direction:=wdCollapseEnd

We needed to specify the Direction option as wdCollapseEnd because the default is to collapse toward the Start of the range.

If you’re not used to VBA in Word, you might squint at the screen a little at this command, but using the Collapse method for range movement is not uncommon in VBA.

What if rNext ends at another heading?

An important use case is extending the user selection over successive headings. In these cases, rNext would end at a new heading, so the collapse de facto moves the range to the next heading since the last heading selected logically ends at the beginning of the next one.

What if rNext ends inside a heading?

If rNext doesn’t span up to the end of a heading, then after the collapse, we’ll be positioned somewhere inside the last heading range. This is still mostly intuitive for this macro because the later extension will just expand to include the entire last heading.

No extra move necessary (for next version)

We do not need to include a follow-up move command to move “inside” the next heading.

rNext.Move Unit:=wdCharacter ' not necessary (omitted)

The beginning of the heading is considered part of the heading, so we’re okay. However, we will need something like this move step when extending the heading selection backward in the document (see following macro below).

Selecting the next heading

Sit up straight because this gets a tad confusing to describe in text form (as if it hasn’t already).

The rNext range variable is now positioned at the beginning of the next heading, but we need to get the entirety of the range and reassign it to rNext.

Still need \HeadingLevel bookmark

Since we’re still using derived heading styles, we can only accomplish this using the pre-defined \HeadingLevel bookmark. Unfortunately as mentioned earlier, this bookmark only works for the heading range around the current Selection (we have no control over this requirement), but the Selection still resides inside the initial heading above … not with rNext.

Ughhh.

The Selection is not in the same heading as rNext, so if we use this mysterious Word bookmark, we’ll get the same range as earlier.

No help (so far).

Force Selection move with range Select

The rNext range is now positioned at the beginning of the next heading range, so we need to select it to force the Selection to move into the same heading.

rNext.Select

Now Word automatically updates the \HeadingLevel bookmark in the background, and we can use the bookmark to span the next heading range.

This step acts as if the user intentionally clicked at the same location as rNext in the document. It just happens at computer speed in VBA. As a result, this step may cause the display to flicker since Word will update this on screen immediately, but a later step again changes the Selection in rapid succession.

I cringe when including this step, but we have no choice if we want to work with derived heading paragraph styles.

Store the next heading range

The Selection is now properly positioned at the same document position as our rNext range—the beginning of the next heading. We again access the \HeadingLevel bookmark in the Bookmarks collection of the Selection. Then using the bookmark’s Range property, we store (update) the next heading range in the rNext range variable.

Set rNext = Selection.Bookmarks("\HeadingLevel").Range

This range is independent of rCurrent or the Selection.

Extend the range

We now have two range variables we want to merge. The first, rCurrent, stores the current (original) heading range; and rNext stores the full heading range after the initial selection. Our extended selection should start at the top of the initial heading and extend downward in the document to the end of the next heading.

How do we assign this in VBA?

Ranges have Start and End positions which we reference just like any other property:

rCurrent.Start
rNext.End

More specifically, our extended selection begins at the Start position of the initial heading at rCurrent.Start. At the bottom, we use the End position of the next heading range corresponding to rNext.End.

Reassign the extended range

There are several ways to define the extended range, and all are equivalent. We’ll reference the SetRange method just to keep the command on one line. We’ll also reassign the extended range to the rCurrent range, so we don’t need to define a new range variable.

' Redefine current heading range to extended range
rCurrent.SetRange Start:=rCurrent.Start, End:=rNext.End

The Start and End options just correspond to the new Start and End positions of the referenced range rCurrent. Command options with assigned values require a colon-equals := symbol rather than just an equals = sign (see alternative assignments below). Multiple options must be separated by commas.

Alternative longer version

The SetRange method is just a shorthand command to set the Start and End positions of a range. It saves us from using two lines for the position assignments like:

' Two line version of above (not necessary)
rCurrent.Start = rCurrent.Start
rCurrent.End = rNext.End

Here, we use equals = signs because the Start and End range properties are document positions which are just regular numbers.

Select the extended range

Currently, this newly extended range variable is still invisible to the user, so we need to select it to make the changes visible on screen.

rCurrent.Select

That’s it as far as a necessary macro logic.

Using the Selection is (usually) bad

In general, manipulating the Selection directly in a macro is not the best choice. It’s slower overall since all changes are updated on the screen in real time while the macro runs which is not ideal (but we can work around this aspect below).

Moreover, only one Selection exists in each document. Earlier, we defined two range variables to work within our active document which made the logic easier to work through. Some macros might not even work using only the Selection.

Why use the Selection in these macros then?

If you missed the comments above, we’re manipulating the Selection directly in this macro because we need to access Word’s predefined \HeadingLevel bookmark. This bookmark spans the heading range around the current user Selection in the document.

The \HeadingLevel bookmark range is updated automatically by Word in the background even while a macro is running. It is invisible to a user outside of VBA, but unfortunately, it is only available in VBA using either the Selection or the ActiveDocument.

Disable screen updates while running

A previous article explains how to disable screen (as well as document pagination) updates inside a VBA macro. Since we’re manipulating the Selection directly (see above comments), we can mitigate any screen flicker issues by disabling screen updates while the macro runs. The macro name is:

DisableScreenUpdates
' Other macro steps ...

Then just before the macro finishes, we re-enable screen updates. The second function is:

EnableScreenUpdates

We don’t need to use “Call” before either macro because they require no arguments.

Not a serious issue in this macro

I’ve used these heading extension macros below both with and without screen updates, and any screen flicker is minimal, so I would not consider this an essential addition. It is, however, a good practice just to be clean and clear.

Just be careful when debugging your macros. Although, even then, the chance of a problem is probably minimal since Word doesn’t seem to like leaving screen updates off after a macro runs, but it can happen.

Gotchas

What could go wrong in this macro? There are several things to consider in this pair of macros.

Range subtleties of this macro

This macro has at least three places where it’s easy to get off track or to make a subtle logical error that only works for certain use cases. It’s a good example of why you should playtest your macros (yes, sometimes creating macros is “playtime”; just admit it.) before assuming they’re done.

A related issue is when the initial Selection only partially spans two or more headings. Fortunately, the existing macro logic just selects both (or all) of the partially spanned headings, so no problem exists here.

What if the \HeadingLevel bookmark doesn’t exist?

Since \HeadingLevel is a bookmark, it might not exist … right? Not all documents have headings—

Well, it is a pre-defined Word bookmark.

In my testing, I’ve had no problems using the macros even in new documents with no headings and limited text, so Word seems to account for missing headings. In the interest of keeping the macro simpler, I omitted any range validation steps based on this pre-defined bookmark.

What if no “next” heading exists after the current one?

If no heading exists after the current one, the macro simply doesn’t move the next range variable rNext forward in the document, and the Selection doesn’t change. This is validated with testing, so no problem exists here.

What about higher priority heading levels?

Headings exist in levels 1 (highest priority) to 10 (body text). Since we’re working with headings, our macros will eventually encounter heading ranges with different levels. How do the macros handle them?

Next heading range version

Yeah, if the next heading range is a higher priority heading level, the macro will extend over the entire heading content including any subheadings.

This is recognized but accepted as a reasonable way for the macro to work—

But … shouldn’t we “fix” it?

Okay, since you brought it up, let’s think about it a little bit longer. A slew of questions comes to mind:

  • What does the user want in each use case?
  • Why “shouldn’t” it expand over the entire next higher priority heading?
  • Is extending over the same heading level always correct?
  • What makes the most sense?
  • What is the “right” way for it to work?

We could try to somehow restrict the extension to the “top” of the higher priority heading content, but that doesn’t make sense to me. In my mind, no clear argument or need exists to restrict the extended range to just part of a higher priority heading it encounters.

The “right” solution isn’t obvious or even clear enough to me to spend the time and effort to “fix” it.

Sometimes just make a choice

Sometimes we just need to decide how the macro works. As long as that choice is reasonable and relatively intuitive, it’s probably okay.

Previous heading range extension version

If the backward (previous headings) version encounters a higher priority outline level, it will automatically expand over the entire higher level heading range. Similar to the above argument for the next heading version, this is reasonable behavior, so we won’t override it.

Final extend to next heading range macro

Putting the steps together, our heading range extension macro is:

Sub ExtendHeadingSelectionNext()
' Extend heading selection over next heading range
' Select current heading if not already spanning a heading range
' Expands selection if it encounters a higher priority level heading
' Also works for derived heading styles

DisableScreenUpdates ' Not required

' Declare range variables for current and next headings
Dim rCurrent As Range, rNext As Range

' Get current heading range (also works for selections across headings)
Set rCurrent = Selection.Bookmarks("\HeadingLevel").Range

' Get next heading range
Set rNext = Selection.Range ' Begin with initial Selection
rNext.Collapse Direction:=wdCollapseEnd ' Move to next heading
rNext.Select ' Select range to use Word bookmark
Set rNext = Selection.Bookmarks("\HeadingLevel").Range

' Redefine current heading range to the extended range
rCurrent.SetRange Start:=rCurrent.Start, End:=rNext.End

' Select extended or expanded heading
rCurrent.Select

EnableScreenUpdates ' Required if disabled above
End Sub

Modifications for previous heading range version

When extending backward in the document instead, the bulk of the macro steps carry over with appropriate obvious modifications to account for the change in direction. We will only cover the differences to shorten the presentation (no, that's not sarcasm).

New previous range variable

A variable name change is trivial, but for completeness, we use rPrevious to represent the previous heading range over which we will extend the current heading selection.

' Declare range variables for current and previous headings
Dim rCurrent As Range, rPrevious As Range

Get previous heading range

The next step that changes is when we move to the previous heading.

Collapse toward Start position

We move the rPrevious range by collapsing it, but we need to move toward the beginning of the document. This means we need to collapse toward the Start position of the range.

rPrevious.Collapse

Given the next step is a move, this step is actually redundant (the following move will initiate from the start of the range), but it is still clearer to include it.

Move into previous heading range

When we collapse toward the Start of the range, we’re still inside the current range, so we need to move our collapsed range into the previous heading content. It is sufficient to move backward by a single character, so we use the Move method.

rPrevious.Move Unit:=wdCharacter, Count:=-1

As discussed in other articles, the Move method can take a Unit option which can be any value in this standard Units table. We choose a character step size with wdCharacter. We then specify the Count option to be negative 1 since the default is to move forward by one unit. A negative value moves backward in the document.

This move places the collapsed range just to the left of the last paragraph mark in the heading, so the \HeadingLevel bookmark used later will properly recognize the previous heading range.

Encountering a higher heading level

When we’re moving backward in the document, we can encounter headings with a higher priority level than the current heading level. How does our selection extension macro handle this? How should it work?

The previous heading range identification using the \HeadingLevel bookmark would naturally expand across the entire heading including any subheading content. In this macro, the expanded range would encompass our current rCurrent heading range.

This is acceptable behavior to me, so the next question is how do we detect a higher priority heading level?

Conditional statement to detect overlapping ranges

Instead of detecting the higher heading level directly, we just need to detect whether the ranges overlap. More specifically, we need to know if the “previous” heading range has expanded over our current heading range. The conceptual If statement logic to decide what to do is:

' Check whether previous range overlaps current range
If current heading range is inside previous range Then
' Span entire previous heading range ...
Else
' Extend current heading range backward to start of previous heading range ...
End If

The Else part is the regular case for this macro. See this previous article if you want to review conditional statements in VBA.

Checking for one range inside another

All range variables include an InRange method. If we want to know whether the current heading range rCurrent is inside another range we reference the InRange method and give it a range to test:

rCurrent.InRange(rPrevious)

This statement asks whether the rCurrent range is inside the rPrevious range. The result is a True or False (Boolean) value we can use in the more detailed conditional statement below.

Span the full previous heading range

If the ranges overlap we just want to span the full previous heading. We just redefine the current heading range to be the previous one.

Set rCurrent = rPrevious

This range includes the old “current” heading range, but it may extend beyond it in either or both directions. That was easier than all the hoopla above made it sound.

Span the extended range

Extending the range is similar to the earlier macro, but we instead need to set the Start position of our extended range to the Start of the previous heading range and the End position to the End of the current heading range. We again use the SetRange method and give it these Start and End positions.

rCurrent.SetRange Start:=rPrevious.Start, End:= rCurrent.End

Conditional statement

Putting the earlier conditional statement and the two range assignments together, our more specific conditional statement is:

' Check whether previous range overlaps current range
If rCurrent.InRange(rPrevious) Then
' Previous overlaps current, so span entire previous heading range
Set rCurrent = rPrevious
Else
' No overlap, so extend current heading range back to start of previous heading range
rCurrent.SetRange Start:=rPrevious.Start, End:= rCurrent.End
End If

If you don’t like this overlapping range check, the following alternative version based on outline levels after the macro.

Alternative heading level validation for range expansion

An alternative validation check for whether we need to extend or expand the current selection uses the outline level of the heading paragraph. Only one version is needed.

Technically, the earlier InRange method doesn’t actually validate whether we’ve expanded over the higher-level heading range. It just checks whether one range is inside the other. It shouldn’t be a problem in practice, but we can be more specific if we insist.

Define outline level variables

If we want to be more concrete, we can compare the actual heading (outline) levels before expanding our heading selection. It will be easier to read if we use a couple outline level variables.

' Declare outline level variables
Dim initialHeadingLevel As WdOutlineLevel
Dim extendedHeadingLevel As WdOutlineLevel

Technically, these data types are defined using the WdOutlineLevel enumeration (an "enumeration" is just a list of constants with nicer names), but that is overcomplicating things. In the end, they are just outline level numbers 1 (highest priority heading level) through 9 (lowest priority heading level) with 10 corresponding to body text.

Store current heading level value

After selecting the current heading range, we can store the outline level of the first paragraph.

' Store initial heading level for later comparison
initialHeadingLevel = rCurrent.Paragraphs.First.OutlineLevel

We’re referencing the First paragraph of the Paragraphs collection which is the heading paragraph. Every paragraph has its own OutlineLevel property which is just a numerical value between 1 and 10 as mentioned above, but since this command references the heading paragraph, we know we’re getting the outline level of the heading.

Store previous heading level value

Once we define our previous heading range, we can also store its outline level in the same manner.

extendedHeadingLevel = rPrevious.Paragraphs.First.OutlineLevel

Again, we’re referencing the first paragraph since it is the heading paragraph.

Outline level comparison

We set up a conditional statement where we literally just compare the two numbers using a less than < sign.

extendedHeadingLevel < initialHeadingLevel

A lower number is a higher priority heading.

Conditional statement

If we find a higher priority outline level, we expand the range over the full previous heading. Our conditional statement is then:

If extendedHeadingLevel < initialHeadingLevel Then
' Previous heading is a higher priority level, so expand over all of it
Set rCurrent = rPrevious
Else
' Extend over previous heading range
rCurrent.SetRange Start:=rPrevious.Start, End:= rCurrent.End
End If

The InRange version is more concise, but this version is more explicit. I prefer the more concise version in this macro.

Final extend to previous heading range macro

The “previous version” requires extra steps compared to the above version to properly check the extended or expanded heading range.

Sub ExtendHeadingSelectionPrevious()
' Extends heading selection over previous heading range
' Select current heading if not already spanning at least one heading range
' Expands if it encounters a higher priority level heading
' Also works for derived heading styles

DisableScreenUpdates ' Not required

' Declare range variables for current and previous headings
Dim rCurrent As Range, rPrevious As Range

' Get current heading range (also works for selections across headings)
Set rCurrent = Selection.Bookmarks("\HeadingLevel").Range

' Get previous heading range
Set rPrevious = Selection.Range ' Start with initial Selection
rPrevious.Collapse ' Move to Start of current initial Selection
' Move backward one character into the previous heading range if present
rPrevious.Move Unit:=wdCharacter, Count:=-1
rPrevious.Select ' Move Selection for bookmark access
Set rPrevious = Selection.Bookmarks("\HeadingLevel").Range

' Check whether previous range overlaps current range
If rCurrent.InRange(rPrevious) Then
' Previous overlaps current, so span entire previous heading range
Set rCurrent = rPrevious
Else
' Not inside previous heading, so extend current heading range back
' to start of previous heading range
rCurrent.SetRange Start:=rPrevious.Start, End:= rCurrent.End
End If

' Select current extended or expanded heading range
rCurrent.Select

EnableScreenUpdates ' Required if disabled above
End Sub

One could readily tweak these macros to create “shrink” versions, but in the interest of “brevity,” these are left to the reader. The differences are not significant.

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.