// http://akelpad.sourceforge.net/forum/viewtopic.php?p=11121#11121
// Version: 2015-09-19
// Author: KDJ
//
// *** Sort list by using Format plugin ***
//
// Usage:
//   Call("Scripts::Main", 1, "SortList.js")
//
// Shortcut keys in dialog box:
//   Enter - Sort
//   Esc   - Close
//   Shift+Alt+Right (Left, Down, Up, End, Home, PgDn, PgUp) - move dialog box.
//
// Remarks:
//   In each line, sorts separately.
//   If you do not specify a separator, it will be sorted single characters.

var sTxtCaption  = "行内列表排序";
var sTxtSep      = "分隔符:";
var sTxtTab      = "\\t = 制表符";
var sTxtSortMet  = "排序方法";
var sTxtDesc     = "降序(&D)";
var sTxtIgCase   = "忽略大小写(&I)";
var sTxtString   = "字串(&S)";
var sTxtNum      = "数字顺序(&N)";
var sTxtNoSort   = "不排序，仅";
var sTxtReverse  = "反向排列元素(&O)";
var sTxtRemoDup  = "删除重复元素(&M)";
var sTxtExtrDup  = "提取重复元素(&X)";
var sTxtExtrUni  = "提取唯一元素(&C)";
var sTxtCopyToCB = "复制结果到剪贴板，而不是替换文本(&T)";
var sTxtSort     = "排序";
var sTxtUndo     = "撤消(&U)";
var sTxtRedo     = "恢复(&R)";
var sTxtNoSep    = "选定的文本不包含分隔符。";

var DT_DWORD    = 3;
var BM_SETCHECK = 241;

var oSys       = AkelPad.SystemFunction();
var hMainWnd   = AkelPad.GetMainWnd();
var hEditWnd   = AkelPad.GetEditWnd();
var hGuiFont   = oSys.Call("Gdi32::GetStockObject", 17 /*DEFAULT_GUI_FONT*/);
var hInstDLL   = AkelPad.GetInstanceDll();
var sClassName = "AkelPad::Scripts::" + WScript.ScriptName + "::" + hInstDLL;
var sSep       = ";";
var bTab       = 1;
var bDesc      = 0;
var bIgCase    = 1;
var nMethod    = 1;
var nNoSort    = 0;
var bCopyToCB  = 0;
var nUndoLimit = 0;
var nRedoLimit = 0;
var hWndDlg;
var lpBuffer;
var bIsReturn;
var hFocus;
var nWndPosX;
var nWndPosY;

ReadWriteIni(0);

var aWnd       = [];
var IDGROUP1   = 1000;
var IDSTATSEP  = 1001;
var IDSEP      = 1002;
var IDTAB      = 1003;
var IDGROUP2   = 1004;
var IDDESC     = 1005;
var IDIGCASE   = 1006;
var IDGROUP3   = 1007;
var IDSTRING   = 1008;
var IDNUM      = 1009;
var IDGROUP4   = 1010;
var IDREVERSE  = 1011;
var IDREMODUP  = 1012;
var IDEXTRDUP  = 1013;
var IDEXTRUNI  = 1014;
var IDCOPYTOCB = 1015;
var IDSORT     = 1016;
var IDUNDO     = 1017;
var IDREDO     = 1018;

var CLASS   = 0;
var WND     = 1;
var EXSTYLE = 2;
var STYLE   = 3;
var X       = 4;
var Y       = 5;
var W       = 6;
var H       = 7;
var TXT     = 8;

//0x50000000 - WS_VISIBLE|WS_CHILD
//0x50000007 - WS_VISIBLE|WS_CHILD|BS_GROUPBOX
//0x50000009 - WS_VISIBLE|WS_CHILD|BS_AUTORADIOBUTTON
//0x50010000 - WS_VISIBLE|WS_CHILD|WS_TABSTOP
//0x50010003 - WS_VISIBLE|WS_CHILD|WS_TABSTOP|BS_AUTOCHECKBOX
//0x50010080 - WS_VISIBLE|WS_CHILD|WS_TABSTOP|ES_AUTOHSCROLL
//Windows              CLASS, WND, EXSTYLE,      STYLE,   X,   Y,   W,   H, TXT
aWnd[IDGROUP1  ] = ["BUTTON",   0,       0, 0x50000007,  10,  10, 170,  75, ""];
aWnd[IDSTATSEP ] = ["STATIC",   0,       0, 0x50000000,  20,  30,  60,  13, sTxtSep];
aWnd[IDSEP     ] = ["EDIT",     0,   0x200, 0x50010080,  80,  30,  90,  20, sSep];
aWnd[IDTAB     ] = ["BUTTON",   0,       0, 0x50010003,  80,  60,  90,  16, sTxtTab];
aWnd[IDGROUP2  ] = ["BUTTON",   0,       0, 0x50000007,  10,  95, 260,  65, sTxtSortMet];
aWnd[IDDESC    ] = ["BUTTON",   0,       0, 0x50010003,  40, 115,  90,  16, sTxtDesc];
aWnd[IDIGCASE  ] = ["BUTTON",   0,       0, 0x50010003,  40, 135,  90,  16, sTxtIgCase];
aWnd[IDGROUP3  ] = ["BUTTON",   0,       0, 0x50000007, 150,  95, 120,  65, ""];
aWnd[IDSTRING  ] = ["BUTTON",   0,       0, 0x50000009, 175, 115,  90,  16, sTxtString];
aWnd[IDNUM     ] = ["BUTTON",   0,       0, 0x50000009, 175, 135,  90,  16, sTxtNum];
aWnd[IDGROUP4  ] = ["BUTTON",   0,       0, 0x50000007,  10, 170, 260, 105, sTxtNoSort];
aWnd[IDREVERSE ] = ["BUTTON",   0,       0, 0x50010003,  60, 190, 200,  16, sTxtReverse];
aWnd[IDREMODUP ] = ["BUTTON",   0,       0, 0x50010003,  60, 210, 200,  16, sTxtRemoDup];
aWnd[IDEXTRDUP ] = ["BUTTON",   0,       0, 0x50010003,  60, 230, 200,  16, sTxtExtrDup];
aWnd[IDEXTRUNI ] = ["BUTTON",   0,       0, 0x50010003,  60, 250, 200,  16, sTxtExtrUni];
aWnd[IDCOPYTOCB] = ["BUTTON",   0,       0, 0x50010003,  20, 285, 240,  16, sTxtCopyToCB];
aWnd[IDSORT    ] = ["BUTTON",   0,       0, 0x50010000, 190,  14,  80,  23, sTxtSort];
aWnd[IDUNDO    ] = ["BUTTON",   0,       0, 0x50010000, 190,  39,  80,  23, sTxtUndo];
aWnd[IDREDO    ] = ["BUTTON",   0,       0, 0x50010000, 190,  64,  80,  23, sTxtRedo];

if (hEditWnd)
{
  AkelPad.SendMessage(hEditWnd, 3128 /*AEM_UPDATESEL*/, 0 /*AESELT_COLUMNOFF*/, 0);
  if (AkelPad.GetSelStart() == AkelPad.GetSelEnd())
    AkelPad.SetSel(0, -1);

  if (AkelPad.WindowRegisterClass(sClassName))
  {
    if (lpBuffer = AkelPad.MemAlloc(256 * _TSIZE))
    {
      hWndDlg = oSys.Call("User32::CreateWindowEx" + _TCHAR,
                  0,               //dwExStyle
                  sClassName,      //lpClassName
                  sTxtCaption,     //lpWindowName
                  0x90CA0000,      //WS_VISIBLE|WS_POPUP|WS_CAPTION|WS_SYSMENU|WS_MINIMIZEBOX
                  0,               //x
                  0,               //y
                  285,             //nWidth
                  340,             //nHeight
                  hMainWnd,        //hWndParent
                  0,               //ID
                  hInstDLL,        //hInstance
                  DialogCallback); //Script function callback. To use it class must be registered by WindowRegisterClass.
      if (hWndDlg)
      {
        //Disable main window, to make dialog modal
        oSys.Call("User32::EnableWindow", hMainWnd, false);

        //Message loop
        AkelPad.WindowGetMessage();
      }
      AkelPad.MemFree(lpBuffer);
    }
    AkelPad.WindowUnregisterClass(sClassName);
  }
}

function DialogCallback(hWnd, uMsg, wParam, lParam)
{
  var i, nLowParam;

  if (uMsg == 1) //WM_CREATE
  {
    for (i = 1000; i < aWnd.length; ++i)
    {
      aWnd[i][WND] = oSys.Call("User32::CreateWindowEx" + _TCHAR,
                       aWnd[i][EXSTYLE], //dwExStyle
                       aWnd[i][CLASS],   //lpClassName
                       0,                //lpWindowName
                       aWnd[i][STYLE],   //dwStyle
                       aWnd[i][X],       //x
                       aWnd[i][Y],       //y
                       aWnd[i][W],       //nWidth
                       aWnd[i][H],       //nHeight
                       hWnd,             //hWndParent
                       i,                //ID
                       hInstDLL,         //hInstance
                       0);               //lpParam
      SetWindowFontAndText(aWnd[i][WND], hGuiFont, aWnd[i][TXT]);
    }

    //Set limit edit text
    AkelPad.SendMessage(aWnd[IDSEP][WND], 197 /*EM_LIMITTEXT*/, 20, 0);

    CheckButtons();
    CheckUndoRedo();

    //Set window position
    if ((nWndPosX == undefined) || (nWndPosY == undefined))
      MoveWindow(hMainWnd, hWnd, "RT");
    else
      MoveWindow(hMainWnd, hWnd, [nWndPosX, nWndPosY]);
  }

  else if (uMsg == 7) //WM_SETFOCUS
  {
    oSys.Call("User32::SetFocus", aWnd[IDSEP][WND]);
    AkelPad.SendMessage(aWnd[IDSEP][WND], 177 /*EM_SETSEL*/, 0, -1);
  }

  else if (uMsg == 256) //WM_KEYDOWN
  {
    if (wParam == 27) //VK_ESCAPE
      oSys.Call("User32::PostMessage" + _TCHAR, hWndDlg, 16 /*WM_CLOSE*/, 0, 0);
    else if (wParam == 13) //VK_RETURN
    {
      if (! bIsReturn)
      {
        hFocus = oSys.Call("User32::GetFocus");
        if ((hFocus != aWnd[IDSORT][WND]) && (hFocus != aWnd[IDUNDO][WND]) && (hFocus != aWnd[IDREDO][WND]))
          oSys.Call("User32::PostMessage" + _TCHAR, hWnd, 273 /*WM_COMMAND*/, IDSORT, 0);
      }
    }
  }

  else if (uMsg == 257) //WM_KEYUP
    bIsReturn = 0;

  else if ((uMsg == 260) /*WM_SYSKEYDOWN*/ &&
           (oSys.Call("User32::GetAsyncKeyState", 0xA0 /*VK_LSHIFT*/)))
  {
    if (wParam == 0x27) //VK_RIGHT
      MoveWindow(hMainWnd, hWnd, "R");
    else if (wParam == 0x25) //VK_LEFT
      MoveWindow(hMainWnd, hWnd, "L");
    else if (wParam == 0x28) //VK_DOWN
      MoveWindow(hMainWnd, hWnd, "D");
    else if (wParam == 0x26) //VK_UP
      MoveWindow(hMainWnd, hWnd, "U");
    else if (wParam == 0x23) //VK_END
      MoveWindow(hMainWnd, hWnd, "E");
    else if (wParam == 0x24) //VK_HOME
      MoveWindow(hMainWnd, hWnd, "H");
    else if (wParam == 0x22) //VK_NEXT
      MoveWindow(hMainWnd, hWnd, "B");
    else if (wParam == 0x21) //VK_PRIOR
      MoveWindow(hMainWnd, hWnd, "T");
  }

  else if (uMsg == 273) //WM_COMMAND
  {
    nLowParam = LoWord(wParam);

    if ((nLowParam >= IDTAB) && (nLowParam <= IDCOPYTOCB))
    {
      CheckButtons(nLowParam);
    }

    else if (nLowParam == IDSORT)
    {
      if (SortList())
        CheckUndoRedo(nLowParam);
    }

    else if (nLowParam == IDUNDO)
    {
      bIsReturn = 1;
      CheckUndoRedo(nLowParam);
    }

    else if (nLowParam == IDREDO)
    {
      bIsReturn = 1;
      CheckUndoRedo(nLowParam);
    }
  }

  else if (uMsg == 16) //WM_CLOSE
  {
    ReadWriteIni(1);

    //Enable main window
    oSys.Call("User32::EnableWindow", hMainWnd, true);
    //Destroy dialog
    oSys.Call("User32::DestroyWindow", hWnd);
  }

  else if (uMsg == 2) //WM_DESTROY
  {
    //Exit message loop
    oSys.Call("User32::PostQuitMessage", 0);
  }

  else
    SetDefID(hWnd);

  return 0;
}

function SetWindowFontAndText(hWnd, hFont, sText)
{
  AkelPad.SendMessage(hWnd, 48 /*WM_SETFONT*/, hFont, true);
  oSys.Call("User32::SetWindowText" + _TCHAR, hWnd, sText);
}

function MoveWindow(hWndParent, hWnd, vAction)
{
  var aRectParent;
  var aRect;
  var nX;
  var nY;

  if (! hWndParent)
    hWndParent=oSys.Call("User32::GetDesktopWindow");

  aRectParent = GetWindowPos(hWndParent);
  aRect       = GetWindowPos(hWnd);

  nX = aRect.left;
  nY = aRect.top;

  if (vAction == "R") //Move right
    nX = aRect.left + ((aRect.left < aRectParent.right - 50) ? 20 : 0);
  else if (vAction == "L") //Move left
    nX = aRect.left - ((aRect.right > aRectParent.left + 50) ? 20 : 0);
  else if (vAction == "D") //Move down
    nY = aRect.top + ((aRect.top < aRectParent.bottom - 50) ? 20 : 0);
  else if (vAction == "U") //Move up
    nY = aRect.top - ((aRect.bottom > aRectParent.top + 50) ? 20 : 0);
  else if (vAction == "E") //Move end (right)
    nX = aRect.left + (aRectParent.right - aRect.right);
  else if (vAction == "H") //Move home (left)
    nX = aRect.left + (aRectParent.left - aRect.left);
  else if (vAction == "B") //Move bottom
    nY = aRect.top + (aRectParent.bottom - aRect.bottom);
  else if (vAction == "T") //Move top
    nY = aRect.top + (aRectParent.top - aRect.top);
  else if (vAction == "C") //Center window
  {
    nX = aRectParent.left + ((aRectParent.right  - aRectParent.left) / 2 - (aRect.right  - aRect.left) / 2);
    nY = aRectParent.top  + ((aRectParent.bottom - aRectParent.top)  / 2 - (aRect.bottom - aRect.top)  / 2);
  }
  else if (vAction == "LT") //Move left top
  {
    nX = aRectParent.left;
    nY = aRectParent.top;
  }
  else if (vAction == "RT") //Move right top
  {
    nX = aRect.left + (aRectParent.right - aRect.right);
    nY = aRectParent.top;
  }
  else if (vAction == "LB") //Move left bottom
  {
    nX = aRectParent.left;
    nY = aRect.top + (aRectParent.bottom - aRect.bottom);
  }
  else if (vAction == "RB") //Move right bottom
  {
    nX = aRect.left + (aRectParent.right - aRect.right);
    nY = aRect.top + (aRectParent.bottom - aRect.bottom);
  }
  else
  {
    nX = vAction[0];
    nY = vAction[1];
  }

  oSys.Call("User32::SetWindowPos", hWnd, 0, nX, nY, 0, 0, 0x15 /*SWP_NOZORDER|SWP_NOACTIVATE|SWP_NOSIZE*/);
}

function GetWindowPos(hWnd)
{
  var lpRect = AkelPad.MemAlloc(16) //sizeof(RECT);
  var aRect  = [];

  oSys.Call("User32::GetWindowRect", hWnd, lpRect);

  aRect.left   = AkelPad.MemRead(_PtrAdd(lpRect,  0), DT_DWORD);
  aRect.top    = AkelPad.MemRead(_PtrAdd(lpRect,  4), DT_DWORD);
  aRect.right  = AkelPad.MemRead(_PtrAdd(lpRect,  8), DT_DWORD);
  aRect.bottom = AkelPad.MemRead(_PtrAdd(lpRect, 12), DT_DWORD);
  AkelPad.MemFree(lpRect);

  return aRect;
}

function LoWord(nParam)
{
  return (nParam & 0xFFFF);
}

function SetDefID(hWnd)
{
  var nID    = 1000;
  var hFocus = oSys.Call("User32::GetFocus");

  while ((nID < aWnd.length) && (hFocus != aWnd[nID][WND]))
    ++nID;

  if ((nID != IDUNDO) && (nID != IDREDO))
    nID = IDSORT;

  oSys.Call("User32::DefDlgProc" + _TCHAR, hWnd, 1025 /*DM_SETDEFID*/, nID, 0);
}

function ReadWriteIni(bWrite)
{
  var oFSO     = new ActiveXObject("Scripting.FileSystemObject");
  var sIniName = WScript.ScriptFullName.substring(0, WScript.ScriptFullName.lastIndexOf(".")) + ".ini";
  var aRect;
  var oFile;
  var sIniTxt;

  if (bWrite)
  {
    aRect = GetWindowPos(hWndDlg);

    oSys.Call("User32::GetWindowText" + _TCHAR, aWnd[IDSEP][WND], lpBuffer, 256);
    sSep = AkelPad.MemRead(lpBuffer, _TSTR);

    sIniTxt = 'nWndPosX='  + aRect.left + ';\r\n' +
              'nWndPosY='  + aRect.top  + ';\r\n' +
              'sSep="'     + sSep.replace(/[\\"]/g, "\\$&") + '";\r\n' +
              'bTab='      + bTab + ';\r\n' +
              'bDesc='     + bDesc + ';\r\n' +
              'bIgCase='   + bIgCase + ';\r\n' +
              'nMethod='   + nMethod + ';\r\n' +
              'nNoSort='   + nNoSort + ';\r\n' +
              'bCopyToCB=' + bCopyToCB + ';'

    oFile = oFSO.OpenTextFile(sIniName, 2, true, 0);
    oFile.Write(sIniTxt);
    oFile.Close();
  }

  else if (oFSO.FileExists(sIniName))
  {
    try
    {
      eval(AkelPad.ReadFile(sIniName));
    }
    catch (oError)
    {
    }
  }
}

function CheckButtons(nButton)
{
  if (nButton == IDTAB)
    bTab = ! bTab;
  else if (nButton == IDDESC)
    bDesc = ! bDesc;
  else if (nButton == IDIGCASE)
    bIgCase = ! bIgCase;
  else if ((nButton == IDSTRING) || (nButton == IDNUM))
    nMethod = nButton - IDSTRING + 1;
  else if ((nButton == IDREVERSE) || (nButton == IDREMODUP) ||
           (nButton == IDEXTRDUP) || (nButton == IDEXTRUNI))
  {
    if (nNoSort == nButton - IDREVERSE + 1)
      nNoSort = 0;
    else
      nNoSort = nButton - IDREVERSE + 1;
  }
  else if (nButton == IDCOPYTOCB)
    bCopyToCB = ! bCopyToCB;

  if (nNoSort > 1)
    nMethod = 1;

  AkelPad.SendMessage(aWnd[IDTAB][WND],      BM_SETCHECK, bTab,           0);
  AkelPad.SendMessage(aWnd[IDDESC][WND],     BM_SETCHECK, bDesc,          0);
  AkelPad.SendMessage(aWnd[IDIGCASE][WND],   BM_SETCHECK, bIgCase,        0);
  AkelPad.SendMessage(aWnd[IDSTRING][WND],   BM_SETCHECK, (nMethod == 1), 0);
  AkelPad.SendMessage(aWnd[IDNUM][WND],      BM_SETCHECK, (nMethod == 2), 0);
  AkelPad.SendMessage(aWnd[IDREVERSE][WND],  BM_SETCHECK, (nNoSort == 1), 0);
  AkelPad.SendMessage(aWnd[IDREMODUP][WND],  BM_SETCHECK, (nNoSort == 2), 0);
  AkelPad.SendMessage(aWnd[IDEXTRDUP][WND],  BM_SETCHECK, (nNoSort == 3), 0);
  AkelPad.SendMessage(aWnd[IDEXTRUNI][WND],  BM_SETCHECK, (nNoSort == 4), 0);
  AkelPad.SendMessage(aWnd[IDCOPYTOCB][WND], BM_SETCHECK, bCopyToCB,      0);

  oSys.Call("User32::EnableWindow", aWnd[IDDESC][WND],   (nNoSort == 0));
  oSys.Call("User32::EnableWindow", aWnd[IDIGCASE][WND], (nNoSort != 1));
  oSys.Call("User32::EnableWindow", aWnd[IDSTRING][WND], (nNoSort != 1));
  oSys.Call("User32::EnableWindow", aWnd[IDNUM][WND],    (nNoSort == 0));
}

function CheckUndoRedo(nButton)
{
  var nBegSel  = AkelPad.GetSelStart();
  var sTxtUndo = aWnd[IDUNDO][TXT];
  var sTxtRedo = aWnd[IDREDO][TXT];

  if (nButton == IDSORT)
  {
    if (nUndoLimit < AkelPad.SendMessage(hEditWnd, 3084 /*AEM_GETUNDOLIMIT*/, 0, 0))
      ++nUndoLimit;
    nRedoLimit = 0;
  }
  else if ((nButton == IDUNDO) && (nUndoLimit))
  {
    --nUndoLimit;
    ++nRedoLimit;
    AkelPad.SendMessage(hEditWnd, 3077 /*AEM_UNDO*/, 0, 0);
  }
  else if ((nButton == IDREDO) && (nRedoLimit))
  {
    ++nUndoLimit;
    --nRedoLimit;
    AkelPad.SendMessage(hEditWnd, 3078 /*AEM_REDO*/, 0, 0);
    AkelPad.SetSel(nBegSel, AkelPad.GetSelEnd());
  }

  if (nUndoLimit)
    sTxtUndo += " (" + nUndoLimit + ")";
  if (nRedoLimit)
    sTxtRedo += " (" + nRedoLimit + ")";

  if (((oSys.Call("User32::GetFocus") == aWnd[IDUNDO][WND]) && (nUndoLimit == 0)) ||
      ((oSys.Call("User32::GetFocus") == aWnd[IDREDO][WND]) && (nRedoLimit == 0)))
    oSys.Call("User32::SetFocus", aWnd[IDSEP][WND]);

  SetWindowFontAndText(aWnd[IDUNDO][WND], hGuiFont, sTxtUndo);
  SetWindowFontAndText(aWnd[IDREDO][WND], hGuiFont, sTxtRedo);

  oSys.Call("User32::EnableWindow", aWnd[IDUNDO][WND], nUndoLimit);
  oSys.Call("User32::EnableWindow", aWnd[IDREDO][WND], nRedoLimit);
}

function SortList()
{
  var nBegSel  = AkelPad.GetSelStart();
  var nEndSel  = AkelPad.GetSelEnd();
  var sText    = AkelPad.GetSelText();
  var nLine1   = AkelPad.SendMessage(hEditWnd, 1078 /*EM_EXLINEFROMCHAR*/, 0, nBegSel);
  var nLine2   = AkelPad.SendMessage(hEditWnd, 1078 /*EM_EXLINEFROMCHAR*/, 0, nEndSel);
  var nBegLen  = TextLen();
  var nBegLine;
  var nEndLine;
  var bSepEnd;
  var rSep;
  var rSep_g;
  var rSepEnd;
  var i;

  oSys.Call("User32::GetWindowText" + _TCHAR, aWnd[IDSEP][WND], lpBuffer, 256);
  sSep = AkelPad.MemRead(lpBuffer, _TSTR);

  if (bTab) sSep = sSep.replace(/\\t/g, "\t");

  rSep    = new RegExp(escapeRegExp(sSep));
  rSep_g  = new RegExp(escapeRegExp(sSep), "g");
  rSepEnd = new RegExp(escapeRegExp(sSep) + "$");

  if (! rSep.test(sText))
  {
    AkelPad.MessageBox(hWndDlg, sTxtNoSep, sTxtCaption, 48 /*MB_ICONEXCLAMATION*/);
    return 0;
  }

  SetRedraw(hEditWnd, false);
  AkelPad.SendMessage(hEditWnd, 3080 /*AEM_STOPGROUPTYPING*/, 0, 0);
  AkelPad.SendMessage(hEditWnd, 3081 /*AEM_BEGINUNDOACTION*/, 0, 0);
  AkelPad.ReplaceSel(sText, -1);

  for (i = nLine1; i <= nLine2; ++i)
  {
    if (nLine1 < nLine2)
    {
       nBegLine = AkelPad.SendMessage(hEditWnd, 187 /*EM_LINEINDEX*/, i, 0);
       nEndLine = AkelPad.SendMessage(hEditWnd, 187 /*EM_LINEINDEX*/, i, 0) +
                    AkelPad.SendMessage(hEditWnd, 193 /*EM_LINELENGTH*/, nBegLine, 0);
      if (i == nLine1)
        AkelPad.SetSel(nBegSel, nEndLine);
      else if (i == nLine2)
        AkelPad.SetSel(nBegLine, nEndSel - nBegLen + TextLen());
      else
        AkelPad.SetSel(nBegLine, nEndLine);

      sText = AkelPad.GetSelText();
    }

    if (rSep.test(sText))
    {
      bSepEnd = rSepEnd.test(sText);
      sText   = sText.replace(rSep_g, "\r");
      AkelPad.ReplaceSel("\r" + sText + "\r", -1);
      AkelPad.SetSel(AkelPad.GetSelStart() + 1, AkelPad.GetSelEnd() - 1);
      SortByPlugin();
      sText = AkelPad.GetTextRange(AkelPad.GetSelStart(), AkelPad.GetSelEnd() - 1 + bSepEnd).replace(/\r/g, sSep);
      AkelPad.SetSel(AkelPad.GetSelStart() - 1, AkelPad.GetSelEnd() + bSepEnd);
      AkelPad.ReplaceSel(sText, -1);
    }
  }

  AkelPad.SetSel(nBegSel, AkelPad.GetSelEnd());
  AkelPad.SendMessage(hEditWnd, 3082 /*AEM_ENDUNDOACTION*/, 0, 0);
  AkelPad.SendMessage(hEditWnd, 3080 /*AEM_STOPGROUPTYPING*/, 0, 0);

  if (bCopyToCB)
  {
    AkelPad.SetClipboardText(AkelPad.GetSelText());
    AkelPad.SendMessage(hEditWnd, 3077 /*AEM_UNDO*/, 0, 0);
  }

  SetRedraw(hEditWnd, true);
  return ! bCopyToCB;
}

function SortByPlugin()
{
  var sAscDesc = bDesc ? "Desc" : "Asc";

  if (nNoSort == 1)
    AkelPad.Call("Format::LineReverse");
  else if (nNoSort == 2)
    AkelPad.Call("Format::LineRemoveDuplicates", ! bIgCase);
  else if (nNoSort == 3)
    AkelPad.Call("Format::LineGetDuplicates", ! bIgCase);
  else if (nNoSort == 4)
    AkelPad.Call("Format::LineGetUnique", ! bIgCase);
  else if (nMethod == 1)
    AkelPad.Call("Format::LineSortStr" + sAscDesc, ! bIgCase);
  else if (nMethod == 2)
    AkelPad.Call("Format::LineSortInt" + sAscDesc, ! bIgCase);
}

function escapeRegExp(sStr)
{
  return sStr.replace(/[\\\/.^$+*?|()\[\]{}]/g, "\\$&");
}

function SetRedraw(hWnd, bRedraw)
{
  var oSys = AkelPad.SystemFunction();
  AkelPad.SendMessage(hWnd, 11 /*WM_SETREDRAW*/, bRedraw, 0);
  bRedraw && oSys.Call("User32::InvalidateRect", hWnd, 0, true);
}

function TextLen()
{
  var nOffset = -1;
  var lpIndex;

  if (lpIndex = AkelPad.MemAlloc(_X64 ? 24 : 12 /*sizeof(AECHARINDEX)*/))
  {
    AkelPad.SendMessage(hEditWnd, 3130 /*AEM_GETINDEX*/, 2 /*AEGI_LASTCHAR*/, lpIndex);
    nOffset = AkelPad.SendMessage(hEditWnd, 3136 /*AEM_INDEXTORICHOFFSET*/, 0, lpIndex);
    AkelPad.MemFree(lpIndex);
  }
  return nOffset;
}
