Тема: 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
}