Place Cursor at beginning of paragraph n

Hi,
Is it possible to place the cursor at the beginning of a particular paragraph in any application,
Let’s say in “Script Editor” ?

-- Tested with SE 2.10 in 10.13.1.

set targetParagraphNumber to 12

delay 5 -- For testing only. Use the time to bring the target document to the front.

tell document 1 of application "Script Editor"
	if (targetParagraphNumber is 1) then
		-- Use the index of the insertion point at the beginning of the text.
		set i to 1
	else
		-- Get the index of the insertion point after the line ending before the target paragraph …
		set i to (count characters of paragraphs 1 thru (targetParagraphNumber - 1)) + 1
		-- … then of the insertion point after any indentation at the beginning of the paragraph.
		repeat while (character i is in {tab, space})
			set i to i + 1
		end repeat
	end if
	-- Select the insertion point.
	set selection to insertion point i
end tell

-- Wiggle the cursor to bring it into view if it's not already.
tell application "System Events" to key code {124, 123}

Unfortunately, other applications require different code.

Edit: “Wiggle” added.

Here is one method for Microsoft Word.

tell application "Microsoft Word"
	activate
	set targetParagraphNumber to 12 as integer
	if targetParagraphNumber = 1 then
		set countValue to -1 as integer
	else
		set countValue to 1 as integer
	end if
	move range (text object of paragraph targetParagraphNumber of document 1) by a character item count countValue
end tell

@Nigel Garvey
I did suspect the insertion point to be a helpful command, but just now I realized how it works. This outcome is amazing!
I tested your code with a short script and everything worked like a charm. Then I tried to apply my flavour and tested it on a longer script; paragraph 500. Of course tabs and spaces have a much higher numerical value than paragraph 500. Maybe grep could be faster to count tabs and spaces than a repeat handler (as AS Editor hung for long without too resolve), but I don’t figure out how.

@haolesurferdude
Thanks very much for sharing! This command could apply very well with calc tables, I think

Hi Joy.

I’m afraid I don’t understand that sentence. But what takes the time with a longer script document isn’t the repeat — which only iterates across the indentation at the beginning of the target paragraph — but counting the characters of all the preceding paragraphs within the Script Editor document itself. It’s faster to extract the contents as AppleScript text first and do the count in that:

-- Tested with SE 2.10 in 10.13.1.

set targetParagraphNumber to 422

delay 5 -- For testing only. Use the time to bring the target document to the front.

if (targetParagraphNumber is 1) then
	-- Use the index of the insertion point at the beginning of the text.
	set i to 1
else
	-- Get the contents of the document as AppleScript text.
	tell document 1 of application "Script Editor" to set txt to contents
	
	-- Get the index of the insertion point after the line ending before the target paragraph …
	set i to (count text 1 thru paragraph (targetParagraphNumber - 1) of txt) + 2
	-- … then of the insertion point after any indentation at the beginning of the paragraph.
	repeat while (character i of txt is in {tab, space})
		set i to i + 1
	end repeat
end if

-- Select the insertion point in the Script Editor document.
tell document 1 of application "Script Editor" to set selection to insertion point i

-- Wiggle the cursor to bring it into view if it's not already.
tell application "System Events" to key code {124, 123}

As an exercise, I’ve worked out a regex version. I don’t think it’s any better than the vanilla one immediately above, but it’s interesting for a bug — er, “feature” — it’s brought to light which affects Script Editor, Script Debugger, and ASObjC Explorer 4 in both High Sierra and El Capitan. Namely: if a script running in one of these editors contains a ‘tell’ statement addressed to the editor itself, any ASObjC code below that point isn’t understood. For example, running this in Script Editor:

use AppleScript version "2.4"
use framework "Foundation"
use scripting additions

set str to current application's class "NSString"'s stringWithString:("Hello")
say (str as text) --> No problem.

tell application "Script Editor"
end tell

set str to current application's class "NSString"'s stringWithString:("Hello")
--> error "Script Editor got an error: class \"NSString\" doesn’t understand the “stringWithString_” message." number -1708 from class "NSString"

One way round this is to move anything the editor has to do before the ASObjC to a handler or handlers at the bottom of the script:

-- Tested with SE 2.10 in 10.13.1.

use AppleScript version "2.4"
use framework "Foundation"

set targetParagraphNumber to 422

delay 5 -- For testing only. Use the time to bring the target document to the front.

if (targetParagraphNumber is 1) then
	-- Use the index of the insertion point at the beginning of the text.
	set i to 1
else
	-- Use a handler below the main script to get the contents of the Script Editor document as AppleScript text …
	set txt to getDocumentContents()
	-- … then use ASObjC to convert it to an NSString.
	set str to current application's class "NSMutableString"'s stringWithString:(txt)
	
	-- Using a regex, reduce the NSString to everything in it up to the end of any indendation in the target paragraph.
	tell str to replaceOccurrencesOfString:("\\A((?:.*+\\R){" & targetParagraphNumber - 1 & "}[[:blank:]]*+).*+") withString:("$1") options:(current application's NSRegularExpressionSearch) range:({0, its |length|()})
	-- The length of its AS equivalent + 1 is the insertion point index we need.
	set i to (count (str as text)) + 1
end if

-- Select the insertion point in the Script Editor document.
tell document 1 of application "Script Editor" to set selection to insertion point i

-- Wiggle the cursor to bring it into view if it's not already.
tell application "System Events" to key code {124, 123}

on getDocumentContents()
	-- This is in a handler below the main code so that 'tell application "Script Editor"' isn't above the ASObjC code.
	tell document 1 of application "Script Editor" to return contents
end getDocumentContents

:cool:
I tried all I know to get the “wiggle” :lol: working. Running the key code from within Script Editor does nothing, asistive devices are on and the Script Editor is authorized. My OS is 10.11

-- Wiggle the cursor to bring it into view if it's not already.
	delay 0.3
	try
		tell application "System Events" #to key code 124 #{124, 123}
			#tell process ASEditor #"Script Editor"
			key code 124 #{124, 123}
			key code 123 #{124, 123}
			#end tell
		end tell
	on error err
		activate
		display dialog err
	end try

Anyway the insertion point works :wink:

It’s a beauty, isn’t it. My hand-waving theory is that the call to the editor makes it match current application, which then stops current application from doing its thing with ASObjC.

I’ll look into it. It’s not all that brilliant in 10.13 either. :wink: I’m finding that when a very long script’s opened in Script Editor, it’s not always possible to scroll to the bottom of it. Running the script above makes it possible, but doesn’t actually do it first time. :rolleyes:

It looks to be something like that. In fact it’s current application which isn’t understood rather than ASObjC per se. Grabbing that first gets round the problem too:

use AppleScript version "2.4"
use framework "Foundation"
use scripting additions

set |⌘| to current application

set str to |⌘|'s class "NSString"'s stringWithString:("Hello")
say (str as text) --> No problem.

tell application "Script Editor"
end tell

set str to |⌘|'s class "NSString"'s stringWithString:("Hello")
say (str as text) --> No problem.

Hi Joy.

The wiggle does work on my macOS 10.11 machine, but isn’t totally reliable. Script Editor has to be the frontmost app, of course, and the key codes (right arrow and left arrow) are applied to the frontmost window. If you happen to come across a more reliable way to make the window scroll to the cursor position, do let me know! :slight_smile: