1 (изменено: Krot66, 2017-05-03 12:04:19)

Тема: AHK: Совсем другой заменитель Punto Switcher'a

Сочинения на эту тему писались уже не однажды. Три с половиной года назад один из таких скриптов был написан мной. Он был сделан на основе команды Input и был лишен некоторых недостатков своих предшественников, позволяя исправлять текст в середине строки.
Все это время во мне подспудно зрело понимание того, что коренным недостатком наших изделий является сам выбор обрабатываемого текста. Например, Punto и большинство скриптов работают только с последним словом. Делались модификации моего скрипта, позволяющие обрабатывать и предыдущие. Некоторым это нравилось, но, очевидно, иногда это хорошо, а иногда плохо, и здесь нужна возможность выбора. Пробелами этот вопрос не ограничивается: если человек часто набирает узкие колонки текста, пусть он сам решает, преобразовывать хвост предыдущей строки или нет.
С программированием вообще все совсем неважно: когда идет набор латинских и кирилических символов без интервалов, плох и Punto Switcher, и все его заменители, т.к. конвертация без ручного выделения делается невозможной.

Поскольку мы стремимся к некоторой интерактивности, скрипт не забивает автоматом символы, а сначала выделяет требуемый участок текста, и уже затем осуществляет его преобразование.
1. Если нажать вслед за набранным текстом "Pause", после небольшой задержки (об этом позже), будет выделено последнее слово и при отпускании клавиши исправлена его раскладка. Но если удержать клавишу нажатой, будет выделено предыдущее слово, а затем и предшествующее ему, пока не будет исчерпана строка, определяемая длиной введенного текста или нажатием "прерывающих" клавиш, прописанных в переменной endkeys (тултип над строкой показывает число оставшихся символов). Соответственно, при отпускании будет конвертирован весь выделенный текст. Конечные пробелы не препятствуют преобразованию текста.
2. Для программирования можно прописать в регулярном выражении комбинации символов, на которых выделение "по словам" будет автоматически останавливаться помимо пробелов. Таким образом можно предотвратить типовые ошибки, когда выделение наезжает на функцию с кирилическим аргументом или html-тэг (в скрипте прописан самый минимум для аргументов функций, присвоения строковых переменных и условных выражений ahk).
3. Помимо выделения "по словам", скрипт позволяет работать и с посимвольным выделением. Если отпустить и повторно нажать "Pause" (после выделения "по словам" или в самом начале - для этого и нужна задержка), дальше выделение будет производиться в посимвольном режиме, причем как при фиксации клавиши, с заданной скоростью выделяющем текст, так и при коротких, дающих лучшую точность нажатиях.
4. Помимо исправления раскладки, скрипт может инвертировать регистр набранного текста (случайное нажатие Caps Lock), переводить его в верхний или нижний регистр или делать первую букву строчной. Выделение работает так же, как было описано выше. Держать нажатой дополнительную клавишу не нужно, в том числе и при посимвольном выделении - достаточно нажатия в самом начале.
5. При вставке по Ctrl+V возможно выделение и изменение регистра текста, как если бы он был только что набран.
6. Повторное нажатие горячей клавиши восстанавливает только что конвертированную строку в ее изначальном виде, причем за этим можно предпринять более удачную попытку выделения текста и его преобразование. При желании можно исправить раскладку, а потом регистр текста - все это возможно пока не произведен непосредственный ввод символов.
7. Скрипт работает с консолью, правда без преобразования ручного выделения. Различие еще в том, что вместо выделения производится затирание символов. Работает и отмена изменений.
8. Скрипт может работать в многострочном режиме. Как это сделать и существующие ограничения описано в настройках.
9. Все клавиши переназначаемые - подробности там же.

В скрипте спользован код определения текущей раскладки от teadrinker и creature.ws, а также ее переключения от YMP.


#NoEnv 
SetWorkingDir %A_ScriptDir%
#SingleInstance, force
#MaxHotkeysPerInterval 500
Menu, Tray, Icon, shell32.dll, 39
SendMode Input

/*
Скрипт для быстрого исправления раскладки и регистра набранного текста
http://forum.script-coding.com/viewtopic.php?pid=115616#p115616

Pause - исправление раскладки набранного текста
RShift+Backspace - инверсия регистра текста (случайное нажатие Caps Lock)
RShift+_ - текст в нижнем регистре
RShift+= - текст в верхнем регистре
RShift+0 - первая буква заглавная
При наличии выделенного текста, для преобразования используется он.
При вставке по Ctrl+V возможно выделение и изменение регистра текста, как если бы он был только что набран.
Повторное нажатие клавиш после конвертации восстанавливает исходную строку, после чего возможно выделение и преобразование текста.
*/

key_pass:=300 ; время зажатия клавиши, необходимое для начала выделения "по словам"; короткое нажатие - начало посимвольного выделения (0 - без начальной задержки, посимволное выделение возможно только после первого слова) 
select_pause:=650 ; пауза перед выделением следующего "слова" при сохранении нажатия клавиши (0 - работа только с последним словом, как в Punto Switcher или старых весиях скрипта)
key_wait:=400 ; время ожидания повторного нажатия клавиши при посимвольном выделении до преобразования текста (0 - полное выключение посимвольного выделения; key_pass не учитывается)
symbolsel_speed:=3.5 ; скорость посимвольного выделения при постоянном нажатии клавиши, символов в секунду
stop_comb:="(\(""|, ?""|= ?"")" ; фрагмент регулярного выражения с вертикальными чертами в качестве разделителя, определяющего дополнительные точки остановки выделения
ttip_dy:=24 ; вертикальный сдвиг тултипа относительно каретки (0 - выключение тултипа)
endkeys:="{Enter}{NumpadEnter}{Tab}{LControl}{RControl}{LAlt}{RAlt}{LWin}{RWin}{Left}{Right}{Up}{Down}{Home}{End}{PgUp}{PgDn}{Del}{NumpadDel}{Ins}{NumpadIns}" ; завершающие клавиши команды Input, прерывающие запись символов и делающие невозможной отмену без выделения текста. Если убрать {Enter}{NumpadEnter}{Tab}, станет возможно преобразовывать текст, захватывающий несколько строк. В программистских редакторах, где начальный пробел или табуляция определяют отступ абзаца, многострочное преобразование будет давать ошибки в таких смещенных абзацах. При переназначении клавиш следует убрать используемую дополнительную клавишу из списка, если она там есть (например, {RControl})

; ================================================

Loop
{
	Input input_text, I V L500, % endkeys
	err:=ErrorLevel, text:=input_text, count+=StrLen(text)	
	If (err="NewInput") && mod
	{
		If (A_ThisHotkey=A_PriorHotkey) && !count && !restor && sel_text
		{
			Send % "{Bs " StrLen(sel_text) "}{Raw}" sel_text
			ControlGetFocus, CtrlFocus, A
			SendMessage, 0x50,, % lang_convert=1049 ? 0x4190419 : 0x4090409, % CtrlFocus, A
			restor:=1
		}
		else
		{
			If restor && !count && text_old
				text:=text_old
			If !restor && !count && text_new
				text:=text_new
			If (A_PriorHotkey="~^vk56") && Clipboard && !count
				text:=Clipboard
			lang_convert:=GetInputLangID("A")
			If mod=1
				conv_text:=Translate(Select(text))
			If mod=2
			{
				conv_text:=ChangeCase(Select(text),3)
				SetCapsLockState Off
			}
			If mod=3
				conv_text:=ChangeCase(Select(text),1)
			If mod=4
				conv_text:=ChangeCase(Select(text))
			If mod=5
				conv_text:=ChangeCase(Select(text),2)
			Send % "{Raw}" conv_text 
			text_new:=SubStr(text,1,StrLen(text)-StrLen(conv_text)) . conv_text
			text_old:=text, restor:=0 
		}
		Sleep 300
		KeyWait % masterkey, T2
		Hotkey % "*" masterkey, Block, Off
		count:=mod:=0
	}
}

>+0::
mod+=1
>+-::
mod+=1
>+=::
mod+=1
>+Bs::
mod+=1
Pause::
mod+=1
Input
return

~^vk56::count:=0

~+Left::
~*LButton::
~*RButton::
~*MButton::
Input
conv_text:=mod:=text:=text_old:=text_new:=""
Block:
return

Select(text)
{
	global
	masterkey:=RegExReplace(A_ThisHotkey,".*(\^|\+|!|#)")
	Hotkey % "*" masterkey, Block, On
	If text~="^\s*$"
	{
		clip_old:=Clipboard, Clipboard:=""
		Send ^{Ins}
		ClipWait 0.5
		If text:=Clipboard
		{
			Send {Del}			
			Clipboard:=clip_old
			return sel_text:=text
		}
		else
		{
			Tooltip Для преобразования`n выделите текст!!!, % A_CaretX-30, % A_CaretY-40
			Sleep 800
			ToolTip
			return
		}
	}
	ToolTip
	txt:=text, key_pass:=key_wait ? key_pass : 0
	key:=WinActive("ahk_class ConsoleWindowClass") ? "Bs" : "Left"
	KeyWait % masterkey, % "T" key_pass/1000
	If ErrorLevel
	{		
		While GetKeyState(masterkey,"P") && txt
		{
			txt_old:=txt			
			txt1:=RegExReplace(txt,"(^|\s+)\S+? {0,3}$")
			txt2:=RegExReplace(txt,stop_comb "\K\S+? {0,3}$", ,rep)
			txt:=(StrLen(txt2)>StrLen(txt1) && rep ? txt2 : txt1)
			Send % "{Shift down}{" key " " StrLen(txt_old)-StrLen(txt) "}{Shift up}"
			If txt
			{
				Sleep 50
				If ttip_dy
					ToolTip % "<< " StrLen(txt), % A_CaretX, % A_CaretY-ttip_dy
				KeyWait % masterkey, % "T" select_pause/1000-0.05
			}
		}
	}
	t_unpress:=A_TickCount	
	While A_TickCount-t_unpress<key_wait && txt
	{		
		Sleep 10
		If GetKeyState(masterkey,"P")
		{
			StringTrimRight txt, txt, 1
			Send {Shift down}{%key%}{Shift up}
			Sleep 50
			If ttip_dy
				ToolTip % "<< " StrLen(txt), % A_CaretX, % A_CaretY-ttip_dy
			KeyWait % masterkey, % "T" 1/symbolsel_speed-0.06
			t_unpress:=A_TickCount
		}
	}
	Sleep 50
	If !txt && ttip_dy
		ToolTip << 0 >>, % A_CaretX, % A_CaretY-ttip_dy
	KeyWait % masterkey, T2
	Sleep 100
	ToolTip
	Send {RShift up}{Del}	
	return sel_text:=SubStr(text,StrLen(txt)+1)
}	

Translate(text)
{
	Eng:="~QWERTYUIOP{}ASDFGHJKL:""ZXCVBNM<>?|``qwertyuiop[]asdfghjkl;'zxcvbnm,./@#$^&"
	Rus:="ЁЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮ,/ёйцукенгшщзхъфывапролджэячсмитьбю.""№;:?"
	Loop, parse, text, ,`r
	{ 		
		If p:=InStr(GetInputLangID("A")=1033 ? Eng : Rus, A_LoopField, true) 
			r:=r . SubStr(GetInputLangID("A")=1033 ? Rus : Eng, p, 1)
		else
			r.=A_LoopField 
	}
	ControlGetFocus CtrlFocus, A
	SendMessage, 0x50, 2,, % CtrlFocus, A
	return r
}

ChangeCase(t,n="")
{	
	If !n
		StringLower t, t
	If n=1
		StringUpper t, t
	If n=2
		StringLower t, t, T
	If n<3
		return t
	StringCaseSense, Locale
	Loop % StrLen(t)
	{
		r:=SubStr(t,A_Index,1)
		If r is upper
			StringLower r, r
		else
			StringUpper r, r
		out.=r
	}
	return out
}

GetInputLangID(window)
{
    If !(hWnd := WinExist(window))
        return
    WinGetClass, Class 
    if (Class == "ConsoleWindowClass")
    {
        WinGet, consolePID, PID
        DllCall("AttachConsole", Ptr, consolePID)
        VarSetCapacity(buff, 16)
        DllCall("GetConsoleKeyboardLayoutName", Str, buff)
        DllCall("FreeConsole")
        langID := "0x" . SubStr(buff, -3)
    }
    Else
        langID := DllCall("GetKeyboardLayout", Ptr, DllCall("GetWindowThreadProcessId", Ptr, hWnd, UInt, 0, Ptr), Ptr) & 0xFFFF 
    return langID
}

2

Re: AHK: Совсем другой заменитель Punto Switcher'a

Последнее набранное слово не конвертируется, при выделении слова, конвертируется через раз (иногда просто удаляет выделенное слово, иногда удаляет пробел между выделенным и предшествующим ему словом).

Win10x64

3

Re: AHK: Совсем другой заменитель Punto Switcher'a

У вас большая сила воли, раз вы исправляете текст таким образом.
Я останусь пока на своем варианте.

Win7x64 up to 2018, AutoHotkey 1.1.30.00

4

Re: AHK: Совсем другой заменитель Punto Switcher'a

becaesium
Странно это. Надо подумать.

5

Re: AHK: Совсем другой заменитель Punto Switcher'a

ИМХО чем держать все эти комбинации в голове и потом вспоминать их, быстрее будет выделить и просто нажать кнопку, оставив динамическую память мозга для других нужд.

Win 10 x64
AHK v1.1.24.00
                       Справка тебе в помощь.

6 (изменено: Krot66, 2017-05-03 21:20:55)

Re: AHK: Совсем другой заменитель Punto Switcher'a

Какие еще комбинации? Одна единственная кнопка, по крайней мере для основной функции.