Here’s an example of the appleScript part to actually make the changes.
The next step would be to add handlers that would get selected text from Pages, Notes and Mail, and then replace the selection with the fixed text.
But, to do that we’d need to know what context the script would be running in. You could do a version for each app, living in the Script Menu. (That’s how I would do it).
use AppleScript version "2.4"
use scripting additions
set myText to " , - ? / !{{ { }} { ) ]. } : ; - / ( [ {"
FixPunctuation(myText)
on FixPunctuation(myText)
set killSpaceBefore to {",", "-", "?", "/", "!", ")", "]", "}", ":", ";"}
set killSpaceAfter to {"-", "/", "(", "[", "{"}
repeat with thisChar in killSpaceBefore
set myText to my ReplaceAllInText(" " & thisChar as text, thisChar as text, myText)
end repeat
repeat with thisChar in killSpaceAfter
set myText to my ReplaceAllInText((thisChar as text) & " ", thisChar as text, myText)
end repeat
return myText
end FixPunctuation
on ReplaceAllInText(findString, replaceString, textToFix)
local findString, replaceString, textToFix
set saveTID to AppleScript's text item delimiters
repeat
set AppleScript's text item delimiters to findString as list
set textToFix to every text item of textToFix
if (count of textToFix) = 1 then
set textToFix to textToFix as text
exit repeat
end if
set AppleScript's text item delimiters to {replaceString}
set textToFix to textToFix as text
if replaceString is in {findString} then exit repeat -- exits after one pass to avoid infinite loop
end repeat
set AppleScript's text item delimiters to saveTID
return textToFix as text
end ReplaceAllInText
Thank you so much for your reply and for doing that for me!
I use HotKey to run applications/scripts from keyboard shortcuts, so – for simplicity – I was wanting to use the same script (therefore the same keyboard shortcut) for each app. I’ll be ‘cleaning up’ the text in any app that deals with bodies of text, so Pages/Notes/Mail/Safari/TextEdit and any others. Hope that helps?
To accomplish what you want, you have to use GUI scripting, which is far from reliable. Also, the script has to be run in the background, and I don’t know if that’s the case with HotKey. Anyways, I tested the following script with TextEdit, and it worked as expected:
use framework "Foundation"
use scripting additions
tell application "System Events" to key code 0 using command down -- select all
delay 0.5
tell application "System Events" to key code 8 using command down -- copy
delay 0.5
set theString to (the clipboard)
set theString to current application's NSMutableString's stringWithString:theString
set thePattern to " ([\\-?/!)\\]}:;])"
theString's replaceOccurrencesOfString:thePattern withString:"$1" options:1024 range:{0, theString's |length|()}
set thePattern to "([\\-/(\\[{]) "
theString's replaceOccurrencesOfString:thePattern withString:"$1" options:1024 range:{0, theString's |length|()}
set the clipboard to (theString as text)
delay 0.5
tell application "System Events" to key code 9 using command down -- paste
A better approach IMO is:
Manually select and copy the text to the clipboard in whatever app is being used.
Run the script which will clean the text on the clipboard.
Manually paste the cleaned text into the app.
Or, even better, use a shortcut if your version of macOS includes the Shortcut app. It is really good at doing stuff like this.
It returned the error “System Events got an error: Untitled is not allowed to send keystrokes. (1002)”.
Does that mean we can’t do it all fully in AppleScript? If not, I have BetterTouchTool so could do a sequence including the keystrokes before and after the script (it can also have delays between each step). So in effect I will ‘manually’: select all, copy, [run the clean-up script on the clipboard text, including copying the results back to clipboard], paste.
If that sounds okay, when you have time, would you mind amending the [script] as above please?
I would take a different approach with this. For this project I created a text file on my Desktop and pasted this following code into that new document and named it “SED_Commands.txt”.
I did this because it’s easier to just use AppleScript to read in all of those sed commands than to worry about figuring out all of the proper escape sequences to use in the do shell script command.
Then in Automator, I created a new Quick Action and added A Run AppleScript action to the Workflow with the following code, and saved that as “SED TEMP”
tell application "System Events" to keystroke "c" using command down
delay 0.1
do shell script (read alias (((path to desktop) as text) & "SED_Commands.txt"))
delay 0.1
tell application "System Events" to keystroke "v" using command down
Now I’m all set. Anytime I have text selected in any application that I want to remove the spaces before and after the punctuation, all I need to do is use my new Keyboard Shortcut, and the currently selected text will be edited.
NOTE: You will need to grant the appropriate permissions in System Settings to allow System Events and Automator to control your computer.
MW21. I retested my earlier script and it worked as expected. I don’t know why it doesn’t work for you.
I’ve modified my script to do the following:
Get the contents of the clipboard.
Report an error if the contents of the clipboard is not a string (there may be a delay of a few seconds before reporting this error).
Remove unwanted spaces from the string.
Show a dialog reporting the number of spaces deleted (this dialog can be omitted if desired).
Place the edited string back on the clipboard.
use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions
try
set theString to (the clipboard)
set theString to current application's NSMutableString's stringWithString:theString
set thePattern to " ([,\\-?/!)\\]}:;])"
set countOne to theString's replaceOccurrencesOfString:thePattern withString:"$1" options:1024 range:{0, theString's |length|()}
set thePattern to "([\\-/(\\[{]) "
set countTwo to theString's replaceOccurrencesOfString:thePattern withString:"$1" options:1024 range:{0, theString's |length|()}
display alert "The contents of the clipboard were processed" message ((countOne + countTwo) as text) & " spaces were deleted" buttons {"OK"} default button 1
set the clipboard to (theString as text)
on error
display alert "An error has occured" message "This most ccommonly happens when a string is not found on the clipboard"
end try
BTW, you may need to place a delay in Better Touch Tool before running this script. Also, as written my script only removes one space before or after the specified characters, but this can be changed to remove multiple spaces if that’s desired.
Thanks for your help everyone, I’ve now got it working! The only remaining problem is that it’s stripped any styling from the cleaned-up text, which will be a problem e.g. in a text document in Notes or Pages. Is there any way around this?
I used peavine’s amended script above, run by a keyboard shortcut in BetterTouchTool, which also included the Mac keystrokes (select all, copy, paste) around the script, with a short delay for the script to complete. Works brilliantly in every app, save for the formatting issue.
I’ve just realised that trimming a double- or triple-space down to a single space, would also be beneficial to me. Would anyone be so kind as to paste me a script for that that I can include in Peavine’s script above?
MW21. I’ve revised my script to replace two and three consecutive spaces with a single space. The timing result with a document that contained 1,025 paragraphs of text with 19,456 replacements was 40 milliseconds.
use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions
try
set theString to (the clipboard)
set theString to current application's NSMutableString's stringWithString:theString
set thePattern to " {2,3}" -- 2 or 3 consecutive spaces
set countOne to theString's replaceOccurrencesOfString:thePattern withString:" " options:1024 range:{0, theString's |length|()}
set thePattern to " ([,\\-?/!)\\]}:;])" -- space before specified character
set countTwo to theString's replaceOccurrencesOfString:thePattern withString:"$1" options:1024 range:{0, theString's |length|()}
set thePattern to "([\\-/(\\[{]) " -- space after specified character
set countThree to theString's replaceOccurrencesOfString:thePattern withString:"$1" options:1024 range:{0, theString's |length|()}
display alert "The contents of the clipboard were processed" message ((countOne + countTwo + countThree) as text) & " replacements were made" buttons {"OK"} default button 1
set the clipboard to (theString as text)
on error
display alert "An error has occured" message "This most ccommonly happens when a string is not found on the clipboard"
end try
BTW, apps vary in how they format a document and interact with the clipboard, and I do not know of a way to retain formatting as you want. However, my knowledge of the clipboard is limited, and perhaps another forum member will have a solution.
Regarding formatting, if we just do scripts for Notes and Pages, would that be possible? If not, hope someone else has some ideas re that. I saw the below thread yesterday but haven’t read through it yet:
Robert. I created the test string with a script, which I failed to save, but I’ve created a reasonable facsimile of it below. I retested my script in post 15 and the timing results were 42 milliseconds with 17,408 replacements.
set theString to "this , is - a ? test / this ! is ( a ) test [ this ] is { a } test : this ; is two spaces and three spaces" & linefeed
repeat 10 times
set theString to theString & theString
end repeat
set the clipboard to theString
count paragraphs of theString --> 1025
The OP does not have the Shortcuts app on his computer, but I thought I would provide a shortcut solution just in case a forum member or Googler might need one in the future. To test this shortcut:
Download and install the shortcut (it’s easily deleted).
Open an app with the text to be processed.
Select the text to be processed.
Run the script by way of the app’s Services menu.
After doing the above, the shortcut will clean the text and will replace the selected text in the app with the cleaned text. In very-limited testing with a TextEdit Rich Text document, some formatting was retained but some was lost.