<scriptlet>

<implements type="Automation" id="dispatcher">
	<property name="PluginEvent">
		<get/>
	</property>
	<property name="PluginDescription">
		<get/>
	</property>
	<property name="PluginExtendedProperties">
		<get/>
	</property>
	<property name="PluginFileFilters">
		<get/>
	</property>
	<property name="PluginArguments">
		<get/>
		<put/>
	</property>
	<property name="PluginVariables">
		<get/>
		<put/>
	</property>
	<method name="ExecFilterCommand"/>
	<method name="MakeUpper"/>
	<method name="MakeLower"/>
	<method name="RemoveDuplicates"/>
	<method name="CountDuplicates"/>
	<method name="SortAscending"/>
	<method name="SortDescending"/>
	<method name="ReverseColumns"/>
	<method name="ReverseLines"/>
	<method name="SelectColumns"/>
	<method name="SelectLines"/>
	<method name="Replace" internalName="ReplaceText"/>
	<method name="Tokenize"/>
	<method name="Trim"/>
</implements>

<script language="VBS">
Option Explicit

Const WinMergeRegKeyPath = "HKCU\Software\Thingamahoochie\WinMerge\"
Const PluginRegKeyPath = "HKCU\Software\Thingamahoochie\WinMerge\Plugins\editor addin.sct\"

Dim wsh: Set wsh = CreateObject("WScript.Shell")
Dim arguments: arguments = ""
Dim variables: variables = Array()

Function get_PluginEvent()
	get_PluginEvent = "EDITOR_SCRIPT"
End Function

Function get_PluginDescription()
	get_PluginDescription = "Basic text functions for the context menu"
End Function

Function get_PluginFileFilters()
	get_PluginFileFilters= ".*"
End Function

Function get_PluginExtendedProperties()
	get_PluginExtendedProperties = _
	"GenerateUnpacker;" & _
	"MakeUpper.MenuCaption=Make Uppercase;" & _
	"MakeUpper.Description=Make characters uppercase;" & _
	"MakeLower.MenuCaption=Make Lowercase;" & _
	"MakeLower.Description=Make characters lowercase;" & _
	"RemoveDuplicates.MenuCaption=Remove Duplicate Lines;" & _
	"RemoveDuplicates.Description=Remove duplicate lines;" & _
	"CountDuplicates.MenuCaption=Count Duplicate Lines;" & _
	"CountDuplicates.Description=Count duplicate lines;" & _
	"SortAscending.MenuCaption=Sort Lines Ascending;" & _
	"SortAscending.Description=Sort lines ascending;" & _
	"SortDescending.MenuCaption=Sort Lines Descending;" & _
	"SortDescending.Description=Sort lines descending;" & _
	"ExecFilterCommand.MenuCaption=Apply Filter Command...;" & _
	"ExecFilterCommand.Description=Apply filter command. " & vbCrLf & _
		"Usage: ExecFilterCommand COMMAND" & vbCrLf & _
		"  COMMAND - command to execute. %1 in the command is replaced with the filename.;" & _
	"ExecFilterCommand.ArgumentsRequired;" & _
	"Tokenize.MenuCaption=Tokenize...;" & _
	"Tokenize.Description=Tokenize selection. " & vbCrLf & _
		"Usage: Tokenize PATTERNS" & vbCrLf & _
		"  PATTERNS - regular expression for tokenizing. (e.g. [^\w]+);" & _
	"Tokenize.ArgumentsRequired;" & _
	"Tokenize.Arguments=[^\w]+;" & _
	"Trim.MenuCaption=Trim Spaces;" & _
	"Trim.Description=Trim spaces;" & _
	"SelectColumns.MenuCaption=Select Columns...;" & _
	"SelectColumns.Description=Select some columns." & vbCrLf & _
		"Usage: SelectColumns RANGES" & vbCrLf & _
		"   or: SelectColumns [-v] [-i] [-g] -e PATTERNS" & vbCrLf & _
		"  RANGES   - list of column ranges to select. (e.g. -3,5-10,30-)" & vbCrLf & _
		"  PATTERNS - regular expression" & vbCrLf & _
		"  -v - select non-matching columns" & vbCrLf & _
		"  -i - ignore case" & vbCrLf & _
		"  -g - enable global flag" & vbCrLf & _
		"  -e - use PATTERNS for matching;" & _
	"SelectColumns.ArgumentsRequired;" & _
	"SelectLines.MenuCaption=Select Lines...;" & _
	"SelectLines.Description=Select some lines." & vbCrLf & _
		"Usage: SelectLines RANGES" & vbCrLf & _
		"   or: SelectLines [-v] [-i] -e PATTERNS" & vbCrLf & _
		"  RANGES   - list of line ranges to select. (e.g. -3,5-10,30-)" & vbCrLf & _
		"  PATTERNS - regular expression" & vbCrLf & _
		"  -v - select non-matching lines" & vbCrLf & _
		"  -i - ignore case" & vbCrLf & _
		"  -e - use PATTERNS for matching;" & _
	"SelectLines.ArgumentsRequired;" & _
	"ReverseColumns.MenuCaption=Reverse Columns;" & _
	"ReverseColumns.Description=Reverse columns;" & _
	"ReverseLines.MenuCaption=Reverse Lines;" & _
	"ReverseLines.Description=Reverse lines;" & _
	"Replace.MenuCaption=Replace...;" & _
	"Replace.Description=Replace text with another text." & vbCrLf & _
		"Usage: Replace [-i] [-e] FIND REPLACE" & vbCrLf & _
		"  FIND    - text to find" & vbCrLf & _
		"  REPLACE - text to replace" & vbCrLf & _
		"  -i - ignore case (only for -e)" & vbCrLf & _
		"  -e - treat the specified text as a regular expression;" & _
	"Replace.ArgumentsRequired;"
End Function

Function get_PluginArguments()
	get_PluginArguments = arguments
End Function

Sub put_PluginArguments(NewValue)
	arguments = NewValue
End Sub

Function get_PluginVariables()
	get_PluginVariables = Join(variables, Chr(0))
End Function

Sub put_PluginVariables(NewValue)
	variables = Split(NewValue, Chr(0))
End Sub

Function regRead(Key, DefaultValue)
	regRead = DefaultValue
	On Error Resume Next
	regRead = wsh.RegRead(Key)
End Function

Function ReplaceVariables(str)
	Dim newstr
	Dim pos
	Dim foundpos
	Dim ch
	pos = 1
	Do While True
		foundpos = InStr(pos, str, "%")
		If foundpos > 0 Then
			ch = Mid(str, foundpos + 1, 1)
			If ch = "%" Then
				newstr = newstr & "%"
				pos = foundpos + 2
			ElseIf IsNumeric(ch) Then
				newstr = newstr & Mid(str, pos, foundpos - pos)
				If CLng(ch) <= SafeUBound(variables) Then
					newstr = newstr & variables(CLng(ch))
				End If
				pos = foundpos + 2
			Else
				newstr = newstr & Mid(str, pos, foundpos - pos + 1)
				pos = foundpos + 1
			End If
		Else
			newstr = newstr & Mid(str, pos)
			Exit Do
		End If
	Loop
	ReplaceVariables = newstr
End Function

Function IsFirstArgumentEmpty()
	IsFirstArgumentEmpty = (Trim(arguments) = "")
End Function

Function SafeUBound(ary)
	SafeUBound = -1
	On Error Resume Next
	SafeUBound = UBound(ary)
End Function

Function ParseArguments(args)
	Dim ary()
	Dim ch
	Dim inQuotes
	Dim token
	Dim argsLen
	Dim i, j
	argsLen = Len(args)
	For i = 1 To argsLen
		ch = Mid(args, i, 1)
		If Not inQuotes Then
			If ch = " " Or ch = vbTab Then
				If len(token) > 0 Then
					ReDim Preserve ary(j)
					ary(j) = token
					j = j + 1
					token = ""
				End If
			ElseIf ch = """" Then
				inQuotes = True
			Else
				token = token & ch
			End If
		Else
			If ch = """" Then
				If i + 1 <= argsLen Then
					If Mid(args, i + 1, 1) = """" Then
						token = token & ch
					End if
				End If
				inQuotes = False 
			Else
				token = token & ch
			End If
		End If
	Next
	If Len(token) > 0 Then
		ReDim Preserve ary(j)
		ary(j) = token
		j = j + 1
		token = ""
	End If
	ParseArguments = ary 
End Function

Function ParseRanges(rangeText)
	Dim i, j
	Dim ary, ary2, aryRanges()
	Dim rangeFrom, rangeTo
	ary = Split(rangeText, ",")
	j = 0
	For i = 0 To SafeUBound(ary)
		ary2 = Split(ary(i), "-")
		rangeFrom = ""
		If SafeUBound(ary2) >= 0 Then
			rangeFrom = Trim(ary2(0))
		End If
		rangeTo = rangeFrom
		If SafeUBound(ary2) > 0 Then
			rangeTo = Trim(ary2(1))
		End If
		If IsNumeric(rangeFrom) Or IsNumeric(rangeTo) Then
			If Not IsNumeric(rangeFrom) Then rangeFrom = 1
			If Not IsNumeric(rangeTo)   Then rangeTo   = 1000000000
			If CDbl(rangeFrom) < 1 Or CDbl(rangeFrom) > 1000000000 Then rangeFrom = 1
			If CDbl(rangeTo)   < 1 Or CDbl(rangeTo)   > 1000000000 Then rangeTo   = 1
			If CDbl(rangeFrom) > CDbl(rangeTo) Then rangeTo = rangeFrom
			Redim preserve aryRanges(j)
			aryRanges(j) = Array(CLng(rangeFrom), CLng(rangeTo))
			j = j + 1
		End If
	Next
	ParseRanges = aryRanges
End Function

Function ParseSelectColumnsLinesArguments(args, invert)
	Dim rangesOrRegExps()
	Dim argAry
	Dim i, j
	Dim re
	Dim isOption
	Set re = Nothing
	argAry = ParseArguments(args)
	invert = False
	For i = 0 To SafeUBound(argAry)
		isOption = False
		If Len(argAry(i)) >= 2 Then
			Select Case Left(argAry(i), 1)
			Case "-", "/"
				Select Case Mid(argAry(i), 2, 1)
				Case "e", "i", "g"
					isOption = True
					If re Is Nothing Then
						Set re = New RegExp
						Redim Preserve rangesOrRegExps(j)
						Set rangesOrRegExps(j) = re
						j = j + 1
					End If
					If InStr(argAry(i), "i") > 0 Then
						re.IgnoreCase = True
					End If
					If InStr(argAry(i), "g") > 0 Then
						re.Global  = True
					End If
				Case "v"
					isOption = True
					invert = True
				End Select
			End Select
		End If
		If Not isOption Then
			If Not (re Is Nothing) Then
				re.Pattern = argAry(i)
				Set re = Nothing
			Else
				Redim Preserve rangesOrRegExps(j)
				rangesOrRegExps(j) = ParseRanges(argAry(i))
				If SafeUBound(rangesOrRegExps(j)) < 0 Then
					Set rangesOrRegExps(j) = New RegExp
					rangesOrRegExps(j).Pattern = argAry(i)
				End If
				j = j + 1
			End If
		End If
	Next
	ParseSelectColumnsLinesArguments = rangesOrRegExps
End Function

Function ParseReplaceArguments(args, regex, ignoreCase)
	Dim patterns()
	Dim argAry
	Dim i, j
	Dim re
	Dim isOption
	Set re = Nothing
	argAry = ParseArguments(args)
	regex = False
	ignoreCase = False
	For i = 0 To SafeUBound(argAry)
		isOption = False
		If Len(argAry(i)) >= 2 Then
			Select Case Left(argAry(i), 1)
			Case "-", "/"
				Select Case Mid(argAry(i), 2, 1)
				Case "e", "i"
					isOption = True
					regex = True
					If InStr(argAry(i), "i") > 0 Then
						ignoreCase = True
					End If
				End Select
			End Select
		End If
		If Not isOption Then
			Redim Preserve patterns(j)
			patterns(j) = argAry(i)
			j = j + 1
		End If
	Next
	ParseReplaceArguments = patterns
End Function

Function Unescape(text)
	Dim textLen
	Dim i
	Dim ch

	textLen = Len(text)
	i = 1
	Do While i <= textLen
		ch = Mid(text, i, 1)
		Select Case ch
		Case "\"
			If i <= textLen - 1 Then
				i = i + 1
				ch = Mid(text, i, 1)
				Select Case ch
				Case "a"
					Unescape = Unescape & Chr(&H07)
				Case "b"
					Unescape = Unescape & Chr(&H08)
				Case "t"
					Unescape = Unescape & Chr(&H09)
				Case "n"
					Unescape = Unescape & Chr(&H0A)
				Case "v"
					Unescape = Unescape & Chr(&H0B)
				Case "f"
					Unescape = Unescape & Chr(&H0C)
				Case "r"
					Unescape = Unescape & Chr(&H0D)
				Case "\"
					Unescape = Unescape & "\"
				Case Else
					Unescape = Unescape & "\" & ch
				End Select
			Else
				Unescape = Unescape & ch
			End If
		Case Else
			Unescape = Unescape & ch
		End Select
		i = i + 1
	Loop
End Function

Function SplitLines(text, eol)
	Dim re, matches
	Set re = New RegExp
	re.Global = False
	re.IgnoreCase = False
	re.Pattern = "\r\n|\n|\r"
	Set matches = re.Execute(text)
	If matches.Count > 0 Then
		eol = matches(0).Value
	End If
	SplitLines = Split(text, eol)
End Function

' transformation functions
Function MakeUpper(Text)
	MakeUpper = UCase(Text)
End Function

Function MakeLower(Text)
	MakeLower = LCase(Text)
End Function

Function ExecFilterCommand(Text)
	Dim cmd
	If IsFirstArgumentEmpty() Then
		cmd = regRead(PluginRegKeyPath & "ExecFilterCommand", "")
		cmd = InputBox("Enter filter command", "ExecFilterCommand", cmd)
		If cmd <> "" Then
			wsh.RegWrite PluginRegKeyPath & "ExecFilterCommand", cmd
		End If
		cmd = ReplaceVariables(cmd)
	Else
		cmd = arguments
	End If
	If cmd = "" Then
		Err.Raise 30001, , "Canceled"
		Exit Function
	End If

	On Error Resume Next

	Dim path
	path = wsh.ExpandEnvironmentStrings("%TEMP%\_winmerge_addin_temp_.txt")

	Dim fso
	Dim ts
	Set fso = CreateObject("Scripting.FileSystemObject")
	Set ts = fso.CreateTextFile(path)
	If ts Is Nothing Then
		Exit Function
	End If
	ts.Write Text
	ts.Close

	Dim exe
	Set exe = wsh.Exec("cmd /c type """ & path & """ | " & cmd & " 2>&1")
	If exe Is Nothing Then
		MsgBox "Failed to execute the command '" & cmd & "':" & Err.Description 
		fso.DeleteFile path
		Exit Function
	End If

	ExecFilterCommand = ""
	Do Until exe.StdOut.AtEndOfStream
		ExecFilterCommand = ExecFilterCommand & exe.StdOut.ReadLine & vbCrLf
	Loop

	fso.DeleteFile path

End Function

Function SelectColumns(Text)
	Dim args, ranges, regexpStr
	Dim matches, m
	Dim lines, eol, line
	Dim i, j, k
	Dim rangesOrRegExps
	Dim invert
	If IsFirstArgumentEmpty() Then
		args = regRead(PluginRegKeyPath & "SelectColumns", "")
		args = InputBox("Enter the list of column ranges to select (e.g. -3,5-10,30-)" & vbCrLf & "or regular expression:", "SelectColumns", args)
		If args <> "" Then
			wsh.RegWrite PluginRegKeyPath & "SelectColumns", args
		End If
	Else
		args = arguments
	End If
	If args = "" Then
		Err.Raise 30001, , "Canceled"
		Exit Function
	End If
	lines = SplitLines(Text, eol)
	rangesOrRegExps = ParseSelectColumnsLinesArguments(args, invert)
	For i = 0 To SafeUBound(lines)
		line = ""
		For j = 0 To SafeUBound(rangesOrRegExps)
			If IsArray(rangesOrRegExps(j)) Then
				For k = 0 To SafeUBound(rangesOrRegExps(j))
					If rangesOrRegExps(j)(k)(0) <= Len(lines(i)) Then
						line = line & Mid(lines(i), rangesOrRegExps(j)(k)(0), rangesOrRegExps(j)(k)(1) - rangesOrRegExps(j)(k)(0) + 1)
					End If
				Next
			Else
				If Not invert Then
					Set matches = rangesOrRegExps(j).Execute(lines(i))
					For Each m In matches
						line = line & m.Value
					Next
				Else
					line = line & rangesOrRegExps(j).Replace(lines(i), "")
				End If
			End If
		Next
		lines(i) = line
	Next
	SelectColumns = Join(lines, eol)
End Function

Function SelectLines(Text)
	Dim args, ranges
	Dim matches, rangesOrRegExps
	Dim lines, newlines(), eol
	Dim i, j, k, l
	Dim max
	Dim matched
	Dim invert
	If IsFirstArgumentEmpty() Then
		args = regRead(PluginRegKeyPath & "SelectLines", "")
		args = InputBox("Enter the list of line ranges to select (e.g. -3,5-10,30-)" & vbCrLf & "or regular expression:", "SelectLines", args)
		If args <> "" Then
			wsh.RegWrite PluginRegKeyPath & "SelectLines", args
		End If
	Else
		args = arguments
	End If
	If args = "" Then
		Err.Raise 30001, , "Canceled"
		Exit Function
	End If
	lines = SplitLines(Text, eol)
	l = 0
	rangesOrRegExps = ParseSelectColumnsLinesArguments(args, invert)
	For i = 0 To SafeUBound(rangesOrRegExps)
		If IsArray(rangesOrRegExps(i)) Then
			For j = 0 To SafeUBound(rangesOrRegExps(i))
				max =rangesOrRegExps(i)(j)(1) - 1 
				If max > SafeUBound(lines) Then max = SafeUBound(lines)
				For k = rangesOrRegExps(i)(j)(0) - 1 To max
					If k <= SafeUBound(lines) Then
						ReDim Preserve newlines(l)
						newlines(l) = lines(k)
						l = l + 1
					End If
				Next
			Next
		Else
			For j = 0 To SafeUBound(lines)
				matched = rangesOrRegExps(i).Test(lines(j))
				If invert Then matched = Not matched
				If matched Then
					ReDim Preserve newlines(l)
					newlines(l) = lines(j)
					l = l + 1
				End If
			Next
		End If
	Next
	If SafeUBound(lines) >= 0 Then
		If lines(UBound(lines)) = "" Then
			If SafeUBound(newlines) >= 0 Then
				If newlines(UBound(newlines)) <> "" Then
					ReDim Preserve newlines(l)
					newlines(l) = ""
					l = l + 1
				End If
			End If
		End If
	End If
	SelectLines = Join(newlines, eol)
End Function

Function ReplaceText(Text)
	Dim args, patterns
	Dim i
	Dim re, regex
	Dim ignoreCase
	ReplaceText = Text
	If IsFirstArgumentEmpty() Then
		args = regRead(PluginRegKeyPath & "Replace", "")
		args = InputBox("Enter the search text and replacement text:", "Replace", args)
		If args <> "" Then
			wsh.RegWrite PluginRegKeyPath & "Replace", args
		End If
	Else
		args = arguments
	End If
	If args = "" Then
		Err.Raise 30001, , "Canceled"
		Exit Function
	End If
	patterns = ParseReplaceArguments(args, regex, ignoreCase)
	If regex Then
		For i = 0 To SafeUBound(patterns) Step 2
			Set re = New RegExp
			re.IgnoreCase = ignoreCase
			re.Global = True
			re.Multiline = True
			re.Pattern = patterns(i)
			If i + 1 <= UBound(patterns) Then
				ReplaceText = re.Replace(ReplaceText, Unescape(patterns(i + 1)))
			Else
				ReplaceText = re.Replace(ReplaceText, "")
			End If
		Next
	Else
		For i = 0 To SafeUBound(patterns) Step 2
			If i + 1 <= UBound(patterns) Then
				ReplaceText = Replace(ReplaceText, patterns(i), patterns(i + 1))
			Else
				ReplaceText = Replace(ReplaceText, patterns(i), "")
			End If
		Next
	End If
End Function

' port from WinMerge2011
Function Tokenize(Text)
	Dim pattern
	If IsFirstArgumentEmpty() Then
		pattern = regRead(PluginRegKeyPath & "Tokenize", "")
		pattern = InputBox("Enter regex to tokenize (e.g. [^\w]+):", "Tokenize", pattern)
		If pattern <> "" Then
			wsh.RegWrite PluginRegKeyPath & "Tokenize", pattern
		End If
	Else
		pattern = arguments
	End If
	If pattern = "" Then
		Err.Raise 30001, , "Canceled"
		Exit Function
	End If
	Dim re
	Set re = New RegExp
	re.Global = True
	re.IgnoreCase = True
	re.Pattern = pattern
	Tokenize = re.Replace(Text, vbCrLf)
End Function

</script>

<script language="JScript">

function RemoveDuplicates(Text) {
	var eol = Text.match(/\r\n|\n|\r/);
	var lines = Text.split(eol);
	var newlines = new Array();
	var dic = {};
	var lasteol = false;

	if (!lines[lines.length - 1]) {
		lines.pop();
		lasteol = true;
	}
	for (var i = 0, j = 0; i < lines.length; i++) {
		var line = lines[i];
		if (typeof dic[line] === 'undefined') {
			dic[line] = 1;
			newlines[j] = line;
			j++;
		}
	}
	if (lasteol)
		newlines.push("");
	return newlines.join(eol);
}

function CountDuplicates(Text) {
	var eol = Text.match(/\r\n|\n|\r/);
	var lines = Text.split(eol);
	var newlines = new Array();
	var dic = {};

	if (!lines[lines.length - 1]) {
		lines.pop();
	}
	
	for (var i = 0; i < lines.length; i++) {
		var line = lines[i];
		if (typeof dic[line] === 'undefined') {
			dic[line] = 1;
			newlines[j] = line;
			j++;
		} else {
			dic[line]++;
		}
	}
	var j = 0;
	for (var line in dic) {
		newlines[j] = line + '\t' + dic[line];
		j++;
	}
	if (eol)
		newlines[j] = "";
	return newlines.join(eol);
}

function ReverseColumns(Text) {
	var eol = Text.match(/\r\n|\n|\r/);
	var lines = Text.split(eol);
	var lasteol = false;
	if (!lines[lines.length - 1]) {
		lines.pop();
		lasteol = true;
	}
	for (var i = 0; i < lines.length; i++) {
		var line = lines[i];
		var newline = "";
		for (var j = 0; j < line.length; j++)
			newline += line.substr(line.length - j - 1, 1);
		lines[i] = newline;
	}
	if (lasteol)
		lines.push("");
	return lines.join(eol);
}

function ReverseLines(Text) {
	var eol = Text.match(/\r\n|\n|\r/);
	var lines = Text.split(eol);
	var lasteol = false;
	if (!lines[lines.length - 1]) {
		lines.pop();
		lasteol = true;
	}
	var m = parseInt(lines.length / 2);
	for (var i = 0; i < m; i++) {
		var line = lines[i];
		lines[i] = lines[lines.length - i - 1]
		lines[lines.length - i - 1] = line;
	}
	if (lasteol)
		lines.push("");
	return lines.join(eol);
}

function SortAscending(Text) {
	var eol = Text.match(/\r\n|\n|\r/);
	var lines = Text.split(eol);

	if (lines.length == 1) {
		return Text;
	} else if (lines[lines.length - 1] == "") {
		lines.pop();
		return lines.sort().join(eol) + eol;
	} else {
		return lines.sort().join(eol);
	}
}

function SortDescending(Text) {
	var eol = Text.match(/\r\n|\n|\r/);
	var lines = Text.split(eol);

	if (lines.length == 1) {
		return Text;
	} else if (lines[lines.length - 1] == "") {
		lines.pop();
		lines.sort(function(a, b) { return a < b ? 1 : -1; });
		return lines.join(eol) + eol;
	} else {
		return lines.sort(function(a, b) { return a < b ? 1 : -1; }).join(eol);
	}
}

function Trim(Text) {
	var eol = Text.match(/\r\n|\n|\r/);
	var lines = Text.split(eol);
	for (var i = 0; i < lines.length; i++) {
		lines[i] = lines[i].replace(/^\s+|\s+$/g, "");
	}
	return lines.join(eol);
}

</script>

</scriptlet>
