Trimming sentences faster in Word will obviously expedite the process of editing a manuscript. Toward this end, we create macros to delete to the beginning or ending of a sentence while intuitively handling common sentence punctuation.
Thanks for your interest
This content is part of a paid plan.
Delete part of a sentence
Word includes some standard keyboard shortcuts for manipulating words or paragraphs. Sentences are also a rather fundamental writing unit, but the standard shortcuts for manipulating them are lacking. A few hidden commands exist allowing us to navigate between or select sentences, but that the extent of the list. Creating additional macros to improve sentence editing experience is a worthy endeavor.
To my chagrin, the first version below is probably one of my more used macros, so you should definitely try it. The base macros from the previous versions are short and gloss over a few details, so we enhance them to better account for sentence punctuation as well as make them a tiny bit safer to use.
Create the empty macro
If you're starting from scratch, a previous post covers creating an empty pair of macros:
While VBA is generally easy to read, the single quote on the second line starts a comment. VBA ignores the comment, so we'll start our commands on the blank line below it. We'll focus on the first version and then explain the differences in the second.
Delete to the end of a sentence
We don't want to expand the selection over the entire sentence before deleting it, so we need a way to select only part of a sentence.
Define a working range
Using the Selection (see our brief review) repeatedly within a macro can cause some screen flicker as it changes in real time. While the current macro would most likely work just fine without switching over to using a Range variable (see our brief introduction for more explanation), it's better to be general, and it does not hurt anything.
We use Dim to define a variable in VBA and As Range defines the data type. Technically, we do not have to include this line in standard VBA, but as your macros grow, it's a good practice to be specific. The first time you spend more than a few seconds tracking down a misspelled variable, you'll appreciate it.
What is a Range?
A Range is a specific "object" data type in VBA which is its internal representation of a span of document content. A Range is fundamentally defined by Start and End positions in a document, but it contains additional data (properties) and actions (methods) that allow us to manipulate them in VBA and in the document.
A Range acts like an invisible selection we can modify independent of the VBA Selection (unless any changes overlap). Only one Selection exists per document, but we can define as many document range variables as we need for a macro, and changes to the range are only visible to the user when we specifically change the document content.
When various methods extend or expand a range variable, it acts like Word does when working with the document selection.
Variable names
Generally speaking, use variable names that make sense and don't make you scratch your head later wondering what you were thinking at the time. Our current name rSentence implies it is a range related to a sentence. Roughly, the lowercase r of rSentence implies the range will change as we step through the macro, but VBA does not care as long as we do not try to claim one of its reserved words (like If or Selection).
Short names are nice when they are clear, but it's probably better to veer toward descriptive over short. On the other hand, when I am working with only one range variable in my own macros, I will often use just "r" as a typical working range variable name but probably with an extra descriptive comment somewhere.
Define the range based on the current selection
We want the initial working range to match the current selection or insertion point in the document.
We need to use Set rather than just an equals = sign because a Range is an object in VBA with additional information beyond just a typical value like a number or plain text. While the Selection is like a document range, the Selection is more specific. Thus, we Set the rSentence variable to the Range of the Selection for the correct match.
Picking the correct method
Sometimes VBA will have a few similar methods to carry out a desired step perhaps with some subtle differences between them. Part of writing good Word macros is picking the correct command for the task at hand. Early on, I floundered, but I eventually discovered Word VBA already had some nicely targeted commands I could leverage for my specific needs. It's easy to overlook some of them, but if we'll just dig a little, we can discover them and pounce ... uhhh, sorry, the metaphor is faltering.
We can use the correct method at the correct time. There, that's better.
Extend the range the end of a sentence
We need a way to just select part of a sentence. We could use standard range extension command, but we can do even better for this macro given our specific need to select to the end of a sentence.
Use the EndOf method
We’ll leverage a lesser used move method of a Range called EndOf to setup the delete step later.
Given the preposition ending the method name, it's not surprising the method requires a relevant document unit. Several standard ones are defined in the WdUnits constants table. We assign the desired constant using the Unit option name with a colon equals := symbol. Of course, in this macro, we'll use wdSentence, but the concept could easily be extended to other document unit types (most useful would be a paragraph using wdParagraph).
EndOf is primarily a movement method, but it does allow us to extend the range.
The Extend parameter requires a constant from the short WdMovementType constants table. It defaults to wdMove which collapses the range and just moves the insertion point, but we want wdExtend which extends the range from the End position.
Contrast with a Range extension method
As an aside, we could accomplish something similar using the regular range MoveEnd method instead. Without going into much detail, the command would be:
MoveEnd only moves the End position of the range leaving the Start position unchanged. Thus, the method effectively extends the range.
What's the problem?
The two approaches are not quite the same. The MoveEnd method extends to the next specified Unit regardless of the current End position of the range in the document. The EndOf command understands if it is already at the end of a sentence and will not extend the range any farther if so.
Using MoveEnd would not be wrong per se, but we would need to add an extra step or two to account for the special case at the end of the sentence. Why bother when EndOf already work exactly the way we need?
Allow for punctuation marks
Now that we've extended the range to the end of the sentence, we want to tweak it to account for any punctuation. We previously created a related macro to delete a full sentence while accommodating double quotes or parentheses.
When deleting only part of a sentence, I prefer to delete just the text of a sentence not the end of sentence punctuation. We're looking to trim a valid sentence not delete it entirely. In the process, it's easy to include double quotes to naturally handle dialog as well as a few other less common punctuation marks. For the delete to sentence start version, we mostly only need to exclude quotes and parentheses, so we'll work on the end of sentence case first.
What's the plan?
After we’ve extended the range to span all characters up to the end of the sentence (which coincides with the beginning of the next sentence in Word's parsing scheme), we want to back up the End position of the range. When solving this problem, we should understand that Word considers the current sentence to extend all the way to the beginning of the next sentence including any spaces or other characters separating the sentences.
Why the squirrely back and forth?
Why are we extending the range forward only to move it back over the end of sentence punctuation?
We're taking advantage of Word's internal sentence detection logic. Not that word is perfect with it, but the simpler version of this macro extended the Selection directly to end of sentence punctuation. It seems more direct—just go immediately to the target end of sentence punctuation—but the relevant method wasn't smart enough to discern between the actual end of a sentence or any decimal numbers or abbreviations inside a sentence. The improved version remedies most of those cases, at least for any sentences Word can properly recognize itself.
Character set
The character set is a little long, so it's convenient to store it in a separate variable first, so the upcoming move command is a little easier.
We will need a character set stored as a plain text string. Now what characters?
Adding plain text strings (of characters)
We add strings (of characters) using the + sign mimicking how we add numbers. This is called concatenation when used with strings where we basically just shove them together and call it another string. For example, "a" + "b" = "ab", but everything has to be a string for it to work. The ampersand & also concatenates strings (also like "a" & "b" = "ab"). It is slightly more forgiving when concatenating strings in VBA, but it just looks messier.
Regular end of sentence punctuation
We want to exclude typical end of sentence punctuation: period ".", exclamation point "!", or question mark "?". Other such punctuation are closing parenthesis ")" and a square bracket "]". We need to define this set of characters as a plain text string. Fortunately, we can just put them all together into one string, ".?!)]".
Paragraph markers
Word includes a trailing paragraph marker in a selection for any sentences at the end of a paragraph. In fact, it will include all paragraph markers of any empty paragraphs that follow that last sentence. This default behavior is counterintuitive to me, but at least Word is smart enough to not delete the trailing paragraph marker when if the selection is deleted.
The paragraph marker needs to be removed from the range for our macro, or we won’t be able to trim the other punctuation marks in front of it. The move while method below would immediately stop when it encounters the vbCr character at the end because it was not in the character list. One alternative would be to just move backward one character and omit the paragraph marker. Just including it in the character set also catches the less common case of multiple paragraph markers due to trailing empty paragraphs after the last sentence.
A paragraph marker is defined as vbCr (i.e., a return character) in a miscellaneous Word constants table, so we manually add it to the trimmed characters string.
End of sentence spaces
Another necessary character to remove is a space " ". In a selection, Word automatically includes any trailing spaces, and range extensions behave the same. Like the paragraph marker above, this is more of a practical choice since the move while method would immediately stop at a space if it were not in the character set. Now our TrimCharacters variable looks like:
It's a little subtle, but the space is included as plain text inside the double quotes.
Double and single quotes
We want to naturally handle dialog quotes, but how do we tell VBA to exclude them?
A straight single quote would be easy. Just include it in double quotes like normal "'", but we need to include all the other variations, so we'll just treat it the same as those to avoid any confusion later.
What's the problem?
We use double quotes to define a plain text string of characters like "abc", so how do we specify a double quote character?
VBA has a special notation where we can specify a straight double quote as two double quotes inside another pair of double quotes ““””. Uhhh … and that’s not confusing at all, right? I suppose not once you get used to it, it's okay, but I won't or haven't. It’s clearer (to me) to just refer to quote characters by number.
An older way (not better)
Many languages have a Chr(…) function to translate the numerical representation to the actual text characters we see on the screen. This function works for most regular characters. For example, straight single and double quotes are Chr(39) and Chr(34), respectively, but the "curly" left and right quotes have different values between Windows and Mac systems. We created some double quote functions to make using them easier in VBA. Despite some information to the contrary, they are available when using Chr(…), but it's just not necessary when a more convenient function exists.
A more convenient character function
It's easier to just use the more general ChrW(…) function since the respective numerical values for double quotes are the same between Word for Windows and Mac. They are summarized below:
- Straight double quote " → ChrW(34)
- Left double quote “ → ChrW(8220)
- Right double quote ” → ChrW(8221)
The character numbers are similar for single quotes.
- Straight single quote ' → ChrW(39)
- Left single quote ‘ → ChrW(8216)
- Right single quote ’ → ChrW(8217)
Now defining these characters using VBA looks like:
We first defined the various quotes as variables. This is not strictly required in standard VBA, but it is clearer. When using Dim, we can add more than one variable declaration on a line, but we need to separate them by commas and give a type "As String" (or whatever) for each variable. If we don't give a type, it will be a generic "Variant" type by default, which is usually okay, but it might cause a few small issues such as with function arguments.
What characters are we excluding?
For the end of sentence macro, we want to exclude the right single and double quotes. We'll also exclude straight single and double quotes. If they occur at the end of a sentence, it is much more likely they are on the right side of a quotation, but the character itself lacks any associated direction.
For the delete to sentence start macro, we want the left single and double quotes but also the straight quotes.
Command notation shorthand
We also took advantage of a special colon : command separation character. When it is used, VBA treats each part separately, but don't go crazy with it because overuse can make some dense, hard-to-read steps you may regret later. We used them here mostly because the assignments are so similar, and I didn't want to take up eight lines defining everything.
The best way (not covered here)
Even better, the definitions above still take four lines in a macro, but they won't change (probably ever), so why not define them as global constants. That is, we define them as constants not in each macro where they are needed but for all macros in the module (a group of macros in the VBA editor). We won't need to redefine them each time they are used.
Overall excluded characters
Putting all these characters together, our string of excluded punctuation and other marks when deleting to the end of sentence is:
Excluding the punctuation marks
After all that explanation, we finally remove the punctuation marks from the end of the range by moving its End position backward in the document. The relevant command is the MoveEndWhile method.
MoveEndWhile does what the command name implies. It will keep moving the End while (as long as) it keeps finding any of the characters specified in Cset.
Using the count option as Count := wdBackward is a special case that tells the command to do just that. In general, we could specify a maximum number of characters to move past with a positive number moving forward in the document and a negative number moving backward. The wdBackward constant, from yet another Word constants table, tells the method to move any number of characters necessary as long as it keeps finding characters in the character set Cset.
Reinclude spaces (optional)
When attempting to trim punctuation marks off the end, we do not generally know whether the command will remove anything other than a paragraph markers or spaces. We needed to trim spaces, so the other characters could be detected easily with the same command.
Since Word includes ending spaces in a selection by default, it is consistent with that behavior for us to try to extend our working range back over the trailing spaces. Basically, we just need to extend the End position forward again but only for spaces. We again use the MoveEndWhile method again but just specify a character set of a space " ".
We're moving forward by default, so we omit the Count option.
While this is optional, it is good to be consistent with how Word works because that is what people expect. Even when you're creating macros for yourself, there is an inherent expectation. Unless there is a solid reason, we should probably strive for that consistency.
An exception I personally make is I do not agree with Word including trailing paragraph markers in sentence selections, so I omit them. This is mostly consistent in that Word is often smart about not deleting the paragraph marker even if a sentence selection including one (or more) is deleted.
Delete the partial sentence
We've seen the Delete method before, but for completeness it is simply:
It's almost anticlimactic at times to finish the macro.
Check for a non-empty range before deleting?
What if no sentence range exists by the time we're done trimming our working range?
Admittedly, it would be a nonsense "sentence" because it would have only contained punctuation, but there is a small problem anyhow. Even if the final range spans nothing, the Delete method would still delete something. Mostly likely, it would delete a paragraph marker (the most logical reason for no sentence being present), but it would delete whatever was next to the insertion point.
Hmmm.
We actually only want to delete the selected contents the range contains at least some document content. How can we detect an empty range?
If the Start and End positions are the same, then the range is empty. If not, the range contains something. Since we're working in text documents, the contents are text in all likelihood. Both positions are numbers as literal character positions in the document, so we just need to check whether the two values are equal.
VBA treats this as a True or False (Boolean) condition when it's used within a conditional statement. Our conditional statement would look something like:
Nothing should be deleted if the range is empty, so no Else part is included. Putting it into a more VBA-like statement, we have:
Uhhh, but this condition is for an empty range. We need the opposite. Thus, we use a Not (Boolean operator) to flip the True or False value of the condition. Just place it in front of the condition.
With the Not included, it doesn't read as much like English, but it's still easy enough to understand.
If we don't have any extra steps to do, the conditional statement is simple, and we can actually condense it into one line:
Delete to Start of Sentence
The parallel macro to delete to the start of the sentence is nearly the same, but we need to tweak the steps in a few places.
Extend the range to the beginning of the sentence
The command to extend the Start of the range backward in the document to the beginning of the current sentence is:
It has the same behavior and options as EndOf which was explained above.
Revised trim characters
We don’t have end of sentence punctuation marks, so we can reduce the number of trimmed characters to an open parenthesis or square bracket and the start quotation characters.
We use this revised set of trimmed characters with the MoveStartWhile method:
As with MoveEndWhile method in the other version above, MoveStartWhile keeps moving the Start position of the range forward in the document as long as it keeps finding any of the characters specified in Cset. We need to remove the characters from the range, so we move the Start position forward in the document. Since forward is the default direction, we can omit the Count option.
Capitalize the new sentence
After we’ve deleted to the beginning of a sentence, we would usually manually capitalize the new first word of the sentence. It won't take long to get annoyed with this, so we should make VBA do it for us.
Sentence range is empty after deletion
After the working range is deleted from the document, it still exists but as a collapsed selection at the position of the deleted text. This range is necessarily positioned at the new first word of the trimmed sentence. We need to capitalize that word.
Capitalizing the new first word
This is handled by the Case property of the range.
In common help-page fashion, the descriptions in the list of VBA case constants are terse (details matter when writing macros), but in short, the constants wdTitleSentence or wdTitleWord are probably best suited for our purposes, but they may not exactly like we would expect in all situations.
Fortunately, our current collapsed rSentence range must be at the beginning of the trimmed sentence because we deleted everything at the beginning of the sentence. If the range is collapsed like ours, it applies the Case property to the current word which is the first word of the sentence for us.
The constant wdTitleSentence capitalizes the first word of each sentence in the range. The constant wdTitleWord makes the Case property capitalize the first letter of each word in a range. Since our working range is already collapsed at the first word, both of these would work for us.
More on using the Case property
The Case property almost acts more like method than a property since it can perform actions rather than just change a value like a paragraph style or font formatting. Assigning one of the relevant Case constants can cause it to cycle through all words in the sentence or range. For example, the wdTitleSentence Case value seems to understand when it’s inside a sentence even when we use it without the entire sentence being selected. That is, if we assign wdTitleSentence to the Case property of a word range somewhere inside the sentence, it will not capitalize the word.
The disadvantage of including the Case capitalization is Word does not understand some words like LastCharacter (it will catch some but not all special words). It will convert the uppercase "C" in the middle of the word to a lowercase "c". That occurs much less often, so it's a reasonable compromise.
Final macros
Now we get to the final macros. These macros show how just getting the details right can sometimes make the steps stretch more than we might prefer; however, we just build it one step at a time and enjoy the result.
Deleting to the end of a sentence
We focused on deleting to the end of a sentence. Putting all the steps together, we get:
I assigned my implementation to Option+Delete on a Mac or Alt+Delete on Windows.
Deleting to the start of a sentence
The sibling macro to delete to the start of a sentence is:
For this version, we swapped out EndOf for StartOf at the beginning. In the middle, the trimmed characters and direction are different, and we removed the line that restores the trailing selection spaces. At the end, we added a step to capitalize the new first word of the sentence just after the range is deleted.
I assigned this macro to Control+Option+Delete in Word for Mac or Alt+Backspace on Windows. Unfortunately, some asymmetry exists between my shortcuts, but MacOS does not like to interpret the (forward) Delete key when used with modifier keys. The Mac Delete key is equivalent to the Windows Backspace key.
Use global quote constants
The quotes are defined as global constants for all macros, but if you chose not to use them, just copy the below variable definitions before the line where the trimmed characters are assigned (or the top of the macro also works just as well).
These definitions were explained earlier. These are technically variables not constants, but the trim characters line just uses them as is.
Improvements or extensions
What could we do to make these macros even better? Do any trivial modifications exist that will enhance our editing capabilities?
Undo records
We could encapsulate the two changes into an undo record, so the undo shortcut can be used just once to reverse the changes. This is more of a user-friendly tweak for this macro since there are only two changes at most, but it's a nice touch. However, please read the tutorial carefully since undo records can also cause problems.
What about paragraphs?
Working through the macro, it's easy to change out the sentence manipulation for a paragraph constant. I don't need the paragraph version as often, but it has come in handy a few times. Given that it requires a tiny expenditure of effort, it's an excellent extension of the base macro.
Delete with a sentence Unit?
Could we trivialize the range extension portion of the macro?
If you've used Command+Delete (Mac) or Control+Delete (Windows) to delete words, you might remember it also deletes partial words if the insertion point is already in the middle of a word. Well … the Delete method actually has a Unit parameter also, so couldn't we just use a wdSentence Unit with it and mimic that delete behavior with words?
Uhhh … sounds good at first, but nope.
We can't actually use a sentence unit with the Delete method presumably because the standard delete action does not understand what to do with a sentence like it does with a word. Apparently, that's our job. Fortunately, doing so allows us to tweak the behavior to intuitively handle punctuation for us.