Custom text wrapping script

Post your working scripts, libraries and tools
kkwillmert
Posts: 3
Joined: 14 Sep 2017, 15:22
GitHub: kkwillmert

Custom text wrapping script

14 Sep 2017, 16:00

In my search for a script that would help me write a hotkey to wrap highlighted text (for use in code header block comments), I stumbled across an old script by Gehn in the archived forums: https://autohotkey.com/board/topic/2459 ... ng-script/

Unfortunately, the script doesn't work on the current AutoHotkey version, I think probably because it uses array-style reference syntax (square brackets) in pseudo-array variable names within function calls (e.g. StringLen), which I'm guessing probably worked prior to the implementation of associative arrays (see https://autohotkey.com/docs/misc/Arrays.htm).

But I really wanted it, so I took the time to correct the bugs and spruce it up a bit. Since this took me most of a day and I found it pretty useful, I thought I should share (see below) :D

Suggested improvements would be appreciated. If anyone is interested in seeing the hotkey I wrote that utilizes this script, I can post that too.

Code: Select all

; TextWrapper.ahk
;
; AutoHotkey Version: 1.0.29
; Language:       English
; Platform:       Windows XP
; Author:         Gehn WillowGlade <gehnofsol@bigfoot.com>
;
; Updates made 09/14/2017 by kkwillmert as follows:
;  - Updated to work with AutoHotkey Versions 1.0.90+ (associative arrays broke
;    the original script);  The input arrays are now associative arrays, so
;    they must be initialized by the caller using Object() before they're
;    populated with their input values (see the revised Example below).
;  - Added limited ability to handle line breaks in the input string.  Two
;    consecutive line breaks will be treated as a delimiter which is preserved,
;    and the content between them is what gets wrapped.  Single line breaks are
;    converted to whatever the first character in breakCharacterSet[1] is, with
;    an attempt made at preventing this conversion from creating two
;    consecutive instances of that character.  This does, however, cause some
;    probably-undesirable behavior when there are an odd number of consecutive
;    line breaks in the original string, since only double line breaks are
;    preserved.
;
; Script Function:
;   Wraps text manually by adding in newline statements to a string using
;   a configurable methodology. Configuration is done by setting specific
;   variables, detailed below. Settings are remembered between calls. Most
;   variables have a default and hence do not absolutely require setting.
;   Text is wrapped by breaking the lines as long as possible, at certain
;   'break characters' defined in sets. These sets have priority, starting
;   at 1. Wrap Text will attempt to break the line using set 1, and if it
;   fails, it will continue on to set 2, and so forth, until it runs out of
;   sets. The result is that it will prefer wrapping very short lines to
;   wrapping at lower priority break characters.
;
; Variables:
;   textToWrap                - The text to be wrapped. No default.
;   maxCharactersPerLine      - The number of characters that are allowed
;                               on a line before a wrap is attempted. This
;                               setting defaults to 80.
;   breakType                 - This can be set to either 'after', 'before' 
;                               or 'eat'. 'after' will have lines break after
;                               the break character. 'before' will have the
;                               lines break before the break character. 'eat'
;                               will destroy the break character entirely.
;                               This setting defaults to 'after'.
;   breakCharacterSet[*]      - A set of individual characters which all
;                               have the same priority, starting at 1. Do
;                               not include spaces unless you want space to
;                               be a break character. Use a blank set to end
;                               the set list.
;   breakCharacterSetType[*]  - This allows the break type to be set on a per
;                               break character set basis. This setting
;                               defaults to the breakType setting.
;   forceWrap                 - This will force wrapping of the text, even if
;                               no break characters are found. In such a case
;                               the line will break right at the limit. Set
;                               this option using true or false. The default
;                               is false.
;   wrappedText               - This variable is not set. Instead, this is the
;                               text, wrapped, returned by Wrap Text.
;
; Example:
;   textToWrap := "C:\Games\Guild Wars\GW.exe"
;   maxCharactersPerLine := 10
;
;   breakCharacterSet    := Object()
;   breakCharacterSet[1] := "\"
;   breakCharacterSet[2] := " "
;   breakCharacterSet[3] := ""
;
;   Gosub , WrapText
;   MsgBox % wrappedText
;
;   wrappedText would be: "C:\Games\`nGuild`nWars\`nGW.exe"


; Invoked externally to perform core function
WrapText:
{
    Gosub , WrapText_Initialize
    
    If ErrorLevel <> 0
    {
        Return
    }

    ; Preserve double-line breaks, but re-wrap single-breaks
    originalTextToWrap := textToWrap
    newWrappedText     := ""

    StringReplace, textToWrap, textToWrap, `n`n, ``, All
    StringSplit, inputTextLines, textToWrap, ``
	Loop, %inputTextLines0%
	{
        textToWrap := inputTextLines%a_index%

        ; Use the first specified wrap character instead of `n; `n charactersseem to goof up
        ; the wrapping logic somewhere along the way.
        ; Also, try to prevent this conversion from creating two of the wrap character in a row
        wrapChar := SubStr( breakCharacterSet[1], 1, 1 )
        StringReplace, textToWrap, textToWrap, %wrapChar%`n, %wrapChar%, All
        StringReplace, textToWrap, textToWrap, `n%wrapChar%, %wrapChar%, All
        StringReplace, textToWrap, textToWrap, `n, %wrapChar%, All

        Gosub , WrapText_BreakIntoLines

        Gosub , WrapText_CombineLines

        newWrappedText := newWrappedText "`n`n" wrappedText
	}
    StringTrimLeft , newWrappedText , newWrappedText , 2

    wrappedText := newWrappedText

    Return
}

; Makes sure all variables are user set or filled with defaults (where applicable)
WrapText_Initialize:
{
    If ( textToWrap == "" )
    {
        MsgBox , 48 , WrapText Error , The variable 'textToWrap' must be set when WrapText is called.
        ErrorLevel := 1
        Return
    }

    If ( maxCharactersPerLine == "" )
    {
        maxCharactersPerLine := 80
    }
    Else If maxCharactersPerLine is not integer
    {
        MsgBox , 48 , WrapText Error , The variable 'maxCharactersPerLine' must be a positive, nonzero integer.
        ErrorLevel := 1
        Return
    }
    Else If ( maxCharactersPerLine <= 0 )
    {
        MsgBox , 48 , WrapText Error , The variable 'maxCharactersPerLine' must be a positive, nonzero integer.
        ErrorLevel := 1
        Return
    }

    If ( breakType == "" )
    {
        breakType := "after"
    }
    Else If ( !( breakType = "after" OR breakType = "before" OR breakType = "eat" ) )
    {
        MsgBox , 48 , WrapText Error , The variable 'breakType' may only be set to 'after', 'before' or 'eat'.
        ErrorLevel := 1
        Return
    }

    If ( breakCharacterSet[1] == "" )
    {
        breakCharacterSet := Object()
        breakCharacterSet[1] := " "
        breakCharacterSet[2] := ""
    }

    Loop
    {
        If ( breakCharacterSet[a_index] == "" )
        {
            Break
        }

        If ( !( breakCharacterSetType[a_index] == "" OR breakCharacterSetType[a_index] == "after" OR breakCharacterSetType[a_index] == "before" OR breakCharacterSetType[a_index] == "eat" ) )
        {
          MsgBox , 48 , WrapText Error , The variable 'breakCharacterSetType[%a_index%]' may only be set to 'after', 'before' or 'eat'.
          ErrorLevel := 1
          Return
        }
    }

    If ( forceWrap == "" )
    {
        forceWrap := false
    }
    Else If ( !( forceWrap == true OR forceWrap == false ) )
    {
        MsgBox , 48 , WrapText Error , The variable 'forceWrap' must be either true or false.
        ErrorLevel := 1
        Return
    }

    Return
}

; Breaks the text into lines, as needed
WrapText_BreakIntoLines:
{
    ; Chop off the ends of lines, to create new lines, until all lines are under the character limit

    WrapText_line      := Object()
    WrapText_line[1]   := textToWrap
    WrapText_lineCount := 1
    Loop
    {
        WrapText_currentLineNumber := a_index
        WrapText_currentLineLength := StrLen( WrapText_line[WrapText_currentLineNumber] )

        If ( WrapText_currentLineLength <= maxCharactersPerLine )
        {
            Break
        }
        Else
        { 
            ; Loop through break character sets until a break character is found, or we run out of sets

            WrapText_breakCharacterPosition := -1
            Loop
            {
                ; Search backwards from farthest possible character to find a break character

                WrapText_currentBreakCharacterSet := a_index

                If ( ( WrapText_breakCharacterPosition >= 0 ) OR ( ( breakCharacterSet[WrapText_currentBreakCharacterSet] == "" ) AND !forceWrap ) )
                {
                    Break
                }

                If ( ( breakCharacterSet[WrapText_currentBreakCharacterSet] == "" ) AND forceWrap )
                {
                    WrapText_forceWraptextToWrap := true
                }
                Else
                {
                    WrapText_forceWraptextToWrap := false
                }

                If ( breakCharacterSetType[WrapText_currentBreakCharacterSet] != "" )
                {
                    WrapText_currentBreakType := breakCharacterSetType[WrapText_currentBreakCharacterSet]
                }
                Else
                {
                    WrapText_currentBreakType := breakType
                }

                If ( WrapText_currentBreakType == "after" )
                {
                    WrapText_currentPosition := maxCharactersPerLine
                }
                Else
                {
                    WrapText_currentPosition := maxCharactersPerLine + 1
                }

                Loop , %maxCharactersPerLine%
                {
                    If ( WrapText_forceWraptextToWrap )
                    {
                        WrapText_breakCharacterPosition := 1
                    }
                    Else
                    {
                        WrapText_characterAtCurrentPosition := SubStr( WrapText_line[WrapText_currentLineNumber], WrapText_currentPosition, 1 )
                        WrapText_breakCharacterPosition := InStr( breakCharacterSet[WrapText_currentBreakCharacterSet], WrapText_characterAtCurrentPosition )
                    }

                    If ( WrapText_breakCharacterPosition > 0 )
                    {
                        WrapText_lineCount += 1
                        If ( WrapText_currentBreakType == "after" )
                        {
                            WrapText_line[WrapText_lineCount] := SubStr( WrapText_line[WrapText_currentLineNumber], WrapText_currentPosition + 1 )
                            WrapText_line[WrapText_currentLineNumber] := SubStr( WrapText_line[WrapText_currentLineNumber], 1, WrapText_currentPosition )
                        }
                        Else If ( WrapText_currentBreakType == "before" )
                        {
                            WrapText_line[WrapText_lineCount] := SubStr( WrapText_line[WrapText_currentLineNumber], WrapText_currentPosition )
                            WrapText_line[WrapText_currentLineNumber] := SubStr( WrapText_line[WrapText_currentLineNumber], 1, WrapText_currentPosition - 1 )
                        }
                        Else ; WrapText_currentBreakType == "eat"
                        {
                            WrapText_line[WrapText_lineCount] := SubStr( WrapText_line[WrapText_currentLineNumber], WrapText_currentPosition + 1 )
                            WrapText_line[WrapText_currentLineNumber] := SubStr( WrapText_line[WrapText_currentLineNumber], 1, WrapText_currentPosition - 1 )
                        }

                        Break
                    }

                    WrapText_currentPosition -= 1
                }
            }
        }
    }

    Return
}

; Recombines the text into a single variable
WrapText_CombineLines:
{
    wrappedText := ""
    Loop , %WrapText_lineCount%
    {
        WrapText_currentLine := WrapText_line[a_index]
        wrappedText := wrappedText "`n" WrapText_currentLine
    }
    StringTrimLeft , wrappedText , wrappedText , 1

    Return
}
Last edited by kkwillmert on 19 Sep 2017, 13:46, edited 1 time in total.
Helgef
Posts: 3298
Joined: 17 Jul 2016, 01:02
Contact:

Re: Custom text wrapping script

14 Sep 2017, 16:42

Nice, thanks for sharing :thumbup:
Maybe add msgbox % wrappedText after Gosub , WrapText in the example.
Note:
wrappedText would be: "C:\Games\`nGuild`nWars\`nGW.exe"
actually becomes C:\Games\`nGuild Wars\GW.exe

Cheers.
carno
Posts: 133
Joined: 20 Jun 2014, 16:48

Re: Custom text wrapping script

17 Sep 2017, 22:09

I would like to see the hotkey you wrote that utilizes this script. Please post that too with an example how to do it.
kkwillmert
Posts: 3
Joined: 14 Sep 2017, 15:22
GitHub: kkwillmert

Re: Custom text wrapping script

19 Sep 2017, 14:02

Helgef wrote:Maybe add msgbox % wrappedText after Gosub , WrapText in the example.
Done :-)
Helgef wrote:Note:
wrappedText would be: "C:\Games\`nGuild`nWars\`nGW.exe"
actually becomes C:\Games\`nGuild Wars\GW.exe
Thanks for catching that, that's the OP's example and I didn't actually test it out :oops: . I'm not sure if I broke his multiple character set functionality when I fixed up the script to work in current AutoHotkey versions, or if it didn't work to begin with, but unfortunately I won't have the time to figure it out for a while :-(
kkwillmert
Posts: 3
Joined: 14 Sep 2017, 15:22
GitHub: kkwillmert

Re: Custom text wrapping script

19 Sep 2017, 14:12

carno wrote:I would like to see the hotkey you wrote that utilizes this script. Please post that too with an example how to do it.
Here you go! I'm using this in Windows 10, not sure how well it will behave for other OS's.

Code: Select all

; Assuming you have the previously-posted script saved in a file named
; TextWrapper.ahk and in the same directory as the below script
#Include TextWrapper.ahk

; Copies the selected text, wraps it, and pastes it back (overwriting the selection)
^+w::
{
	Send {Ctrl Down}c{Ctrl Up}

	clipCopy  := clipboard
	windowsNL := false
	errored   := false

	if ( StrLen( clipCopy ) )
	{
		if ( InStr( clipCopy, "`r`n" ) )
		{
			windowsNL := true
			StringReplace clipCopy, clipCopy, `r`n, `n, All
		}

		firstNLPosition := InStr( clipCopy, "`n" )

		if ( firstNLPosition )
		{
			wrapWidth := firstNLPosition - 1
		}
		else
		{
			InputBox userInput, Wrap selected text, Enter the width to which lines will be wrapped (number of characters):
			
			if ErrorLevel
			{
				errored := true
				TrayTip AHK, Text wrap canceled.
			}
			else
			{
				if userInput is integer
				{
					wrapWidth := Abs( userInput )
				}
				else
				{
					errored := true
					TrayTip AHK, %userInput% is an invalid width., 5, 3
				}
			}
		}

		if ( !errored )
		{
			;;; call gosub for wrapping ;;;
			wrappedText := "" ; output arg

			textToWrap           := clipCopy
			maxCharactersPerLine := wrapWidth
			breakType            := "eat"
			breakCharacterSet    := Object()
			breakCharacterSet[1] := " "
			breakCharacterSet[2] := ""
			Gosub , WrapText
			;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

			if ( StrLen( wrappedText ) )
			{
				clipCopy := wrappedText

				if ( windowsNL )
				{
					StringReplace clipCopy, clipCopy, `n, `r`n, All
				}

				clipboard := clipCopy
				Send {Ctrl Down}v{Ctrl Up}
			}
			else
			{
				TrayTip AHK, Text wrap failed., 5, 3
			}
		}
	}
}
return
So with this hotkey set up, you could highlight all of this text...

Code: Select all

/* **************************************************************************************************

This is a code header comment:

This is a really long purpose that has lots of text and stuff and is going to be way super long so that i will want it to wrap because i am writing an autohotkey for wrapping text and in order to wrap text i need some super long text to test the routine on because i need to know if it's actually working the way i want or not and stuff.

************************************************************************************************** */
...then hit Ctrl + Shift + W, and it would paste over it with this text:

Code: Select all

/* **************************************************************************************************

This is a code header comment:

This is a really long purpose that has lots of text and stuff and is going to be way super long so
that i will want it to wrap because i am writing an autohotkey for wrapping text and in order to wrap
text i need some super long text to test the routine on because i need to know if it's actually
working the way i want or not and stuff.

************************************************************************************************** */
In this type of comment format, you can also use the hotkey to re-do the wrapping on the text after you make an edit.

Return to “Scripts and Functions”

Who is online

Users browsing this forum: arcticir, kczx3 and 15 guests