fuzzywan. I’m pretty sure you’ve correctly identified the issue. I’ll take a look and see if this can be fixed.
I haven’t been following, but what if you use something like:
set {cr1, cr2} to selection ASObjC range
And then remove the corrections for zero-based indexes used elsewhere.
Thanks Shane for the suggestion. I wasn’t aware of that property, and I think it can be used to simplify my script.
fuzzywan. As best I can determine, the issue arises when a character anywhere in the script cannot be expressed in 16 bits, and, when any such character is encountered, the paragraphRangeForRange
method returns a result that breaks the script. Most characters found in a script including most accented Latin characters can be expressed in 16 bits, but emoticon codes cannot. There are some alternatives to the paragraphRangeForRange
method, and I’ll investigate those, but your script in post 6 completely avoids this issue and should probably be preferred.
I like that your version is faster, but I do use a lot of emoji in my scripts (I’m sure no one here would’ve guessed that ).
FWIW, it was added for just this sort of scenario.
Ironically, the selection expansion is the problem in both cases. Just happens to be two very different problems.
The Select Lines command handles emoji just fine, but it accounts for almost half the run time of the entire dmbrown-fuzzywan script. Combining the two approaches — call it dmbrown-fuzzywan-peavine, or dfp for short — shaves off 10–20ms, but pure peavine is still twice as fast. And dfp doesn’t persist the selection. Sigh…
FWIW, I thought one of the four NSString properties that normalize strings might allow my script to work with emoji’s but that wasn’t the case. As far as I can tell, the four normalize properties simply ignore emojis, although my knowledge on this topic is quite limited.
Normalize accented letters, which apparently are already 16-bit code units. This works as I would expect.
Normalize emojis, which doesn’t appear to do anything as regards the precomposed properties.
Hmm. Since count (characters of (theString as text))
gives a different result — “” has a
length
of 2 and count
of 1 — that gives me a starting point.
The difference between the count
and length
of everything prior to cr1
could be used as an adjustment factor. Something like this:
on getParagraphRange(theCode, cr1, cr2)
set theCode to current application's NSString's stringWithString:theCode
-- Adjustment factor for characters wider than one UTF-16 code unit, like emoji.
set leftString to characters 1 thru (cr1 - 1) of (theCode as text) as string
set leftString to current application's NSString's stringWithString:leftString
set codeUnitFactor to (leftString's |length|()) - (count (characters of (leftString as text)))
-- Adjust something (?) here.
set paragraphRange to theCode's paragraphRangeForRange:{(cr1 - 1), cr2}
return {((paragraphRange's location) + 1), paragraphRange's |length|}
end getParagraphRange
That gives me the right numbers — “” has a an adjustment factor of 1, and “
” has a factor of 3 due to the skin tone modifier — but using it to adjust
cr1
doesn’t have the effect I expected. It looks like the original selection itself is off by codeUnitFactor
, so that needs to be adjusted somehow. I’m not sure if it’s possible.
I rewrote my script in post 9 to incorporate the selection ASObjC range
property, as suggested by Shane. This cleans up the code a bit but does not alter the basic operation of the script.
use framework "Foundation"
use scripting additions
on main()
set commentCharacters to "--"
tell application "Script Debugger" to tell document 1
set allCode to source text
set {cr1, cr2} to selection ASObjC range
set {pr1, pr2} to getParagraphRange(allCode, cr1, cr2) of me
set selection ASObjC range to {pr1, pr2}
set selectedCode to selection
set {editedCode, editCount, editAction} to getEditedCode(selectedCode, commentCharacters) of me
set contents of selection to editedCode
set pr2 to (pr2 + ((count commentCharacters) * editCount * editAction))
set selection ASObjC range to {pr1, pr2}
end tell
end main
on getParagraphRange(theString, cr1, cr2)
set theString to current application's NSString's stringWithString:theString
set paragraphRange to theString's paragraphRangeForRange:{cr1, cr2}
return {paragraphRange's location, paragraphRange's |length|}
end getParagraphRange
on getEditedCode(theString, theCharacters)
set removeString to current application's NSMutableString's stringWithString:theString
set addString to current application's NSMutableString's stringWithString:theString
set removeCount to removeString's replaceOccurrencesOfString:("(?m)^(\\h*)" & theCharacters) withString:"$1" options:1024 range:{0, removeString's |length|()}
set addCount to addString's replaceOccurrencesOfString:"(?m)^(\\h*)" withString:(theCharacters & "$1") options:1024 range:{0, addString's |length|()}
if removeCount is equal to addCount then return {removeString as text, removeCount, "-1"}
return {addString as text, addCount, "1"}
end getEditedCode
main()
Applying the adjustment factor to cr1
or pr1
either makes things much, much worse, or in some cases (subtracting it from cr1
) has no effect at all. And since cr1
is setting pr1
(I think?), I don’t get why the effects are wildly different. I’m clearly not understanding how that part works.
You shouldn’t be using any adjustment at all – just use selection ASObjC range
to get the selection range in the right units to begin with.
fuzzywan. Can you test my revised script in post 24 with a script that contains emojis. I think it may have resolved that issue. I didn’t fully understand the operation of the selection ASObjC range
property and may have confused matters.
It’s the zero-based range in 16-bit units – that’s what NSString
works in.
Basically, if you’re going to manipulate text using normal AppleScript, use selection
; if you’re going to be converting it to an NSString
and manipulating it using ASObjC, use selection ASObjC range
.
I’ll be damned. I’d made some changes based on Shane’s selection ASObjC range
suggestion, but I must have missed part of it because it was working exactly the same as the original.
I see it now. Missed both of the set selection ASObjC range to {pr1, pr2}
lines.
Anyway, it’s working perfectly now.
I’ve been half following along; do you mind posting the final version?
how does one run this script (post #24) on a different script — i.e. not on itself ? Do I have to use a tool like FastScript to do it?
EDIT> just discovered the User Scripts Folder, and it hot-loads new scripts, nice. now I just need to assign a keyboard control… I wonder if Apples Shortcuts Settings are up for that?!
EDIT 2> it is, yay, something working for once.
You can set keyboard shortcuts for the Scripts menu right in Script Debugger’s settings. Very handy.
thanks, that’s even better!