Help setting caret at specific position in text quickly Topic is solved

Get help with using AutoHotkey and its commands and hotkeys
NiptheZephyr
Posts: 12
Joined: 01 Aug 2020, 21:33

Help setting caret at specific position in text quickly

08 Aug 2020, 09:07

Good morning all!

My goal is to simulate jumping to predefined delimiters in a window's typed text. This function is seen in applications like Dragon Naturally Speaking (next field command jumping to whatever text is in []) and in the Epic Electronic Medical Record (F2, if I remember correctly jumping to ***). The purpose is to have templates where I can jump to the predefined fields and either edit them or accept what is there. I have a functional way of doing this with the below code, but it is too slow. The code checks the active window's text and how many characters it is, puts it in a variable, RegExMatch's [] with any amount of text between the brackets and gets the first instances position and length, counts the number of return lines between that position, presses left enough times to put the caret at the start of the text to have a consistent starting position, then presses right enough to get placed just before the desired text, finally holding shift+right enough times to highlight the text.

While that is fast enough when trialing it in a Notepad document, when I connect to our very old system from home through Citrix, the delay with larger amount of texts while the computer presses right 500 times actually takes a couple of seconds, to the point where it's just be easier to look through and use the mouse to highlight what I want, defeating the purpose.

So, does anyone have a good way to hunt through editable text in programs that don't have an innate search function? It is also limited to the point where "Active Window" doesn't pull the specific text (uncertain if it is the program itself or accessing it through Citrix, but am using Ctrl+A/Ctrl+C to copy the text into a clipboard variable to work with it), and doesn't recognize Ctrl+down for skipping through paragraphs. I know there are AHK variables for the X and Y positions of the caret, but it appears those are read only. Or if there is a way to practically convert character position in a string to X and Y coordinates, then I could just have the mouse click at that spot. I'm fairly new to coding, but pick up things pretty quickly, so any pointers in possible directions would be useful.
V/r,
NiptheZephyr

Code: Select all

;Select next Field. Works in Notepad, but not in AHLTA through Citrix since AHLTA doesn't seem to let WinGetText, windowText, A work. May be able to get this working if I can dive into the HWND. Also need to check and see if this works on AHLTA that is accessed directly. Works with ^a,^c to set the clipboard as the windowText variable.

^+n:: ;jump to and highlight first delimiter
Sleep, 200 ;used to get rid of text if using a hotstring

WinGetText, windowText, A ;look for all the text in the active window, assign it to %windowText%

characterCount := StrLen(windowText) ;count number of characters to know how far back to set the cursor

RegExMatch(windowText, "O)\[.*?]", delimiter) ;search for the first occurance of [*], name stored in delimiter.Value, Position stored in delimiter.Pos, and length stored in delimiter.Len

NeedleAmount := 0 ;create variable with a value. The below few lines modified from a post by Leroxy: https://autohotkey.com/board/topic/26018-counting-characters-and-lines/

Loop % delimiter.Pos ;check all characters up through the delimiter for returns.
{
ReturnsBeforeDelimiter := InStr(windowText, Chr(13) ,false,A_index)
If A_index = %ReturnsBeforeDelimiter%
{
NeedleAmount := NeedleAmount + 1
}
}

Loop, %characterCount% ;set cursor at beginning of text
{
Send, {Left}
}

counter := delimiter.Pos - NeedleAmount - 1 ;count up the number of spaces needed to get to the start of the delimiter; NeedleAmount gets rid of 'r'n, and -1 sets you right before it.

Loop % counter ;move to just left of the delimiter
{
	Send, {Right}
} 

Loop % delimiter.Len ;highlight the delimiter
{
	Send, +{right}
}
Return

+esc:: ExitApp
NiptheZephyr
Posts: 12
Joined: 01 Aug 2020, 21:33

Re: Help setting caret at specific position in text quickly

08 Aug 2020, 09:10

Btw, looking at the code, wanted to clarify that the reason I'm looping {right} and {left} instead of saying {right 200} of whatever, is because I could not figure out how to put the variable "counter" inside of it, i.e. {right counter}. Not certain if I am using the wrong syntax, but if anyone can explain it to me or knows any nifty workarounds , maybe that would speed it up a bit.

V/r,

NiptheZephyr
NiptheZephyr
Posts: 12
Joined: 01 Aug 2020, 21:33

Re: Help setting caret at specific position in text quickly

16 Aug 2020, 09:13

Just an update here, and will post it on the script forums. If one can figure out which Control of whatever application you are using, one can use SendMessage 0xB1, which is the equivalent of EM_Select, which selects/highlights text at a certain position in a control. The update code below searches for anything inside of brackets, and then selects the text.

Code: Select all

;Should work in any program that you can dive into it's control that contains it's text.

^+n:: ;jump to and highlight first delimiter
Sleep, 200 ;used to get rid of text if using a hotstring
		
ControlGetText, windowText, Edit1, ahk_class  Notepad ;look for all the text in the active window's edit control, assign it to %windowText%

RegExMatch(windowText, "O)\[.*?]", delimiter) ;search for the first occurance of [*], name stored in delimiter.Value, Position stored in delimiter.Pos, and length stored in delimiter.Len

Start := delimiter.Pos - 1
End := Start + delimiter.Len

SendMessage, 0xB1,% Start, % End, Edit1, ahk_class  Notepad ; sends EM_Sel to select text (aka highlight) in the window from a start point to end point, in this case the position of the RegExMatch'd [], through its length. 
User avatar
boiler
Posts: 6131
Joined: 21 Dec 2014, 02:44

Re: Help setting caret at specific position in text quickly  Topic is solved

16 Aug 2020, 09:21

NiptheZephyr wrote:
08 Aug 2020, 09:10
Btw, looking at the code, wanted to clarify that the reason I'm looping {right} and {left} instead of saying {right 200} of whatever, is because I could not figure out how to put the variable "counter" inside of it, i.e. {right counter}. Not certain if I am using the wrong syntax, but if anyone can explain it to me or knows any nifty workarounds , maybe that would speed it up a bit.

Code: Select all

Send, {Right %counter%}
NiptheZephyr
Posts: 12
Joined: 01 Aug 2020, 21:33

Re: Help setting caret at specific position in text quickly

05 Sep 2020, 17:57

Thanks boiler! I ended up making that change in addition to a few below. The final version looks at the whole text and jumps between each of the selections with successive presses of the hotkey. Compressed version at top of code, version with commentary below.

Code: Select all


^+m::
ControlGetFocus, activeControlVar, A
ControlGetText, windowText, %activeControlVar%, A
	counter := 1
	dValue := []
	dPosition := []
	dLength := []
Loop
{ 
	RegExMatch(windowText, "O)\[.*?]", delimiter, counter)
	counter := delimiter.Pos + delimiter.Len
		if (delimiter.Value = "" )
			break
		dValue.Push(delimiter.Value)
		dPosition.Push(delimiter.Pos)
		dLength.Push(delimiter.Len)
}
	VarSetCapacity(Pos1, 4, 0), VarSetCapacity(Pos2, 4, 0)
	SendMessage, 0x00B0, &Pos1, &Pos2,%activeControlVar%, A
	Pos1 := NumGet(Pos1), Pos2 := NumGet(Pos2)
Loop
	{
	If(Pos2 < dPosition[A_Index])
	{
		Start := dPosition[A_Index] -1
		End := Start + dLength[A_Index]
		break
	}
	Else 
		If(dPosition[A_Index] = "")
		{
		Start := dPosition[1] -1
		End := Start + dLength[1]
		break
		}
	}
SendMessage, 0xB1,% Start, % End, %activeControlVar%, A
Return 

/*
;Code to select any text inside brackets, based upon current caret position, jumping to the next one whenever you re-press the appropriate key.

;Learning points include the following: Determining the active control based upon cursor position, pulling an active control's text, RegExMatch to search a string for a pattern, how to create an Array, how to reference an Array in the coding (must use Array[], not Array. ), how to push values into an array, using the A_Index variable to count the iteration of a loop, and SendMessages of EM_GETSEL and EM_SETSEL.

^+m::
;Next two lines identify the active window/control/text that your cursor currently is in.
ControlGetFocus, activeControlVar, A
ControlGetText, windowText, %activeControlVar%, A

;Next 4 lines create arrays to store value/position/length of the string caught by RegExMatch, with the counter variable assisting in making a loop to store ALL of the substrings, instead of just the first one.
counter := 1
dValue := []
dPosition := []
dLength := []

;Loops to read all of the text in the window and create variables stored inside the arrays. Can pull the first one only out without arrays using the delimiter.Value, delimiter.Pos and delimiter.Len variables. using the Array.Push function stores what is pulled each loop into a key in the specific array. The counter variable increases in size, having RegExMatch start reading the text just after the substring it previously caught, allowing it to work its way through the text. Finally, the Loop breaks when RegExMatch puts "nothing" in the delimiter.

Loop
{ 
	RegExMatch(windowText, "O)\[.*?]", delimiter, counter)
	counter := delimiter.Pos + delimiter.Len
		if (delimiter.Value = "" )
			break
		dValue.Push(delimiter.Value)
		dPosition.Push(delimiter.Pos)
		dLength.Push(delimiter.Len)
}

;Coding needed to create the variables to store the positions of the caret. SendMessage 0x00B0 is EM_GETSEL, querying and pulling the locations of the caret.
VarSetCapacity(Pos1, 4, 0), VarSetCapacity(Pos2, 4, 0)
SendMessage, 0x00B0, &Pos1, &Pos2,%activeControlVar%, A
Pos1 := NumGet(Pos1), Pos2 := NumGet(Pos2)

;Bringing it all together, this loop compares the current caret position to the positions of the substrings one at a time, jumping to the next one in front of the caret, or if your caret is past them all, jumping to the first one. It creates the start and end variables of the below SendMessage, based upon which time through the loop it is (logged via the A_Index built in variable.
Loop
	{
	If(Pos2 < dPosition[A_Index])
	{
		Start := dPosition[A_Index] -1
		End := Start + dLength[A_Index]
		break
	}
	Else 
		If(dPosition[A_Index] = "")
		{
		Start := dPosition[1] -1
		End := Start + dLength[1]
		break
		}
	}

;The final part of the immediate selection, SendMessage 0xB1 is EM_SETSEL, which highlights 
SendMessage, 0xB1,% Start, % End, %activeControlVar%, A
Return
*/

Return to “Ask For Help”

Who is online

Users browsing this forum: Dante, DavidP, geloSemen, Google [Bot], Lem2001, mikeyww, nestea2k and 50 guests