TextEdit script - Find and replace all with clipboard content

TextEdit script - Find and replace all with clipboard content

Often I need to insert a particular phrase (such as an address) 15-20 times in one Text Edit document, and pasting it in continuously interrupts the flow of my typing. This script allows one to type a phrase once, then a short keyword for each following instance. (I have used “zzz” for the keyword as you’re probably not going to run into any unwanted replacements with that. Of course, you can use whatever you will.)

Here’s how it works:
You highlight the portion you want to be the replacement text in a TextEdit document, then run the script.
Script will copy the highlighted portion to clipboard.
Script opens Find and Replace, enters keyword into “find” and clipboard content into “replace with.”
Script clicks “Replace All” button.
Script then closes F&R window.

My Applescripting experience is limited; I’m sure it could be streamlined, but the syntax escapes me! Many thanks to all the generous scripters here, from which I’ve found inspiration and borrowed snippets.


activate application "TextEdit"
tell application "System Events"
	tell process "TextEdit"
		keystroke "c" using {command down}
	end tell
end tell

tell application "System Events"
	tell process "TextEdit"
		keystroke "f" using {command down}
	end tell
end tell

tell application "System Events"
	tell process "textedit"
		keystroke "zzz"
		keystroke tab
	end tell
	
	tell process "textedit"
		keystroke "v" using {command down}
	end tell
	
	tell process "TextEdit"
		tell window "Find"
			click button "Replace All"
			tell window "Find"
				keystroke "w" using {command down}
			end tell
		end tell
	end tell
end tell

Hi Sue,

Another way might be like the script below.
Which just replaces the whole keywords with the selected text, rather than doing a all those key strokes.

The only issue with this method, is you can not undo. So at beginning the script copies all of the text and at the end of the script puts it into the clipboard. Incase you are not happy with the results.
You could actually write out the old version to a temp file first, which would be better…

activate application "TextEdit"
tell application "System Events"
	tell process "TextEdit"
		keystroke "c" using {command down}
	end tell
end tell
tell application "TextEdit"
	set doc1 to document 1
	set theOldText to text of doc1
	display dialog "Enter Word to replace" default answer "zzz" buttons {"Cancel", "OK"} default button 1
	copy the result as list to {keyword, button_pressed}
	if button_pressed is "OK" then
		
		set thepaste to the clipboard
		set words of doc1 whose it is keyword to thepaste
	end if
	set the clipboard to theOldText
end tell

Thanks Mark, really very kind of you! It’s a vast improvement and will be very helpful to me.

I am busy to replace keywords with their real value using a dialog.
So, my code looks similar but I walks into a trap when I use << >>.
So I did a search here at MScripter and found this topic.
Old topic but almost same question. Is this solvable?
btw, it is for in text edit but also in mail.

tell application "TextEdit"
	activate
	set doc to document 1
	set keyword to "<<NAME>>" -- as string
	--set keyword to "NAME" --<-- works but let the <<>> there
	display dialog "Enter a Name to replace <<NAME>>" default answer "<<NAME>>" buttons {"Cancel", "OK"} default button 2
	copy the result as list to {button_res, value_res}
	if button_res is "OK" then
		set keyword_repl to value_res
		set words of doc whose it is keyword to keyword_repl
	end if
end tell

btw, If I use


... 
	if button_res is "OK" then
		set keyword_repl to value_res as Unicode text

		set _body to the text of doc
		set _body to my ReplaceText(_body, keyword, keyword_repl)
		set the text of doc to _body
	end if
end tell

(* found here at MS to test with *)
to ReplaceText(|SourceText|, |SearchText|, |ReplacemetText|)
	set {DTID, AppleScript's text item delimiters} to {AppleScript's text item delimiters, |SearchText|}
	try
		set {TextItems, AppleScript's text item delimiters} to {every text item of |SourceText|, |ReplacemetText|}
		set {|SourceText|, AppleScript's text item delimiters} to {TextItems as text, DTID}
	on error errorMessage number errorNumber -- oops
		set AppleScript's text item delimiters to DTID
		error errorMessage number errorNumber -- pass it on
	end try
	return |SourceText|
end ReplaceText

then I loose the formatting.

You’re getting the text from TextEdit as a whole but you’ll also get the text as plain text not an marked up text. Then change the text and set the contents back as plain text. There is no way TextEdit, or any kind of text editor, that can change magically plain text into text with markup.

When working inside text editor you’re working with attributes strings (attribute runs) which is an array of strings but each attribute run contains also a font, font size and color. So to maintain overlap in runs for search and replace it’s just easier to work with text, character, word or paragraph inside the document so the search and replace is done correctly.

Hoi en bedankt.

Klopt. eh. Right, did want to show that I did try several things.
I know when you copy text from textedit you got several clipboards type’s back and depending on the ‘paste’ ( target ) you get the plain or rtf info.

In the case of formatted text e.t.c I should use

set words of doc whose it is keyword to keyword_repl

, right?
Like in my first sample?
Why isn’t that working when there are <<>> around the word?
With other words it is working.
What do I miss?

Aha, ik begrijp het probleem nu (Now I see the problem).

The problem is that “«class»” is not a word, when you do:

word of "«class»" 

AppleScript will return just “class”. This means that you will have an invalid objects specifier. So I thought you can work on character (text) level but unfortunately it seems that TextEdit doesn’t support this. So I came up with this, it looks cumbersome but I’ll explain below why I have written it down like this:

set findString to "«class»"
set replaceString to "«replace»"

tell application "TextEdit"
	tell document 1
		set offsets to my getOffsets(text of it, findString)
		set offsets to reverse of offsets
		repeat with o in offsets
			delete characters (o + 1) thru (o + (count of findString) - 1) of it
			set character o of it to replaceString
		end repeat
	end tell
end tell

on getOffsets(theString, subString)
	set offsets to {}
	set totalLen to 0
	set sLen to count of subString
	set {oldTID, AppleScript's text item delimiters} to {AppleScript's text item delimiters, subString}
	set textItems to every text item of theString
	set AppleScript's text item delimiters to oldTID
	if (count of textItems) = 1 then return offsets
	
	repeat with i from 1 to (count textItems) - 1
		set iLen to count of item i of textItems
		set end of offsets to totalLen + iLen + 1
		set totalLen to totalLen + iLen + sLen
	end repeat
	return offsets
end getOffsets
  • The first and most important reason was that we search and replace inside TextEdit to make sure that we keep our markup.
  • When search and replace, the markup of the replace string must equal the markup of the first character of the search string. Therefore I delete the second to last character of the search string and keep the first character in the text. Then I replace the remaining character, the first character of the search string, with the replace string.
  • I get all the offsets of a substring before starting to replace them because when the replace string contains the search string we would end in an infinite loop
  • We reverse the offsets the search and replace works backwards, this way the string before the replacement is always untouched resulting that the offsets of the substring to proceed won’t shift.
  • In the getOffsets handler I’m using text item delimiters to determine the offset rather than using the offset command because of 2 reasons. 1) this way our code is scripting addition free (read: we don’t need the use scripting addition statement in Mavericks) 2) offset command is slower on some machines than text item delimiters