Autohotkey 将在执行过程中提前结束循环

Autohotkey will prematurely end loop during execution

我编写了一个 AutoHotkey 脚本,它允许我在 Microsoft SQL Server Management Studio 2012 中的光标当前位置 运行 SQL 查询。它有效--大多数时候都是这样。

该实现使用一种增量移动来确定它何时到达 SQL 查询块的顶部或 window 的顶部。然后它向下选择,直到它以相同的方式到达 window 的底部或块的底部。然后,它按 F5 键 运行 突出显示的脚本。

这是脚本:

$F5::
    ; first check that current line isn't blank (the cursor has to be on some text)
    Send, {Home}
    hltext := SelectNextChar()
    If hltext = `r`n
    {
        ; return to current position
        Send, {Left}
        Return
    }

    ; move cursor up until it gets to the top of the text block

    ; get first length for future comparisons
    Send, {End}
    Send, {Shift Down}{Home}{Shift Up}
    Send, ^c
    StringLen, slctSize, clipboard

    ; begin checking lengths to see if there is still 'movement'
    Loop
    {
        Send, {Shift Down}{Left}{Home}{Shift Up}
        Send, ^c

        ; if the new length is the same then we've hit the top and can break out
        StringLen, temp, clipboard
        IfEqual, slctSize, %temp%
        {
            Send, {Left}
            Break
        }

        ; if we have hit a blank space then we can stop here as well
        firstChar := SubStr(clipboard, 1, 1)
        If firstChar is space
        {
            Send, {Left}{Down}
            Break
        }

        ; if neither one of these conditions are met, continue on
        slctSize = %temp%
        Sleep, 50
    }

    ; select down until blank space or end of file

    Send, {Shift Down}{End}{Shift Up}
    Send, ^c
    StringLen, slctSize, clipboard

    Loop
    {
        Send, {Shift Down}{Right}{End}{Shift Up}
        Send, ^c

        StringLen, temp, clipboard
        IfEqual, slctSize, %temp%
        {
            Break
        }

        ; if we have hit a blank space then we can stop here as well
        lastChar := SubStr(clipboard, 0)
        If lastChar is space
        {
            Break
        }

        ; if neither one of these conditions are met, continue on
        slctSize = %temp%
        Sleep, 50
    }

    ; execute!
    Send, {F5}
    ; place cursor at end of last line
    Send, {Right}
Return


SelectNextChar()
{
    Send, {LShift Down}{Right}{LShift Up}
    Send, ^c
    return %clipboard%
}

这些移动——向上然后向下——有时会提前结束,导致在按下 F5 时查询没有被完全选中。对于小查询,这不是问题;对于跨越 ~10 行以上的大型查询,很快就会发现正在发生某些事情。

我测试过将 Sleep, 500 放入循环中, 似乎 可以解决这个问题,但这个脚本的全部意义在于对飞得快多了。如果我等待它(希望)突出显示超过 2 或 3 秒,那么是什么让它比我的旧策略更快,即用鼠标手动突出显示?

需要说明的是,较大的查询 大部分是 运行,但该过程的速度不够快,无法保证使用,并且不能保证它们会执行前完全突出显示。我也明白我的实现本质上是 O(x^2)。但是,如果我 运行 脚本完全爆炸 (SetKeyDelay, -1),那将不是问题。

对此事有任何想法,还是只是 OS/program/AHK 的限制?

此外,是我的问题还是 SO 的 AHK 语法突出显示严重损坏?

更新:这是包含建议编辑的更新脚本:

SendMode, Input ; Very fast but gives unpredictable results at low sleep speeds
SetBatchLines, -1

$F5::
    ; first check that current line isn't blank (the cursor has to be on some text)
    Send, {Home}
    hltext := SelectNextChar()
    If hltext = `r`n
    {
        ; return to current position
        Send, {Left}
        Return
    }

    ; move cursor up until it gets to the top of the text block

    ; get first length for future comparisons
    Send, {End}
    Send, {Shift Down}{Home}{Shift Up}
    Send, ^c
    SleepAfterCopy()
    StringLen, slctSize, clipboard

    ; begin checking lengths to see if there is still 'movement'
    Loop
    {
        Send, {Shift Down}{Left}{Home}{Shift Up}
        Send, ^c
        SleepAfterCopy()

        ; if the new length is the same then we've hit the top and can break out
        StringLen, temp, clipboard
        IfEqual, slctSize, %temp%
        {
            Send, {Left}
            Break
        }

        ; if we have hit a blank space then we can stop here as well
        firstChar := SubStr(clipboard, 1, 1)
        If firstChar is space
        {
            Send, {Left}{Down}
            Break
        }

        ; if neither one of these conditions are met, continue on
        slctSize = %temp%
    }

    ; select down until blank space or end of file

    Send, {Shift Down}{End}{Shift Up}
    Send, ^c
    SleepAfterCopy()
    StringLen, slctSize, clipboard

    Loop
    {
        Send, {Shift Down}{Right}{End}{Shift Up}
        Send, ^c
        SleepAfterCopy()

        StringLen, temp, clipboard
        IfEqual, slctSize, %temp%
        {
            Break
        }

        ; if we have hit a blank space then we can stop here as well
        lastChar := SubStr(clipboard, 0)
        If lastChar is space
        {
            Break
        }

        ; if neither one of these conditions are met, continue on
        slctSize = %temp%
    }

    ; execute!
    Send, {F5}
    ; place cursor at end of last line
    Send, {Right}
Return


SelectNextChar()
{
    Send, {LShift Down}{Right}{LShift Up}
    Send, ^c
    return %clipboard%
}

SleepAfterCopy()
{
    Sleep, 50
}

更新 2: 这是每个 @Sidola 包含 ClipWait 的版本。因此它 运行s 尽可能快。此更新还包括逻辑,以确保如果您之前复制了一些东西,它不会因为我们过度使用剪贴板而被抹杀。最后,它考虑了行首/末尾的任何缩进或空格:

SendMode, Input ; Very fast but gives unpredictable results at low sleep speeds
SetBatchLines, -1

#IfWinActive ahk_exe Ssms.exe

$F5::
    ; Save current clipboard material and restore it at the end
    before = %clipboard%

    ; first check that current line isn't blank (the cursor has to be on some text)
    Send, {Home}
    clipboard =
    hltext := SelectNextChar()
    If hltext = `r`n
    {
        ; return to current position
        Send, {Left}
        Return
    }

    ; move cursor up until it gets to the top of the text block

    ; get first length for future comparisons
    Send, {End}
    Send, {Shift Down}{Home}{Home}{Shift Up}
    clipboard =
    Send, ^c
    SleepAfterCopy()
    StringLen, slctSize, clipboard

    ; begin checking lengths to see if there is still 'movement'
    Loop
    {
        Send, {Shift Down}{Left}{Home}{Home}{Shift Up}
        clipboard =
        Send, ^c
        SleepAfterCopy()

        ; if the new length is the same then we've hit the top and can break out
        StringLen, temp, clipboard
        IfEqual, slctSize, %temp%
        {
            Send, {Left}
            Break
        }

        ; if we have hit a blank space then we can stop here as well
        firstChar := SubStr(clipboard, 1, 1)
        If firstChar = `r
        {
            Send, {Left}{Down}
            Break
        }



        ; if neither one of these conditions are met, continue on
        slctSize = %temp%
    }

    ; select down until blank space or end of file

    Send, {Shift Down}{End}{Shift Up}
    clipboard =
    Send, ^c
    SleepAfterCopy()
    StringLen, slctSize, clipboard

    Loop
    {
        Send, {Shift Down}{Right}{End}{Shift Up}
        clipboard =
        Send, ^c
        SleepAfterCopy()

        StringLen, temp, clipboard
        IfEqual, slctSize, %temp%
        {
            Break
        }

        ; if we have hit a blank space then we can stop here as well
        lastChar := SubStr(clipboard, 0)
        If lastChar = `n
        {
            Break
        }

        ; if neither one of these conditions are met, continue on
        slctSize = %temp%
    }

    ; execute!
    Send, {F5}
    ; place cursor at end of last line
    Send, {Right}

    ; restore clipboard
    clipboard = %before%
Return

+F5::
    Send, {F5}
Return


SelectNextChar()
{
    Send, {LShift Down}{Right}{LShift Up}
    Send, ^c
    SleepAfterCopy()
    return %clipboard%
}

SleepAfterCopy()
{
    ClipWait
    ; Sleep, 30
}

它提前结束的原因可能是复制命令没有足够的时间在您开始工作之前正确更新剪贴板。

处理此问题的最佳方法是在复制任何内容之前清除剪贴板,并依靠 ClipWait 来告诉我们何时复制了某些内容。或者让它超时,告诉我们没有复制任何内容。

ClipWait 还允许我们在不检查重复项的情况下检测文档的顶部和底部,因为我们只是在等待超时。

下面是一个尽可能快的工作示例。

但请注意:此脚本仅适用于复制命令正常运行的程序。这意味着如果您在没有选择的情况下进行复制,则不会复制任何内容。在尝试此操作时,我发现某些程序没有此行为,因此该脚本不适用于这些程序。在这些情况下,您将不得不以某种方式检查重复项。

SendMode, Input
SetBatchLines, -1

Esc::ExitApp

$F5::
    ; Check if the line we're currently at is just a line break
    if (isLineBreak( getFirstChar() ))
        return

    ; Get to the top of the document
    traverseText("up")
    ; Get to the bottom
    lineCount := traverseText("down")
    ; Select everything
    selectAllLines(lineCount)
return

; --- Only functions below ---

selectAllLines(lineCount) {
    Send, {LShift Down}
    Loop, % lineCount {
        Send, {Up}
    }
    Send, {Home}
    Send, {LShift Up}
} 

traverseText(direction) {
    Loop {
        selectLine(direction)
        thisLine := copyText()

        ; If it's just a line break, we're out
        if (isLineBreak(thisLine)) {

            ; If we were going up we want to move down once first
            if (direction = "up")
                Send, {Down}

            break
        }

        ; If nothing was copied, we're out
        if (!thisLine)
            break

        i := A_Index ; Keep track of how many lines we've moved passed
    }

    ; If we had a line break beneath us
    ; we need to add one to the counter
    if (thisLine)
        i++

    return i ; Return the amount of lines we traversed
}

isLineBreak(value) {
    return value = "`r`n"
}

getFirstChar() {
    Send, {Home}+{Down} ; Home, Shift + Down
    char := copyText()
    Send, {Left}
    return char
}

selectLine(direction) {
    Send, % direction = "up" ? "{Home}" : "{End}" ; Ternary operator 
    Send, {LShift Down}
    Send, % direction = "up" ? "{Up}" : "{Down}" ; Ternary operator 
    Send, {LShift Up}
}

copyText() {
    Clipboard := ""
    Send, ^c
    ClipWait, 0.2 ; Time-out after 200ms
    return Clipboard
}