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

Move sentences plus

Word • Macros • Editing
Peter Ronhovde
35
min read

Move a sentence forward or backward in the document with a keystroke?

This is one of those macros you feel like you’d never need. You can always grab the mouse, right? But it’s refreshing when you just get it done and don’t have to click-and-tap around to do it.

Thanks for your interest

This content is part of a paid plan.

Create the empty macro

A previous post covers creating an empty macro like the one shown below. When you’re done, you’ll have something like:

Sub MoveSentenceLeft()
' Move current sentence before the previous

End Sub

The single quote tells VBA the rest of the text on that line is a comment meant for human readers. We start our macro steps on the empty line.

Example macro moving a sentence left in a paragraph

Of course, create a second one to move the current sentence by one sentence right in the document.

Improving the sentence macro

Today, we’re improving a previous macro to make it act more intuitively and robustly.

The explanations overlap some with the simpler version, but this one addresses the problem using ranges and goes into more detail while solving a few other gotchas and special cases along the way.

If you like optimizing your macros so they behave even more naturally and intuitively while editing, read on.

We’ll focus on the move-backward macro and adjust for movement forward at the end. The work required for this one is more than most we’ve done, but I’m guiding you through the process the whole way.

Fair warning

This is the longest macro we’ve completed to date, but rather than infantilize it, we’ll work through it with a nearly full implementation (omitting only handling dialog and parentheses naturally). Then in upcoming articles, we’ll use it as a launching point to show why we can and should make better macros using functions.

Base macro

The original macro included several features and solved some issues we encountered as we created it:

  • Moved a sentence backward in the document with a keystroke (the whole point)
  • Allowed formatted text using the clipboard
  • Omitted the paragraph mark from the sentence selection which prevented splitting the paragraph when we moved the last sentence
  • Adjusted some sentence spacing when necessary

We’ll keep these features in the new and improved version, but we’ll also dig deeper.

What are the new issues to solve?

What do we plan to improve? Some changes are small but still add to the functionality of the macro.

  • Include more intuitive sentence movement specifically when moving sentences between paragraphs
  • Find another way to preserve character formatting other than using the clipboard
  • Correct spacing between sentences and at the end of a paragraph as needed
  • Delete an empty paragraph after moving the only sentence
  • Finish with the moved sentence selected

I like ending the macro with the sentence selected as a visual indicator of the change. It’s just a little UI tweak that helps the user think less about what just happened.

Two other changes incorporate small text corrections due to the moved text, so we don’t have to do anything extra after using the macro. We don’t want to spend extra keystrokes adjusting the text after running the macro.

The goal is for the macro to “just work,” so the user can continue editing without interruption.

Solutions

Two main structural changes for this macro include:

Avoiding the clipboard

Not using the clipboard is more of a preference, but I don’t like my macros mysteriously altering the clipboard in the background. In VBA, it is difficult to save and restore the clipboard contents. If the user had something there, it’s essentially gone assuming they don’t use the multi-item clipboard feature in Word.

How do we get around this?

Using Ranges

We’ll use two ranges and copy the formatted text between them (see earlier brief introduction to ranges). Ranges are a tidier way to solve the problem than using the Selection object. They also allow us to tweak a couple other minor features easily.

Getting started

We start by defining two working ranges for the current sentence in the document with a goal to copy formatted text between them.

Set up the initial sentence Range

We get the user’s starting position in the document using the Selection’s Range property.

' Set the starting range
Set rSentence = Selection.Range

Recall we have to “Set” a range because it holds more information than just a value like a number or text.

Collapse the range

Collapse the sentence range, so we don’t have to worry about any issues with a starting selection.

rSentence.Collapse ' Avoid any starting range issues

Now the range spans no text, so the Start and End positions are the same.

This collapse does not change the actual Selection in the document since we’re working with a range variable.

This isn’t strictly necessary, but if there were a starting selection that spanned sentences, then the macro would move all connected sentences as a group. Not really a bad feature to allow, but it is slightly more than expected. If you like it as an extra feature, then omit this step.

Expand over sentence

Expand the rSentence Range over the current sentence.

rSentence.Expand Unit:=wdSentence

Recall the Expand command automatically expands the current range to include the beginning and end of the given Unit in the document. It uses Unit constants which includes the common ones we consider when creating documents (e.g., words, sentences, paragraphs, etc.). Each constant is preceded with a “wd” to denote it as a word constant.

The Expand command will not grow the Selection beyond the Unit specified. This differs from how it’s used on the keyboard with F8 which grows the selection by successive unit sizes when you press the key again.

We specify command options using an option name, which is Unit here, followed by a := before we give the value.

Remove ending paragraph mark(s)

Word will include the paragraph marker by default for the last sentence of a paragraph—

What? You’re telling me—

Yes, Word selects all empty paragraph marks after an expanded sentence range.

Really?

It’s a little odd to me, but we need to work around it, so let’s remove it from the working Range (not the document).

Simple method

The simple approach, which was used in the original macro, is to just remove a single paragraph mark character. It would be the very last character in the expanded selection using the Characters collection of the range.

rSentence.Characters.Last

The Last character here is a range in the document, but we want the Text for that range, so we add the Text property.

LastCharacter = rSentence.Characters.Last.Text

We don’t have to assign the character to a variable like we’ve done here, but it makes the macro easier to read.

Now we test whether it is a paragraph mark or not. The paragraph mark is a special character, so we refer to it using the constant vbCr.

If LastCharacter = vbCr Then
' Remove just one paragraph mark from the end
rSentence.MoveEnd Count:=-1
End If

The MoveEnd command adjusts the End position of the rSentence range by one character backward since Count:=-1. The default Unit (step size) is by character, so we didn’t need to specify it.

General method

I prefer to remove all paragraph marks from the end of the selection.

' Remove all empty paragraph markers
rSentence.MoveEndWhile Cset:=vbCr, Count:=wdBackward

This is a little nicer because we don’t have to worry about multiple empty paragraphs following our selected sentence. It’s a fringe case, but it only takes a small command change to handle the general case.

We’ve encountered the MoveEndWhile command before. It keeps moving the End position of the Range as long as it keeps finding any characters listed in the Cset option. It is not trying to match the character set as a whole like using Find would do.

Here we’ve given Cset as a special character for the paragraph mark. In general, we would include any characters in double quotes like Cset:=“abc”.

The Count:=wdBackward option does what it implies. It tells the command to move End backward in the document since forward is the default.

One constraint is the End position cannot move earlier than the Start position for the range, so Word will automatically set them equal if End gets to the Start position.

Interestingly, we don’t even need an If statement like we used above because the “while” part of the MoveEndWhile command handles the condition for us.

Create a second range

Now that we have our sentence selected, we need a copy of it which we’ll move to the target location. The above range marks the sentence we want to move, so we begin there.

Gotcha with copying ranges

Unfortunately, we can’t just set the range in the obvious manner.

' Both ranges would literally be the same range
Set rCopy = rSentence ' Does not work as expected

If you try a direct assignment like this, the ranges will literally be the same thing. You change one, and the other one also changes. It’s a little odd to me as a default assignment behavior, but that’s how Range assignments work.

How to properly duplicate ranges

We need to be able to change the copied range independent of the first one. To accomplish this, we set rCopy to a duplicate of the sentence Range

Set rCopy = rSentence.Duplicate

While the two Ranges currently span the same document text, they are nevertheless independent Ranges. Changes to one will not affect the other unless we insert or remove text.

Move to previous sentence location

Now that we have a duplicate range rCopy, we can move it to the previous sentence using the Move command

rCopy.Move Unit:=wdSentence, Count:=-2

Obviously, we need to move with a wdSentence Unit, but we use the Move command with a Count:=-2 because negative is backward in the document. The 2 is because the first sentence step just moves to the beginning of the current sentence.

After the move, rCopy is a collapsed range at the target position one sentence earlier in the document.

Copy sentence

We’re avoiding Cut and Paste here since they use the clipboard. We might be tempted just set the Text properties equal

' Works only for plain text
rCopy.Text = rSentence.Text ' Nope ...

This works if the copied text is plain text with no formatting. This is probably fine for most novels since novels tend not to use a lot of formatted text, but even they occasionally use italics.

We want the macro to be general purpose with no extra work, so we instead with use the Range’s FormattedText property.

rCopy.FormattedText = rSentence.FormattedText

In general, this command would also include any paragraph formatting if the paragraph marker is included in the Range, but we excluded that earlier, so our command will only keep the character formatting, if applicable.

Include ending spaces

Word usually includes trailing spaces after an automatic selection or extension, so we’ll mimic that behavior for consistency

rCopy.MoveEndWhile Cset:=" "

We don’t specify a direction since forward is the default.

This step also makes checking for a missing space between sentences (coming up later) a little easier since we know we’ve included any that are present at the end of the copied sentence.

Delete the original sentence

The original sentence range is still unchanged by the macro, so we can delete it now.

rSentence.Delete

The main content of the macro is done, but we’ll include several improvements and corrections below.

Base Macro

The new base macro using ranges as follows. We’ll extend this with new features and corrections coming up.

Sub MoveSentenceBackwardBaseVersion()
' Move the current sentence one sentence backward
' Does not use the clipboard

' Set the starting Selection Range
Set rSentence = Selection.Range

' Select the current sentence
rSentence.Collapse ' Avoid starting selection
rSentence.Expand Unit:=wdSentence
' Remove any paragraph marks from selection
rSentence.MoveEndWhile Cset:=vbCr, Count:=wdBackward

' Move the copy location back one sentence
' Set target range to same as original sentence
Set rCopy = rSentence.Duplicate
rCopy.Move Unit:=wdSentence, Count:=-2

' Move formatted text to new location
rCopy.FormattedText = rSentence.FormattedText
' Extend copied text range over ending spaces
rCopy.MoveEndWhile Cset:=" "

rSentence.Delete ' Delete original sentence
End Sub

I do not include a forward version since this is intended as an intermediate macro.

Is that all?

Often you can start with a working macro but then realize you want it to do X or Y or handle some other annoying special case automatically.

Forewarning

Sometimes accounting for special cases or fixing annoying gotchas can be harder than creating the core macro. This move sentence macro is one of those.

It’s one of the longer editing macros I’ve created, but I insist my macros work intuitively with little extra action on my part. If you want to follow along, just be aware it’s a slog in places.

Fixing Gotchas

Before we start tweaking the movement behavior of the macro, we need to catch and correct special cases to match what we expect in normal usage.

Delete extra spaces at end of source paragraph

Suppose we move the last sentence of the source paragraph. After the original text is deleted, the previous sentence will probably leave a space at the end of the paragraph which is annoying.

Detecting the end of the paragraph

To detect whether this is the case, we use the rSentence range. As of the end of the core macro, the range is currently empty at the location of the deleted original sentence. We just need to test the next character to validate whether it’s at the end of the paragraph or not.

Unfortunately, when there is no text spanned by a range, the “next” character is actually found using the “First” character of the empty Range. This is a little confusing since it’s not actually spanning any text. I wish it wasn’t like that for consistency, but that’s why you have me here.

FirstCharacter = rSentence.Characters.First.Text

Given that oddity, our comparison needs to be whether the First character is a paragraph mark.

FirstCharacter = vbCr

Delete the extra spaces

If we have a paragraph mark on the right, then select all spaces to the left and delete them. We’ve used If conditions before, but briefly they look like

If SomeCondition Then
' Do some extra steps ...
End If

The extra steps inside are only done if SomeCondition is true. For our condition, we use

If FirstCharacter = vbCr Then
' Select and delete all ending paragraph spaces
rSentence.MoveStartWhile Cset:=" ", Count:=wdBackward
rSentence.Delete
End If

The MoveStartWhile command works just like MoveEndWhile except it changes the Start position of the range.

Usually there is only one space to the left, but we might as well handle the general case using the MoveStartWhile command to extend over all spaces prior to the rSentence range.

We then delete any spaces found using the Delete method.

But we have a problem …

Unfortunately, the above will still delete a character if it detects a paragraph mark regardless of whether any spaces were found.

In addition to checking whether the character after the empty rSentence range is a paragraph mark, we need to make sure at least one space exists before deleting anything.

Checking two conditions

How do we check two conditions?

If we want both conditions to be true, we use an And between them.

If ConditionOne And ConditionTwo Then
' Do something extra ...
End If

With And between them, both ConditionOne and ConditionTwo must be true before the extra steps inside are carried out.

Detecting any preceding spaces

We add a second check for whether a preceding space exists before selecting and deleting them. We need to get the preceding character for the check.

PreviousCharacter = rSentence.Previous(Unit:=wdCharacter).Text

The Previous method returns the range of the previous Unit specified which is a character here. We didn’t add a Count:=1 option because that is the default value.

We included the Unit in parentheses because we needed to access the Text property of the previous character range.

We only delete the space if the paragraph mark and at least one prior space both exist.

If PreviousCharacter = " " And FirstCharacter = vbCr Then
' Select and delete all ending paragraph spaces
rSentence.MoveStartWhile Cset:=" ", Count:=wdBackward
rSentence.Delete
End If

Overcoming Word’s “helpful” features

Sometimes Word re-inserts deleted spaces. This can be convenient in everyday use, so you don’t have to manually insert a space when you paste text, for example, but it can be quite annoying when creating macros … do you hear it coming?

Since this is the end of the paragraph, we should be able to just delete the excess space(s) and be done, but in my testing, Word indeed reinserts a space right at the end of the paragraph.

Arghhh.

We need to add another step inside the end-of-paragraph checks to detect a reinserted trailing space and then delete it despite Word’s attempt to help us.

We redefine the first character for the empty sentence range, but this time we’re checking whether it’s a space or not, the one Word reinserted automatically.

FirstCharacter = rSentence.Characters.First.Text
If FirstCharacter = " " Then
' Delete straggling space Word reinserts
rSentence.Delete
End If

A simple Delete command deletes the single space Word reinserted to the right of the empty range.

I understand the point of Word adjusting the spacing for us since those features are implemented for real-time use of Word, but it does make our job harder at times when creating macros.

Wouldn’t it be simpler if …

Why not just delete the space every time? You might just add the extra delete either way.

' Why not just do this ...?
If PreviousCharacter = " " And FirstCharacter = vbCr Then
' Select and delete all ending paragraph spaces
rSentence.MoveStartWhile Cset:=" ", Count:=wdBackward
rSentence.Delete
rSentence.Delete ' delete reinserted space?
End If

After all, you “know” it’s there, right?

Uhhh … that generally scares me. Do we really know it’s there? Can I guarantee that Word will always insert it?

Maybe … but I don’t know.

I prefer to make sure the space is present first. Otherwise, I might end up deleting some other character. Probably not a big deal since it’s just one character, but I feel better about my macro if I check first.

Finally delete the spaces

Here is the result for deleting any excess end of the paragraph spaces.

' Delete any end of paragraph spaces at source location
PreviousCharacter = rSentence.Previous(Unit:=wdCharacter).Text
FirstCharacter = rSentence.Characters.First.Text
If PreviousCharacter = " " And FirstCharacter = vbCr Then
' Select and delete all ending paragraph spaces
rSentence.MoveStartWhile Cset:=" ", Count:=wdBackward
rSentence.Delete

' Delete straggling space Word reinserts
FirstCharacter = rSentence.Characters.First.Text
If FirstCharacter = " " Then
rSentence.Delete
End If
End If

Notice how this simple task of deleting excess spaces grew into a chunk of macro steps.

Reinsert missing spaces

When moving the last sentence of a paragraph, the copied sentence at the new location will not include an ending space because the original sentence ends with a paragraph mark instead. We omitted the paragraph mark on purpose from the initial sentence range, but we need to add a space after we move it earlier in the paragraph.

Now we’re working with the rCopy range since it is at the target location for the copied text.

Getting the last character

As a reminder, we’re doing this check right after extending the end of the rCopy range over any trailing spaces.

' Extend range over all ending spaces
rCopy.MoveEndWhile Cset:=" "

So, the range should include any spaces if they are present.

We need to check whether a space exists before adding one, so we get the last character of the range.

LastCharacter = rCopy.Characters.Last.Text

Inserting the space

Check whether the last character is already a space before adding one.

' Ensure a space separates sentences inside a paragraph
If LastCharacter <> " " Then
' No ending space found, so add one ...
rCopy.InsertAfter Text:=" "
End If

Remember <> is the “not equal to” symbol (literally less than or greater than), so we’re checking whether the LastCharacter is not a space before adding one.

The InsertAfter command does what it says. It adds the given Text option text within the double quotes to the end of the range, extending the Range in the process.

InsertAfter only adds the plain text given, but we can also incorporate special characters, if necessary. The inserted text will inherit any paragraph formatting or character formatting of the character immediately prior to the insert location if no spaces separate the text.

But not at the end of a sentence

Since we’re about to allow moving a sentence to the end of a paragraph (see more intuitive behavior next), let’s fix a small gotcha immediately.

We just added a space between two sentences above, but don’t want to add a space when it’s the last sentence of the new paragraph, so we need to exclude that case.

Ughh … we need the next character now.

NextCharacter = rCopy.Next(Unit:=wdCharacter).Text

The Next method works like the Previous method except it gives the next character after a range. There is a gotcha if the range is empty which was mentioned earlier, but here we know the range spans some text since we just copied it to the target location.

We don’t add a space if we’re already at the end of a paragraph, so we don’t want the NextCharacter to be a paragraph mark. The condition is the next character is not a paragraph mark.

NextCharacter <> vbCr

So, we’re making two comparisons.

NextCharacter = rCopy.Next(Unit:=wdCharacter).Text
If LastCharacter <> " " And NextCharacter <> vbCr Then
' No ending space found, so add one ...
End If

When using And, both conditions must be true before we add the extra space inside the If statement.

Spacing sentences properly

The resulting chunk of steps to ensure proper sentence spacing is

' Extend range over all ending spaces
rCopy.MoveEndWhile Cset:=" "
' Ensure a space separates sentences inside a paragraph
LastCharacter = rCopy.Characters.Last.Text
NextCharacter = rCopy.Next(Unit:=wdCharacter).Text
If LastCharacter <> " " And NextCharacter <> vbCr Then
' No ending space found, so add one
rCopy.InsertAfter Text:=" "
End If

To get everything done correctly, the steps even for a small task seem to grow more than we might initially expect. 

More intuitive behavior

Now we adapt the base macro to make it act more intuitively.

Moving the first sentence of a paragraph

The original macro will move the first sentence of a paragraph two sentences deep into the previous paragraph. This behavior is a little jarring in my opinion.

Instead, it should just become the last sentence of the previous paragraph. I think that makes the movement “flow” better.

Detecting the first sentence of a paragraph

To check whether we are at the first sentence of a paragraph, we need the previous character using the Previous method.

PreviousCharacter = rCopy.Previous(Unit:=wdCharacter).Text

For a general sentence, the previous character can be any text, but if it is the first sentence of a paragraph, then the previous character will be the paragraph mark of the prior paragraph (unless it is the first sentence of the entire document which we’ll ignore).

If PreviousCharacter = vbCr Then
' Original sentence begins a paragraph ...
End If

Moving to end of previous paragraph

Rather than move two sentences backward in the document, which is the standard case, we just need to pick a different target location for the copied sentence.

In this case, we only want to move back to the end of the previous paragraph which is literally one character backward in the document. We use the rCopy range since we’re using it to mark the target location for the moved sentence.

rCopy.Collapse ' just in case
rCopy.Move Unit:=wdCharacter, Count:=-1

We move by a Unit of character, and Count is negative 1 because we’re moving backward in the document.

The Collapse command isn’t strictly necessary since the move is made from the beginning of the range, but it makes the steps clearer.

Selecting the correct position

In the base macro, we always moved back two sentences, but now we have a different possible target location. We need to pick between this new end of paragraph position or the regular move backward by 2 sentences in the document.

We’re already detecting the beginning of the starting paragraph, so use the same condition to determine the target location.

If Else conditions …

We’ve used If statements several times, but here we have a regular case and a special case. We want to include both, so we use an If Else statement.

If SomeCondition Then
' Do condition steps ...
Else
' Otherwise do these steps ...
End If

The first part is when we’re moving the first sentence of a paragraph, so we compare the previous character to a paragraph mark, and the second is for all other starting sentence locations.

If PreviousCharacter = vbCr Then
' Move to end of previous paragraph
rCopy.Collapse
rCopy.Move Unit:=wdCharacter, Count:=-1

' ... but we have a small problem ...
Else
' Move back one sentence in the document
rCopy.Move Unit:=wdCharacter, Count:=-2
End If

Correct missing sentence space

The end of the previous paragraph probably doesn’t have a space to properly separate the sentences. Let’s fix that immediately rather than add a whole chunk of steps later. The rCopy Range is already at the desired location, so just add the space now.

' Insert space for sentence at end of previous paragraph
rCopy.InsertBefore Text:=" "

If you really wanted, you could add a check for a space first. I would in my own version, but this handles the most probable case.

The InsertBefore command acts just like InsertAfter except it inserts the given text before the range as its name suggests.

Don’t overwrite the space

Unfortunately, the various Insert methods automatically extend the range over the inserted characters, so the space will be part of the rCopy range. This means our new space would be overwritten when we copy the formatted text only a few steps later. We can avoid this by just collapsing the rCopy range.

' Collapse again so we don't overwrite the space
rCopy.Collapse Direction:=wdCollapseEnd

We collapse toward the end using the Direction option, so the space is before the upcoming copied text.

More intuitive target sentence position

Accumulating the steps to handle proper target location for the copied sentence text, we have

' Move copy location back one sentence
' Check for beginning of starting paragraph
PreviousCharacter = rCopy.Previous(Unit:=wdCharacter).Text
If PreviousCharacter = vbCr Then
' Move to end of previous paragraph
rCopy.Collapse
rCopy.Move Unit:=wdCharacter, Count:=-1

' Insert space for sentence at end of previous paragraph
rCopy.InsertBefore Text:=" "
' Collapse again so we don't overwrite the space
rCopy.Collapse Direction:=wdCollapseEnd
Else
' Move back one sentence in the document
rCopy.Move Unit:=wdCharacter, Count:=-2
End If

Now our macro will handle the regular case, but it will also handle backward moves more naturally for the first sentence of a paragraph.

Delete extra spaces at end of target paragraph

Unfortunately, we just introduced a small gotcha regarding proper spacing between sentences.

Huh? How?

Now we allow a sentence to move to the end of the previous paragraph. The moved sentence probably has a trailing space that moves with it, so we need to delete it like we did for the source paragraph earlier.

Ahhhhh! Get me out of here!

This is a good example of how changes to one part of a macro affect other parts. What do we do besides pull our hair out and give up on macros entirely (that’s not very productive)?

Reuse prior solution

Fortunately, we can reuse the previous steps where we deleted extra spaces at the end of the source paragraph. We’ll just need to change the range we used to correspond to the new copied sentence (target) range. We don’t want to disturb the overall copied text range, rCopy, since it holds our desired sentence content at the new location, but we do want to delete any excess spaces at the end if they exist.

Create sentence copy copy range

We just need to store a copy of the rCopy range in a new variable.

Set rEnd = rCopy.Duplicate

But we want to check for spaces at the end of the range, so we collapse it toward the End position using the Direction option.

rEnd.Collapse Direction:=wdCollapseEnd

Modified steps with copy copy range

Now we have an empty range just at the end of the copied text, so we can check for excess spaces and delete them, if needed.

' Set a duplicate of copied sentence range
Set rEnd = rCopy.Duplicate
' Work with end of copied sentence range
rEnd.Collapse Direction:=wdCollapseEnd

' Delete any end of paragraph spaces at target location
PreviousCharacter = rEnd.Previous(Unit:=wdCharacter).Text
' Use first character of range to detect end of paragraph
FirstCharacter = rEnd.Characters.First.Text
If PreviousCharacter = " " And FirstCharacter = vbCr Then
' Select and delete all ending paragraph spaces
rEnd.MoveStartWhile Cset:=" ", Count:=wdBackward
rEnd.Delete

' Delete straggling space Word reinserts sometimes
FirstCharacter = rEnd.Characters.First.Text
If FirstCharacter = " " Then
rEnd.Delete
End If
End If

This is a why functions exist

This is a big reason why functions exist. We literally just copied the same steps over and made a trivial change to a different range. Look for the upcoming article breaking this macro down into smaller bite-sized chunks.

Delete empty paragraph?

What if we moved the only sentence of our initial paragraph leaving it empty?

If you don’t care, then there’s nothing to do here, but it’s a little unsightly in my opinion to leave a blank line after the move, so let’s delete it.

We use the rSentence range to check whether the paragraph is empty since that range is still at the original deleted sentence location.

One way to check for an empty paragraph is to check for consecutive paragraph marks with no text in between them (since we’ve already deleted all text and any spaces). Given the range location, we’ll get the previous and next characters as we’ve done previously.

PreviousCharacter = rSentence.Previous(Unit:=wdCharacter).Text
FirstCharacter = rSentence.Characters.First.Text

As a reminder, we need the First character of the range since we are sure (given our previous steps) the rSentence range is empty. If rSentence had contained any text, we would need the next character using the Next method.

Check whether both characters are paragraph marks.

If PreviousCharacter = vbCr And FirstCharacter = vbCr Then
' Delete the empty paragraph
rSentence.Delete
End If

A simple Delete command removes the lone paragraph mark.

Select moved sentence

This is another personal preference, but I like to end with the moved sentence selected as a visual clue that something significant changed in the document. We just select the copied text range using the Select method.

rCopy.Select ' Select moved sentence

Plus, if the user wants, they can continue moving the same sentence.

Revised Macros

Let’s put these changes together into a revised, more intuitive macro.

Before you scan below

The special cases make them ungainly, but their complexity gives us a good reason to introduce functions and subroutines in upcoming articles. For example, there are several natural chunks of steps that can be neatly separated out and would probably apply to other macros later.

These macros are nevertheless fully functional as is, but the intention is to use them as springboards to create better macros in the future. However, as a school of hard-knocks example on the utility of functions, these macros serve that secondary purpose well.

Moving sentence backward

Merging the above changes together (order matters), our current macro to move a sentence backward in the document is

Sub MoveSentenceBackwardLongVersion()
' Move the current sentence one sentence backward
' Does not use the clipboard

' Set the starting Selection Range
Set rSentence = Selection.Range

' Select the current sentence
rSentence.Collapse ' Avoid starting selection
rSentence.Expand Unit:=wdSentence
' Remove any paragraph marks from sentence selection
rSentence.MoveEndWhile Cset:=vbCr, Count:=wdBackward

' Set working range to same as original sentence
Set rCopy = rSentence.Duplicate

' Move the copy location back one sentence
' Check for beginning of current paragraph
PreviousCharacter = rCopy.Previous(Unit:=wdCharacter).Text
If PreviousCharacter = vbCr Then
' Move to end of previous paragraph
rCopy.Collapse
' Move one character past previous paragraph mark
rCopy.Move Unit:=wdCharacter, Count:=-1

' Insert space for sentence at end of paragraph
' Assumes no ending space at end of paragraph
rCopy.InsertBefore Text:=" "
' Collapse again so we don't overwrite the space when text is copied
rCopy.Collapse Direction:=wdCollapseEnd
Else
' Regular case with any other sentence
' Move back one sentence in the document
rCopy.Move Unit:=wdSentence, Count:=-2
End If

' Move formatted text to new location
rCopy.FormattedText = rSentence.FormattedText
rSentence.Delete ' Delete original sentence

' Extend range over ending spaces
rCopy.MoveEndWhile Cset:=" "
' Ensure a space separates sentences inside a paragraph
LastCharacter = rCopy.Characters.Last.Text
' NextCharacter checks for end of paragraph exception
NextCharacter = rCopy.Next(Unit:=wdCharacter).Text
If LastCharacter <> " " And NextCharacter <> vbCr Then
' No ending space found, so add one
rCopy.InsertAfter Text:=" "
End If

' Select and delete all ending paragraph spaces at source sentence
' rSentence is already collapsed from Delete above
PreviousCharacter = rSentence.Previous(Unit:=wdCharacter).Text
' Use first character to detect end of paragraph
FirstCharacter = rSentence.Characters.First.Text
If PreviousCharacter = " " And FirstCharacter = vbCr Then
rSentence.MoveStartWhile Cset:=" ", Count:=wdBackward
' Delete preceding spaces
rSentence.Delete

' Delete straggling space Word reinserts sometimes
FirstCharacter = rSentence.Characters.First.Text
If FirstCharacter = " " Then
rSentence.Delete
End If
End If

' Delete empty paragraph if we moved only sentence
' Checked in addition to deleting spaces above
PreviousCharacter = rSentence.Previous(Unit:=wdCharacter).Text
FirstCharacter = rSentence.Characters.First.Text
If PreviousCharacter = vbCr And FirstCharacter = vbCr Then
' Delete empty paragraph
rSentence.Delete
End If

' Select and delete any end of paragraph spaces at target location
' Create temporary range to delete spaces at end of copied text range since we do not want to change rCopy at the moved position
' Set a duplicate of copied sentence range
Set rEnd = rCopy.Duplicate
' Work with end of copied sentence range
rEnd.Collapse Direction:=wdCollapseEnd
PreviousCharacter = rEnd.Previous(Unit:=wdCharacter).Text
' Use first character to detect end of paragraph
FirstCharacter = rEnd.Characters.First.Text
' Need the space check also or it will merge paragraphs for last sentence
If PreviousCharacter = " " And FirstCharacter = vbCr Then
rEnd.MoveStartWhile Cset:=" ", Count:=wdBackward
' Delete preceding spaces
rEnd.Delete

' Delete straggling space Word reinserts sometimes
FirstCharacter = rEnd.Characters.First.Text
If FirstCharacter = " " Then
rEnd.Delete
End If
End If

' Finish with moved sentence selected
rCopy.Select
End Sub

Moving sentence forward

The move sentence forward version of the macro is also long. We need to adjust the corresponding steps (performed off screen) for moving forward in the document, so the details are a little different.

Sub MoveSentenceForwardLongVersion()
' Move the current sentence one sentence forward
' Does not use the clipboard

' Set the starting Selection Range
Set rSentence = Selection.Range

' Select the current sentence
rSentence.Collapse ' Avoid starting selection
rSentence.Expand Unit:=wdSentence
' Remove any paragraph marks from sentence selection
rSentence.MoveEndWhile Cset:=vbCr, Count:=wdBackward

' Set working range to same as original sentence
Set rCopy = rSentence.Duplicate

' Move the copy location forward one sentence
' Check for end of current paragraph
NextCharacter = rCopy.Next(Unit:=wdCharacter).Text
If NextCharacter = vbCr Then
' Move to beginning of next paragraph
rCopy.Collapse Direction:=wdCollapseEnd
rCopy.Move Unit:=wdCharacter
Else
' Standard case to move forward one sentence
rCopy.Move Unit:=wdSentence, Count:=2

' For second to last sentence of a paragraph, Word automatically moves past the paragraph mark to next paragraph, so catch beginning of paragraph (wrong) target position and reposition to end of original paragraph
PreviousCharacter = rCopy.Previous(Unit:=wdCharacter).Text
If PreviousCharacter = vbCr Then
rCopy.Move Unit:=wdCharacter, Count:=-1
End If
End If

' Move formatted text to new location
rCopy.FormattedText = rSentence.FormattedText
rSentence.Delete ' Delete original sentence

' Extend selection over ending spaces
rCopy.MoveEndWhile Cset:=" "
' Ensure a space separates it from next sentence inside a paragraph
LastCharacter = rCopy.Characters.Last.Text
' NextCharacter checks for end of paragraph exception
NextCharacter = rCopy.Next(Unit:=wdCharacter).Text
If LastCharacter <> " " And NextCharacter <> vbCr And Then
' No ending space found, so add one
rCopy.InsertAfter Text:=" "
End If

' Ensure a space separates it from prior sentence inside a paragraph
' First character of rCopy cannot be a space since it is copied from original sentence selection
' Previous character checks for beginning of paragraph or space exceptions
PreviousCharacter = rCopy.Previous(Unit:=wdCharacter).Text
If PreviousCharacter <> " " And PreviousCharacter <> vbCr Then
' No prior space found, so add one
rCopy.InsertBefore Text:=" "
' Remove inserted space from range to mimic sentence selections
rCopy.MoveStart Unit:=wdCharacter
End If

' Select and delete ending paragraph spaces at source sentence
' rSentence is already collapsed from Delete above
' First character of rSentence detects end of paragraph
FirstCharacter = rSentence.Characters.First.Text
PreviousCharacter = rSentence.Previous(Unit:=wdCharacter).Text
If PreviousCharacter = " " And FirstCharacter = vbCr Then
rSentence.MoveStartWhile Cset:=" ", Count:=wdBackward
' Delete preceding spaces
rSentence.Delete

' Delete straggling space Word reinserts sometimes
FirstCharacter = rSentence.Characters.First.Text
If FirstCharacter = " " Then
rSentence.Delete
End If
End If

' Delete empty paragraph if we moved only sentence
' Checked in addition to deleting spaces above
PreviousCharacter = rSentence.Previous(Unit:=wdCharacter).Text
FirstCharacter = rSentence.Characters.First.Text
If PreviousCharacter = vbCr And FirstCharacter = vbCr Then
' Delete empty paragraph
rSentence.Delete
End If

' Select and delete ending paragraph spaces at target sentence
' Create a temporary working range to delete spaces at the end of copied text range since we do not want to change rCopy at the moved position
' Set a duplicate of copied sentence range
Set rEnd = rCopy.Duplicate
' Work with end of copied sentence range
rEnd.Collapse Direction:=wdCollapseEnd
PreviousCharacter = rEnd.Previous(Unit:=wdCharacter).Text
' Use first character to detect end of paragraph
FirstCharacter = rEnd.Characters.First.Text
If PreviousCharacter = " " And FirstCharacter = vbCr Then
rEnd.MoveStartWhile Cset:=" ", Count:=wdBackward
' Delete preceding spaces if any
rEnd.Delete

' Delete straggling space Word reinserts sometimes
FirstCharacter = rEnd.Characters.First.Text
If FirstCharacter = " " Then
rEnd.Delete
End If
End If

' Finish with moved sentence selected
rCopy.Select
End Sub

Too much

Ugh … they’re too long, and we haven’t even added in handling dialog and parentheses naturally.

You might notice some very similar chunks of steps within and between the two macros. Eliminating repetitive code is a main use of functions, but we’re already super long in this article, so that will be a lesson for another day.

Keyboard shortcuts

I assigned my versions of these macros to Option+Shift+Left or Right arrows in Word for Mac or Alt+Shift+Left or Right arrows on Windows.

These key combinations aren’t perfect in my opinion since I’d prefer those key combinations extend the selection by sentences instead (akin to Command+Shift+Left or Right arrows for words), but it’s the best solution so far for me since I move sentences more than I need to simply select them.

Differences ...

Some details or gotchas don’t pop up in both macros. For example, we don’t have to worry about the second-to-last sentence target position correction. It’s a subtle distinction, so when creating your own macros with slight variants like these, don’t assume everything is the same.

Comments

Notice the regular comments throughout both macros. They would be even more difficult to follow without the additional explanation.

You’ll thank yourself for including comments if you ever go back to the macro later. I’ve forgotten what I was thinking or how I was solving a problem at the time, and the comments helped me remember and saved me some time fixing or enhancing the macro.

I will generally explain notable individual steps, chunks of steps, what variables mean, or just what I was thinking at the time if it might matter later. I’ll also note any specific issues I was having such as why something didn’t work. If there is a strange step, I’ll add a note about why it was included.

Other improvements for later

With so many changes in sequence, we should probably add an Undo record and maybe even suppress screen updates during the macro since we repeatedly change the document on screen, but those are lessons for another day.

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.