Page 24 of 24

Posted: Mon Apr 26, 2021 9:55 am
by DV
Поскольку под AkelPad такого ещё не было, выкладываю прототип GoToAnything (переход к файлу/строке в файле/поиск текста) для AkelPad. Вдохновлено угадайте каким редактором, в связи с чем предлагается горячая клавиша Ctrl+P.
Начиная с версии 0.4, скрипт поддерживает Избранное (Favourites) и Историю Открытия Файлов (Recent Files History).

GoToAnything.js
Скрипт уже увеличился настолько, что не влазит в сообщение. Поэтому скачайте его тут:
https://github.com/d0vgan/AkelPad-Scrip ... in/Scripts

Давайте обсуждать скрипт в соседней теме:
http://akelpad.sourceforge.net/forum/vi ... php?t=2151

Posted: Thu May 05, 2022 6:11 am
by dothen
Меню для цветовых тем.

Code: Select all

// http://akelpad.sourceforge.net/forum/viewtopic.php?p=35922#p35922
// Version: 1.5 (2023.05.07)
// Author: dothen
//
// *** Menu for color themes. ***
//
// Required to include: ShowMenu.js
//
// Usage:
// "ColorThemeMenu" Call("Scripts::Main", 1, "ColorThemeMenu.js") Icon("%a\AkelFiles\Plugs\Coder.dll", 5)
// "ColorThemeMenu" Call("Scripts::Main", 1, "ColorThemeMenu.js", `-posx=%bl -posy=%bb -mode=1`) Icon("%a\AkelFiles\Plugs\Coder.dll", 5)
//
// Аргументы:
//      -posx -posy     Координаты позиции меню (по умолчанию координаты курсора мыши),
//                      %bl и %bb  переменные плагина ToolBar, он вставит на их место координаты кнопки.
//      -mode           1 - Упрямое меню. 0 - Нормальное меню (по умолчанию 0).
//
// Описание:
// Выгружает на диск встроенные темы добавляя набор переменных от Infocatcher
// (https://github.com/Infocatcher/AkelPad_coder/blob/master/_colors.txt).
// Выгружает на диск пользовательские темы.
// Загружает с диска пользовательские темы.
// Выгрузка тем выполняется в папки \Coder\themes\user\ и \Coder\themes\internal\ с перезаписью файлов.
// Файлы с темами для загрузки в AkelPad надо сложить в папку \Coder\themes\
// Править и удалять темы можно через диалог настроек плагина Coder.
// Меню показывает пользовательские темы и файлы цветовых тем.
// Скрипт можно использовать как дополнение к PluginText.js от KDJ
// (http://akelpad.sourceforge.net/files/plugs/Scripts/KDJ.zip)
//
// Ctrl + Shift + LClick в меню удаляет выбранную тему из Coder.ini без подтверждения.
// Ctrl + LClick в подменю "Импорт" открывает выбранный файл для редактирования.

// Подсветка файлов с темами:
//
//Files:
//*.akel-theme
//
//QuotesRE:
//0 '^[^\s]+ "#([A-F\d]{6}|[A-F\d]{3})"$'   "\0=(0,\1,0)"   0   0


//Syntax
if (!GetSyntaxFile()) WScript.Quit();

//Include
if (!AkelPad.Include("ShowMenu.js")) WScript.Quit();

//HighLight
if (!AkelPad.IsPluginRunning("Coder::HighLight"))
    if (AkelPad.Call("Coder::HighLight") == -1 /*UD_FAILED*/) WScript.Quit();

//Variables
var oSys = AkelPad.SystemFunction();
var oSet = AkelPad.ScriptSettings();
var oFSO = new ActiveXObject("Scripting.FileSystemObject");
var sDirThemes = AkelPad.GetAkelDir(4) + "\\Coder\\themes\\";
var sDirThemesI = sDirThemes + "internal\\";
var sDirThemesU = sDirThemes + "user\\";
var sFileExt = ".akel-theme";
var sCurThemeName;
var aFileList = GetFileList();
var aThemeList = GetThemeList();
var nPosMenuX;
var nPosMenuY;
var nRunAgain;
var bUpdate = false;

var nPosMenuX = AkelPad.GetArgValue("posx", POS_CURSOR)
var nPosMenuY = AkelPad.GetArgValue("posy", POS_CURSOR)
var nRunAgain = AkelPad.GetArgValue("mode", 0)

 while (RunMenu())
{
    if (bUpdate)
    {
        aThemeList.splice(0,aThemeList.length);
        aThemeList = GetThemeList();
        bUpdate = false;
    }
    WScript.Sleep(100);
}

function RunMenu()
{
    var aItems = [];
    var nChecked = 0;
    var nFileListLength = aFileList.length;
    var nThemeListLength = aThemeList.length;
    var nDisabled = (nFileListLength == 0) ? MF_DISABLED | MF_GRAYED : MF_NORMAL;
    var i = -1;
    var c = 0;

    sCurThemeName = GetCurTheme();

    for(c = 0; c < nThemeListLength; c++)       // Список тем
    {
        nChecked = (aThemeList[c] == sCurThemeName) ? MF_NORMAL | MF_CHECKED | MF_USECHECKBITMAPS : MF_NORMAL;
        aItems[++i] = [aThemeList[c], nChecked, i];
    }

    aItems[++i] = ["", MF_SEPARATOR];
    aItems[++i] = ["Экспорт", MF_SUBMENU];
        aItems[++i] = ["Выгрузить свои темы", MF_NORMAL, i];
        aItems[++i] = ["Выгрузить встроенные темы", MF_NORMAL | MF_LAST, i];

    aItems[++i] = ["Импорт", MF_SUBMENU];
        for(c = 0; c < nFileListLength; c++)    // Список файлов
            aItems[++i] = [aFileList[c], MF_NORMAL, i];
        aItems[++i] = ["", MF_SEPARATOR];
        aItems[++i] = ["Загрузить все темы", nDisabled | MF_LAST, i];

    aItems[++i] = ["", MF_SEPARATOR];
    aItems[++i] = ["Настроить...", MF_NORMAL, i];

    //Show menu
    var nItem = ShowMenu(aItems, nPosMenuX, nPosMenuY);
    if (nItem == -1)
        return false;               // Ничего не выбрано.

    if (nItem < nThemeListLength)
    {
        if (Ctrl() && Shift())
        {
            DeleteColorTheme(aThemeList[nItem]);    // Удалить выбранную из меню цветовую тему.
            bUpdate = true;
        }
        else if (!Ctrl() && !Shift())
            ActivateColorTheme(aThemeList[nItem]);  // Активировать выбранную из меню цветовую тему.
        return nRunAgain;
    }

    if (nItem == nThemeListLength + 2)
    {
        ExportUserThemes();         // Выгрузить пользовательские темы в папку \Coder\themes\user\
        return false;
    }

    if (nItem == nThemeListLength + 3)
    {
        ExportInternalThemes();     // Выгрузить встроенные темы в папку \Coder\themes\internal\
        return false;
    }

    if ((nItem > nThemeListLength + 3) && (nItem < i - 3) )
    {
        if (Ctrl() && !Shift())
        {
            AkelPad.OpenFile(sDirThemes + aItems[nItem][0]);    // Открыть выбранный файл для редактирования.
            return false;
        }
        if (!(Ctrl() || Shift()))
        {
            ImportUserThemes(aItems[nItem][0]);                 // Загрузить тему из выбранного файла и активировать.
            bUpdate = true;
            return nRunAgain;
        }
        return false;
    }

    if (nItem == i - 2)
    {
        ImportUserThemes("");       // Загрузить все цветовые темы из папки \Coder\themes.
        bUpdate = true;
        return nRunAgain;
    }

    if (nItem == i)
    {
        ColorThemeSettings();       // Открыть диалог настроек плагина Coder.
        return false;
    }

    WScript.Echo("RunMenu Error");
    return false;
}

function Ctrl()
{
    return Boolean(oSys.Call("User32::GetKeyState", 0x11 /*VK_CONTROL*/) & 0x8000);
}

function Shift()
{
    return Boolean(oSys.Call("User32::GetKeyState", 0x10 /*VK_SHIFT*/) & 0x8000);
}

function DeleteColorTheme(sThemeName)
{
    var sThemeList;
    var aThemeList;

    if (oSet.Begin("Coder", 0x21 /*POB_PLUGS|POB_READ*/))
    {
        sThemeList = oSet.Read("VarThemeList", 3 /*PO_STRING*/);
        oSet.End();
    }

    aThemeList = sThemeList.split("|").sort();

    for (i = 0; i < aThemeList.length; ++i)
    {
        if (aThemeList[i] == sThemeName)
        {
            aThemeList.splice(i, 1);
            break;
        }
    }

    sThemeList = aThemeList.join("|");

    if (oSet.Begin("Coder", 0x22 /*POB_PLUGS|POB_SAVE*/))
    {
        oSet.Delete("/" + sThemeName);
        oSet.Write("VarThemeList", 3 /*PO_STRING*/, sThemeList);

        if (sThemeName == sCurThemeName)
        {
            if (aThemeList.length == 0)
                sThemeName = "Default";
            else sThemeName = aThemeList[0];

            oSet.Write("VarThemeActive", 3 /*PO_STRING*/, sThemeName);
        }
        oSet.End();
    }
    RestartPlugin();
}

function ActivateColorTheme(sThemeName)
{
    AkelPad.Call("Coder::Settings", 5, sThemeName);
}

function ColorThemeSettings()
{
    AkelPad.Call("Coder::Settings");
}

function GetCurTheme()
{
    var sThemeName = "";
    var lpBuf;

    if (lpBuf = AkelPad.MemAlloc(256))
    {
        AkelPad.CallW("Coder::Settings", 20, 0, lpBuf, 256);
        sThemeName = AkelPad.MemRead(lpBuf, 1 /*DT_UNICODE*/);
        AkelPad.MemFree(lpBuf);
    }
    return sThemeName;
}

function RestartPlugin()
{
    var bHighLight    = AkelPad.IsPluginRunning("Coder::HighLight");
    var bCodeFold     = AkelPad.IsPluginRunning("Coder::CodeFold");
    var bAutoComplete = AkelPad.IsPluginRunning("Coder::AutoComplete");
    for(var i = 0; i < 2; i++)
    {
        if (bHighLight)
            AkelPad.Call("Coder::HighLight");
        if (bCodeFold)
            AkelPad.Call("Coder::CodeFold");
        if (bAutoComplete)
            AkelPad.Call("Coder::AutoComplete");
    }
}

function GetFileList()
{
    var aList = [];
    var sFileName;
    var sFile_Ext;

    if (ThemesFolderExists())
    {
        var Folder = oFSO.GetFolder(sDirThemes);
        var Files = new Enumerator(Folder.Files);

        for (; !Files.atEnd(); Files.moveNext())
        {
            sFileName = Files.item().Name;
            sFile_Ext = "." + AkelPad.GetFilePath(sFileName, 4 /*CPF_FILEEXT*/);
            if (sFile_Ext == sFileExt)
                aList.push(sFileName);
        }
    }
    return aList.sort();
}

function ExportUserThemes()
{
    var sThemeList = "";
    var sThemeText = "";

    if (MsgBox(sDirThemesU) == 2 /*IDCANCEL*/) return;

    if (oFSO.FolderExists(sDirThemes) == false)
        oFSO.CreateFolder(sDirThemes);
    if (oFSO.FolderExists(sDirThemesU) == false)
        oFSO.CreateFolder(sDirThemesU);

    if (oSet.Begin("Coder", 0x21 /*POB_PLUGS|POB_READ*/))
    {
        sThemeList = oSet.Read("VarThemeList", 3 /*PO_STRING*/);
        if (sThemeList != "")
        {
            var aList = sThemeList.split("|");
            var nLen = aList.length;

            for(var i = 0; i < nLen; i++)
            {
                sThemeText = oSet.Read("/" + aList[i], 20 /*PO_BINARYSTRING*/);
                if (sThemeText != "")
                    AkelPad.WriteFile(sDirThemesU + aList[i] + sFileExt, sThemeText, -1, 1251, false);
            }
            MsgBoxOk(sDirThemesU);
        }
        oSet.End();
    }
}

function ThemesFolderExists()
{
    return oFSO.FolderExists(sDirThemes);
}

function ImportUserThemes(sFile)
{
    if (ThemesFolderExists())
    {
        if (sFile == "")    // Загрузить все файлы.
        {
            var nLen = aFileList.length;
            for(var i = 0; i < nLen; i++)
                WriteThemeToIniFile(aFileList[i]);
            RestartPlugin();
        }
        else                // Загрузить файл.
        {
            WriteThemeToIniFile(sFile);
            RestartPlugin();
            AkelPad.Call("Coder::Settings", 5 /*DLLA_CODER_SETVARTHEME*/, sFile.replace(sFileExt,""));
        }
    }
}

function ReadThemeFile(sFullFileName)
{
    var sText = AkelPad.ReadFile(sFullFileName);

    sText = sText.replace(/\x0A/g, "\r");       // Newline format - CR (Macintosh)
    sText = sText.replace(/\x0D{2,}/g, "\r");   // Удалить пустые строки.
    sText = sText + String.fromCharCode(0);     // Дописать в конец нули.
    return sText;
}

function WriteThemeToIniFile(sFileName)
{
    var sThemeText = ReadThemeFile(sDirThemes + sFileName);
    var sThemeName = sFileName.replace(sFileExt,"");

    if (oSet.Begin("Coder", 0x21 /*POB_PLUGS|POB_READ*/))
    {
        var sThemeList = oSet.Read("VarThemeList", 3 /*PO_STRING*/);
        oSet.End();
    }

    if (oSet.Begin("Coder", 0x22 /*POB_PLUGS|POB_SAVE*/))
    {
        if (sThemeList == "")
           sThemeList = sThemeName;

        else if (sThemeList.search(sThemeName) == -1)
           sThemeList = sThemeList + "|" + sThemeName;

        oSet.Write("VarThemeList", 3 /*PO_STRING*/, sThemeList);      //  VarThemeList=Active4DEx|BespinEx|...|ZenburnEx
        oSet.Write("/" + sThemeName, 2 /*PO_BINARY*/, sThemeText);    //  /BespinEx=4F004B00...22000D0000000000
        oSet.End();
    }
}

function MsgBox(sDir)
{
    var nChoice = AkelPad.MessageBox(
        AkelPad.GetMainWnd(),
        "\nФайлы в папке\n" + sDir + "\nбудут перезаписаны.\n\nПродолжить?",
        "Экспорт цветовых тем", 65 /*MB_ICONASTERISK | MB_OKCANCEL*/);
    return nChoice;
}

function MsgBoxOk(sDir)
{
    AkelPad.MessageBox(
        AkelPad.GetMainWnd(),
        "\nТемы экспортированы в папку\n" + sDir,
        "Экспорт цветовых тем", 64 /*MB_ICONASTERISK | MB_OK*/);
}

function GetThemeList()
{
    return GetThemeData(1);
}

function ExportInternalThemes()
{
    GetThemeData(2);
    MsgBoxOk(sDirThemesI);
}

function WriteFileTheme(sThemeName, sThemeText)
{
    sThemeText = ExtraVars() + sThemeText;
    sThemeText = sThemeText.replace(/ ([^"]+?)$/gm, ' "$1"');     // Значения переменных взять в кавычки.
    AkelPad.WriteFile(sDirThemesI + sThemeName + "Ex" + sFileExt, sThemeText, -1, 1251, true);
}

function GetThemeData(nMode)
{
    var sThemeName;
    var sThemeText;
    var lpThemeCur;
    var lpThemePrev;
    var lpThemeData;
    var aThemeList = [];
    var CELL = _X64?8:4;

    if (nMode == 2)
    {
        if (MsgBox(sDirThemesI) == 2 /*IDCANCEL*/) return;

        if (oFSO.FolderExists(sDirThemes) == false)
            oFSO.CreateFolder(sDirThemes);
        if (oFSO.FolderExists(sDirThemesI) == false)
            oFSO.CreateFolder(sDirThemesI);
    }

    var lpVTG = AkelPad.MemAlloc(CELL); // **lppVarThemeGlobal
    var lpVTA = AkelPad.MemAlloc(CELL); // **lppVarThemeActive

    AkelPad.CallW("Coder::Settings", 24 /*DLLA_CODER_GETVARTHEMEDATA*/, 0, 0, lpVTG, lpVTA);
    lpThemeCur = AkelPad.MemRead(lpVTA, 2 /*DT_QWORD*/);
    AkelPad.MemFree(lpVTG);
    AkelPad.MemFree(lpVTA);

    while(lpThemeCur != 0) // Находим первую структуру VARTHEME.
    {
        lpThemePrev = lpThemeCur;
        lpThemeCur = AkelPad.MemRead(lpThemeCur + CELL, 2 /*DT_QWORD*/);      // *prev
    }

    lpThemeCur = lpThemePrev

    while(lpThemeCur != 0)
    {
        sThemeName = AkelPad.MemRead(lpThemeCur + CELL * 5, 1 /*DT_UNICODE*/);            // (4+4+12)/4=5          (8+8+24)/8=5          wszVarThemeName[MAX_PATH]
        lpThemeData = AkelPad.MemRead(lpThemeCur + CELL * (_X64?71:136), 2 /*DT_QWORD*/); // (4+4+12+520+4)/4=136  (8+8+24+520+8)/8=71   *wpTextData
        sThemeText = AkelPad.MemRead(lpThemeData, 1 /*DT_UNICODE*/);

        if ((nMode == 1) & (sThemeText == "")) // В пользовательской теме *wpTextData указывает на пустую строку.
            aThemeList.push(sThemeName);

        if ((nMode == 2) & (sThemeText != ""))
            WriteFileTheme(sThemeName, sThemeText);

        lpThemeCur = AkelPad.MemRead(lpThemeCur, 2 /*DT_QWORD*/);
    }
    if (nMode == 1) return aThemeList.sort();
}

/*
AkelPad-4.9.8-src.zip\AkelFiles\Plugs\Coder\Source\Coder.h
typedef struct _VARTHEME {
  struct _VARTHEME *next;             4(8)
  struct _VARTHEME *prev;             4(8)
  STACKVAR hVarStack;                 12(24)
  wchar_t wszVarThemeName[MAX_PATH];  520(520)     #define MAX_PATH 260   stdlib.h
  int nVarThemeNameLen;               4(8)
  const wchar_t *wpTextData;
} VARTHEME;
*/

function GetSyntaxFile()
{
    var pSyntaxFile = "";
    var lpSyntaxFile;
    if (lpSyntaxFile = AkelPad.MemAlloc(256 * 2))
    {
        AkelPad.CallW("Coder::Settings", 16, 0, lpSyntaxFile, 256);
        pSyntaxFile = AkelPad.MemRead(lpSyntaxFile, 1 /*DT_UNICODE*/);
        AkelPad.MemFree(lpSyntaxFile);
    }
    return (pSyntaxFile != "");
}

function ExtraVars()
{
    var sExtraVars =
    "LABEL #64E986\r" +
    "OK #339933\r" +
    "ERR #CC3333\r" +
    "IMPORTANT #CC3333\r" +
    "WARN #BB6622\r" +
    "DBG #9932CC\r" +
    "INFO #3333CC\r" +
    "COMM_C #668000\r" +
    "KEYWORD #9932CC\r" +
    "KEYWORD_ALT #3399CC\r" +
    "KEYWORD_SPECIAL #33CC99\r" +
    "KEYWORD_NOTSTD #800000\r" +
    "GLOBAL_VAR #9932CC\r" +
    "GLOBAL_VAR_NOTSTD #800000\r" +
    "REGEXP #8000FF\r" +
    "PROPERTY #3399CC\r" +
    "PROPERTY_ALT #3333CC\r" +
    "PROPERTY_NOTSTD #CC3333\r" +
    "EVENT #33CC99\r" +
    "EVENT_NOTSTD #CC3333\r" +
    "BLOCK #8B0000\r" +
    "BLOCK_NOTSTD #CC3333\r" +
    "OP_ALT #3399CC\r" +
    "OP_NOTSTD #8B0000\r" +
    "CMD #339933\r" +
    "TAG_NS #3399CC\r" +
    "TAG_ALT #3399CC\r" +
    "TAG_DEPRECATED #992266\r" +
    "TAG_NOTSTD #8B0000\r" +
    "ATTR_ALT #9932CC\r" +
    "ATTR_NS #33CC99\r" +
    "ATTR_NOTSTD #800000\r" +
    "ENTITY #3399CC\r" +
    "IF_NOTSTD #8B0000\r" +
    "NUM_ALT #0000FF\r";
    return sExtraVars;
}


Posted: Sun Aug 07, 2022 7:44 pm
by Infocatcher

Code: Select all

// http://akelpad.sourceforge.net/forum/viewtopic.php?p=36081#p36081
// http://infocatcher.ucoz.net/js/akelpad_scripts/toggleToolbarRows.js
// https://github.com/Infocatcher/AkelPad_scripts/blob/master/toggleToolbarRows.js

// (c) Infocatcher 2022
// Version: 0.1.0 - 2022-08-07
// Author: Infocatcher

//// Toggle multiline toolbar from ToolBar plugin (convert BREAK <-> #BREAK)

// Arguments:
//   -toolBarName="ToolBar2"  - specify file name of ToolBar plugin

// Usage:
//   Call("Scripts::Main", 1, "toggleToolbarRows.js")
//   Call("Scripts::Main", 1, "toggleToolbarRows.js", '-toolBarName="ToolBar2"')

var tbPlugName = AkelPad.GetArgValue("toolBarName", "ToolBar");

function _localize(s) {
	var strings = {
		"ToolBarText data in %P plugin is empty!": {
			ru: "Содержимое ToolBarText плагина %P пустое!"
		},
		"ToolBarText data in %P plugin not recognized:\n%S": {
			ru: "Содержимое ToolBarText плагина %P не распознано:\n%S"
		},
		"Failed to read settings of %P plugin": {
			ru: "Не удалось прочитать настройки плагина %P"
		},
		"Failed to write settings of %P plugin": {
			ru: "Не удалось записать настройки плагина %P"
		},
		"Failed to toggle multiline toolbar from %P plugin: BREAK item not found": {
			ru: "Не удалось переключить многострочность панели инструментов плагина %P: элемент BREAK не найден"
		}
	};
	var lng = "en";
	switch(AkelPad.GetLangId(1 /*LANGID_PRIMARY*/)) {
		case 0x19: lng = "ru";
	}
	_localize = function(s) {
		return strings[s] && strings[s][lng] || s;
	};
	return _localize(s);
}
function _str(s) {
	return _localize(s).replace("%P", tbPlugName);
}

var oSet = AkelPad.ScriptSettings();
var hMainWnd = AkelPad.GetMainWnd();
var isHex = AkelPad.SendMessage(hMainWnd, 1222 /*AKD_GETMAININFO*/, 5 /*MI_SAVESETTINGS*/, 0) == 2 /*SS_INI*/;

if(oSet.Begin(tbPlugName, 0x21 /*POB_READ|POB_PLUGS*/)) {
	var tbData = oSet.Read("ToolBarText", 3 /*PO_STRING*/);
	oSet.End();
	if(
		!tbData
		|| isHex && (tbData.length % 4 || /[^\dA-F]/i.test(tbData))
	) {
		error(
			tbData
				? _str("ToolBarText data in %P plugin not recognized:\n%S")
					.replace("%S", tbData.substr(0, 100))
				: _str("ToolBarText data in %P plugin is empty!")
		);
		WScript.Quit();
	}
}
else {
	error(_str("Failed to read settings of %P plugin"));
}

if(tbData && oSet.Begin(tbPlugName, 0x22 /*POB_SAVE|POB_PLUGS*/)) {
	var tbText = isHex ? hexToStr(tbData) : tbData;
	var changed;
	tbText = tbText.replace(/\r(#?)BREAK\r/g, function(s, commented) {
		changed = true;
		return "\r" + (commented ? "" : "#") + "BREAK\r";
	});
	if(changed) {
		tbData = isHex ? strToHex(tbText) : tbText;
		oSet.Write("ToolBarText", 3 /*PO_STRING*/, tbData);
	}
	else {
		error(_str("Failed to toggle multiline toolbar from %P plugin: BREAK item not found"), 48 /*MB_ICONEXCLAMATION*/);
	}
	oSet.End();

	if(changed && AkelPad.IsPluginRunning(tbPlugName + "::Main")) {
		AkelPad.Call(tbPlugName + "::Main");
		AkelPad.Call(tbPlugName + "::Main");
	}
}
else {
	tbData && error(_str("Failed to write settings of %P plugin"));
}

function hexToStr(h) {
	return h.replace(/[\dA-F]{4}/ig, function(h) {
		var n = parseInt(reorder(h), 16);
		return String.fromCharCode(n);
	});
}
function strToHex(s) {
	return s.replace(/[\s\S]/g, function(c) {
		var h = c.charCodeAt(0).toString(16).toUpperCase();
		h = "0000".substr(h.length) + h;
		return reorder(h);
	});
}
function reorder(h) { // LE <-> BE
	var b1 = h.substr(0, 2);
	var b2 = h.substr(2);
	return b2 + b1;
}

function error(msg, icon) {
	AkelPad.MessageBox(hMainWnd, msg, WScript.ScriptName, icon || 16 /*MB_ICONERROR*/);
}
<download | development version>
Toggle multiline toolbar from ToolBar plugin (convert BREAK <-> #BREAK).


Image

Image

Posted: Mon Jul 10, 2023 7:32 pm
by AlexeyB
В сценарий PluginText.js (автор KDJ) добавил поддержку работы с различными кодировками (сохранение - только UTF-16 или UTF-8 ) и форматами новой строки (сохранение - только в CR или CRLF) файлов .akelmenu.

В оригинальном сценарии сохранять и загружать файлы можно только в UTF-16 LE, CR.

Прежде всего это полезно при работе с Git или Mercurial, которые не поддерживают UTF-16 (сравнивают как бинарные файлы, и с ними потом неудобно работать) и переводы строк в стиле Mac (CR, \r).

Поскольку KDJ перестал посещать этот форум, просто выкладываю модифицированный вариант сценария.

PluginTextEx.js

Re: Scripts collection

Posted: Tue Sep 26, 2023 10:18 pm
by AZJIO
Иногда хочется посмотреть скрипт, но лень искать его в списке скриптов, скопировал имя скрипта (двойной клик -> Ctrl+C), кликаем пункт и скрипт уже открыт.

Code: Select all

// "Открыть скрипт" Call("Scripts::Main", 1, "OpenScript.js")
var pHelp = "Удерживая Ctrl нажмите пункт меню или кнопку, далее в открытом окне выделите имя скрипта двойным кликом (без .js), скопируйте и только потом используйте этот пункт, чтобы отрыть скрипт в AkelPad"
var pName = AkelPad.GetClipboardText(true);
// if (pName.indexOf("\n") = -1) {
// разрешил относительный путь folder/name в регвыре
if ((pName == "") || (pName.lengt > 150) || (pName.search(/[\n:*?"<>|]/) > -1)) {
	WScript.Echo('Буфер обмена не содержит имени файла. ' + pHelp);
	WScript.Quit();
}
var path = 'AkelFiles\\Plugs\\Scripts\\' + pName;
if (path.slice(-3) != '.js') {path += '.js';}
var fso = new ActiveXObject("Scripting.FileSystemObject");
if (fso.FileExists(path)) {
	AkelPad.OpenFile(path);
} else {
	WScript.Echo(path + '\n\nФайл не существует. ' + pHelp);
}

Re: Scripts collection

Posted: Wed Feb 14, 2024 6:53 am
by AZJIO
Stemming (RU) - удаляет окончания слов для генерации списка слов поискового запроса. Например я сделал поле ввода для поиска слов по справке. Мне нужно иметь только слова имеющиеся в справке. Я получаю список всех слов, но там есть такие как "мяч, мячу, мячом, мячами, мячи" и т.д. при этом для поискового запроса требуется только "мяч" чтобы найти все его вхождения в справке.
Обновил, добавил поддержку "ё", точнее восстановление "ё" из оригинального слова.
Обновил, оптимизировал вариант с "ё".
Обновил, добавил перевод в нижний регистр и сообщение о завершении обработки.
Проверка на отсутствие гласных в слове исключает абревиатуры.
70 тыс. строк очень медленно обрабатывает, 1 минута 10 сек. (онлайн-сервисы обрабатывают мгновенно).

Code: Select all

// Стеминг он же Стиммер (удаляет окончания слов оставляя базу), предназначен для генерации списка слов поискового запроса.
// модернизация для AkelPad от AZJIO (14.02.2024)
// алгоритм использовал по ссылке: https://popsul.ru/blog/2012/06/post-10.html
// но он работает с одним словом, пришлось дополнить его захватом русских слов из текста циклом обработки каждого слова,
// и добавить удаление дубликатов и сортировку.

var pAllText=AkelPad.GetTextRange(0, -1);
pAllText=pAllText.toLowerCase()
pAllText = Stemmer(pAllText)
pAllText = RemoveDuplicates(pAllText) // удаление дубликатов и сортировка
pAllText = pAllText.replace(/^[^\n]{0,3}\n/gm, ""); // удаление строк длинной меньше 4-х символов
AkelPad.SetClipboardText(pAllText);
WScript.Echo("готово, результат в буфере обмена");


function RemoveDuplicates(text) {
	oDict = new ActiveXObject("Scripting.Dictionary");
	oDict.CompareMode = 1;
	var ares = [];
	var result = "";
	var i;
	var size;
	var arr = text.split('\n'); // получаем массив слов

	for (i in arr) {
		if (oDict.Exists(arr[i]))
			oDict.Item(arr[i]) += 1;
		else
			oDict.Item(arr[i]) = 1;
		};

	ares = (new VBArray(oDict.Keys())).toArray();
	ares = ares.sort();
	result = ares.join("\n")
	return result;
};


function Stemmer(pAllText) {

	var RVRE= /^(.*?[аеиоуыэюя])(.*)$/i;
	var PERFECTIVEGROUND_1= /([ая])(в|вши|вшись)$/gi;
	var PERFECTIVEGROUND_2= /(ив|ивши|ившись|ыв|ывши|ывшись)$/i;
	var REFLEXIVE= /(с[яь])$/i;
	var ADJECTIVE= /(ее|ие|ые|ое|ими|ыми|ей|ий|ый|ой|ем|им|ым|ом|его|ого|ему|ому|их|ых|ую|юю|ая|яя|ою|ею)$/i;
	var PARTICIPLE_1= /([ая])(ем|нн|вш|ющ|щ)$/gi;
	var PARTICIPLE_2= /(ивш|ывш|ующ)$/i;
	var VERB_1= /([ая])(ла|на|ете|йте|ли|й|л|ем|н|ло|но|ет|ют|ны|ть|ешь|нно)$/gi;
	var VERB_2= /(ила|ыла|ена|ейте|уйте|ите|или|ыли|ей|уй|ил|ыл|им|ым|ен|ило|ыло|ено|ят|ует|уют|ит|ыт|ены|ить|ыть|ишь|ую|ю)$/i;
	var NOUN= /(а|ев|ов|ие|ье|е|иями|ями|ами|еи|ии|и|ией|ей|ой|ий|й|иям|ям|ием|ем|ам|ом|о|у|ах|иях|ях|ы|ь|ию|ью|ю|ия|ья|я)$/i;
	var DERIVATIONAL= /.*[^аеиоуыэюя]+[аеиоуыэюя].*ость?$/i;
	var DER= /ость?$/i;
	var SUPERLATIVE= /(ейше|ейш)$/i;
	var I= /и$/i;
	var P= /ь$/i;
	var NN= /нн$/i;

	var start;
	var rv;
	var wParts;
	var temp;
	var rure = /[а-яё]+/gi;
	var myArray;
	var result = "";

while ((myArray = rure.exec(pAllText)) != null) // цикл перечисления всех русских слов
{
	myArray[0] =  myArray[0].replace(/ё/gi, "е");
	wParts =  myArray[0].match(RVRE); // проверяем совпадение гласных в тексте
	if (!wParts) {
		// return word; // если нет гласных, возвращаем текст как есть назад
		continue
	}
	start = wParts[1];
	rv = wParts[2];
		temp = rv.replace(PERFECTIVEGROUND_2, "");
		if (temp == rv) {
			temp = rv.replace(PERFECTIVEGROUND_1, '$1');
		}
		if (temp == rv) {
			rv = rv.replace(REFLEXIVE, "");
			temp = rv.replace(ADJECTIVE, "");
			if (temp != rv) {
				rv = temp;
				temp = rv.replace(PARTICIPLE_2, "");
				if (temp == rv) {
					rv = rv.replace(PARTICIPLE_1, '$1');
				}
			} else {
				temp = rv.replace(VERB_2, "");
				if (temp == rv) {
					temp = rv.replace(VERB_1, '$1');
				}
				if (temp == rv) {
					rv = rv.replace(NOUN, "");
				} else {
					rv = temp;
				}
			}
		} else {
			rv = temp;
		}
		rv = rv.replace(I, "");
		if (rv.match(DERIVATIONAL)) {
			rv = rv.replace(DER, "");
		}
		temp = rv.replace(P, "");
		if (temp == rv) {
			rv = rv.replace(SUPERLATIVE, "");
			rv = rv.replace(NN, 'н');
		} else {
			rv = temp;
		}
		result += start + rv + "\n";
	};
		return result;
};



Code: Select all

// Стеминг он же Стиммер (удаляет окончания слов оставляя базу), предназначен для генерации списка слов поискового запроса.
// модернизация для AkelPad от AZJIO (14.02.2024)
// алгоритм использовал по ссылке: https://popsul.ru/blog/2012/06/post-10.html
// но он работает с одним словом, пришлось дополнить его захватом русских слов из текста циклом обработки каждого слова,
// и добавить удаление дубликатов и сортировку.

// Восстанавливает "ё" - если строка содержит ё (включаем флаг restore и перед тем как добавить слово в результат берём из оргинала
// число символов по результату и получаем обрезанное но с "ё").

var pAllText=AkelPad.GetTextRange(0, -1);
pAllText=pAllText.toLowerCase()
pAllText = Stemmer(pAllText)
pAllText = pAllText.replace(/^[^\n]{0,3}\n/gm, ""); // удаление строк длинной меньше 4-х символов
AkelPad.SetClipboardText(pAllText); // возвращаем результат в буфер обмена
WScript.Echo("готово, результат в буфере обмена");

function Stemmer(pAllText) {

	var RVRE= /^(.*?[аеиоуыэюя])(.*)$/i;
	var PERFECTIVEGROUND_1= /([ая])(в|вши|вшись)$/gi;
	var PERFECTIVEGROUND_2= /(ив|ивши|ившись|ыв|ывши|ывшись)$/i;
	var REFLEXIVE= /(с[яь])$/i;
	var ADJECTIVE= /(ее|ие|ые|ое|ими|ыми|ей|ий|ый|ой|ем|им|ым|ом|его|ого|ему|ому|их|ых|ую|юю|ая|яя|ою|ею)$/i;
	var PARTICIPLE_1= /([ая])(ем|нн|вш|ющ|щ)$/gi;
	var PARTICIPLE_2= /(ивш|ывш|ующ)$/i;
	var VERB_1= /([ая])(ла|на|ете|йте|ли|й|л|ем|н|ло|но|ет|ют|ны|ть|ешь|нно)$/gi;
	var VERB_2= /(ила|ыла|ена|ейте|уйте|ите|или|ыли|ей|уй|ил|ыл|им|ым|ен|ило|ыло|ено|ят|ует|уют|ит|ыт|ены|ить|ыть|ишь|ую|ю)$/i;
	var NOUN= /(а|ев|ов|ие|ье|е|иями|ями|ами|еи|ии|и|ией|ей|ой|ий|й|иям|ям|ием|ем|ам|ом|о|у|ах|иях|ях|ы|ь|ию|ью|ю|ия|ья|я)$/i;
	var DERIVATIONAL= /.*[^аеиоуыэюя]+[аеиоуыэюя].*ость?$/i;
	var DER= /ость?$/i;
	var SUPERLATIVE= /(ейше|ейш)$/i;
	var I= /и$/i;
	var P= /ь$/i;
	var NN= /нн$/i;

	var start;
	var rv;
	var wParts;
	var temp;
	// var rure = /[а-яё]+/gi;
	var rure = /[а-яё]{4,}/gi;
	var myArray;
	var result = "";
	var orig = "";
	var restoreIO = 0;
	var lenw;
	
	
	oDict = new ActiveXObject("Scripting.Dictionary");
	oDict.CompareMode = 1;
	var ares = [];

while ((myArray = rure.exec(pAllText)) != null) // цикл перечисления всех русских слов
{
	if (/ё/i.test(myArray[0])) {
		orig = myArray[0]
		myArray[0] =  myArray[0].replace(/ё/gi, "е");
		restoreIO=1
	}
	wParts =  myArray[0].match(RVRE); // проверяем совпадение гласных в тексте
	if (!wParts) {
		// return word; // если нет гласных, возвращаем текст как есть назад
		continue
	}
	start = wParts[1];
	rv = wParts[2];
		temp = rv.replace(PERFECTIVEGROUND_2, "");
		if (temp == rv) {
			temp = rv.replace(PERFECTIVEGROUND_1, '$1');
		}
		if (temp == rv) {
			rv = rv.replace(REFLEXIVE, "");
			temp = rv.replace(ADJECTIVE, "");
			if (temp != rv) {
				rv = temp;
				temp = rv.replace(PARTICIPLE_2, "");
				if (temp == rv) {
					rv = rv.replace(PARTICIPLE_1, '$1');
				}
			} else {
				temp = rv.replace(VERB_2, "");
				if (temp == rv) {
					temp = rv.replace(VERB_1, '$1');
				}
				if (temp == rv) {
					rv = rv.replace(NOUN, "");
				} else {
					rv = temp;
				}
			}
		} else {
			rv = temp;
		}
		rv = rv.replace(I, "");
		if (rv.match(DERIVATIONAL)) {
			rv = rv.replace(DER, "");
		}
		temp = rv.replace(P, "");
		if (temp == rv) {
			rv = rv.replace(SUPERLATIVE, "");
			rv = rv.replace(NN, 'н');
		} else {
			rv = temp;
		}
		
		temp = start + rv
		if (restoreIO) {
			lenw = temp.length
			temp = orig.substring(0, lenw)
			restoreIO=0
		}
		
		if (oDict.Exists(temp)) {
			oDict.Item(temp) += 1;
		} else {
			oDict.Item(temp) = 1;
		}
	};
	ares = (new VBArray(oDict.Keys())).toArray();
	ares = ares.sort();
	result = ares.join("\n");
	return result;
};

Re: Scripts collection

Posted: Thu Sep 05, 2024 12:33 pm
by ewild

Code: Select all

// https://akelpad.sourceforge.net/forum/viewtopic.php?t=240&p=36478#p36478
// description: Pass selected text to the default browser
// version: 2024-09-14
// author: ewild
//// based on: InternetRequest.js (v2.2, 2015.04.01) by VladSh
//// https://akelpad.sourceforge.net/forum/viewtopic.php?t=204&p=7303#p7303
// Examples ("$text" is a selected text placeholder):
//// predefined url:
// "Translate: Google: Auto > Ua" Call("Scripts::Main",1,"InternetRequestFix.js","https://translate.google.com/sl=auto&tl=uk&text=$text")
// "Translate: DeepL : Auto > Ua" Call("Scripts::Main",1,"InternetRequestFix.js","https://www.deepl.com/translator#auto/uk/$text")
//// just a text*:
// "Google Search for selected text in default browser" Call("Scripts::Main",1,"InternetRequestFix.js","$text")
// *note: if a valid URL is recognized within the selected text at the beginning it will be opened in the default browser instead

pArgLine=AkelPad.GetArgLine();
if (pArgLine) {
pSelText=AkelPad.GetSelText();
pSelText=pSelText.replace(/ +/g," ");
pSelText=pSelText.replace(/^\s+|\s+$/gm,"");
if (pArgLine.indexOf('deepl') > -1) {
pSelText=pSelText.replace(/[\x2f\x7c]/g,"");}
pSelText=encodeURIComponent(pSelText);
if (pSelText) {
pLink = WScript.Arguments(0).replace('$text',pSelText);
objShell = new ActiveXObject("WScript.Shell");
if (pLink.search('^(http:|https:|www.|ftp:|notes:)') == -1)
pLink = 'https://www.google.com/search?q=' + pLink;
objShell.Run('"'+pLink+'"');}
}


The script is purposed to pass a selected text to the default browser.
  • If there's a predefined URL, the selected text will be processed by the receiving service, e.g. Google Translate, DeepL, etc.
  • If a valid URL is recognized within the selected text at the beginning, it will be opened in the default browser as such.
  • If the selected text is just a text it will be passed as a search query to Google Search.
Basically, this is a customized version of the original InternetRequest.js script by VladSh.
The main difference between the two is the way they handle selected text that a. contains double quotes ["], b. contains multiple lines, c. is simply a text and cannot be recognized as a [part of] valid URI.
  • if the selected text contains double quotes ["]:
    - The original script drops off everything after the very first double quote it meets.
    + The customized script will replace double quotes ["] with single quotes ['] to pass the selected text as a whole.
  • if the selected text contains multiple lines:
    - The original script glues the lines together without any separation which makes it difficult to properly process it further by the receiving services (translators, search engines, etc).
    + The customized script will join the lines via blank space(s) to pass the selected text as a whole readable line.
  • if the selected text is just a text (and cannot be recognized as a [part of] valid URI):
    - The original script adds 'http:\\' prefix to the selected text and then your browser tries to process all that as a URL, which makes no sense and fails.
    + The customized script will wrap the selected text into a valid request, for instance, as a Google Search request for the selected text as a whole.
Changes in version 2024-09-14

  1. The selected text is now being preprocessed with the encodeURIComponent() function within the script itself that brings the following positive effects:
    • multi-line text is now being passed as such (with line breaks); the receiving service would now process that properly (including keeping the multi-line structure intact, both within the input and output*); separate preprocessing of the line breaks in the script is no longer required.
    • double quotes are being passed as such; the receiving service would now process that properly (keeping the double quotes intact); separate preprocessing of the double quotes in the script is no longer required.
  2. If the receiving service is DeepL, slash and pipe characters ("/", \x2f; "|", \x7c, respectively) have to be removed from the text since the service cannot digest them within the externally passed URI (the service would drop off everything starting from the first "/" on; and would hang on when meets "|").
Notes:
* For Google Translate it may look as though the line breaks are gone within the translated text section, however, if you copy-paste the text from there back to AkelPad, the line breaks would be kept in place.**
** This might somehow not always be the case. For instance, if the target language is Russian, the line breaks are actually gone.

Re: Scripts collection

Posted: Sun Sep 15, 2024 1:32 pm
by ewild

Code: Select all

// https://akelpad.sourceforge.net/forum/viewtopic.php?t=240&p=36486#p36486
// version:     2024-09-15
// author:      ewild
// Description: URL download with wget into the Downloads folder
// Usage    (in URL menu): "URL download : wget" Call("Scripts::Main",1,"downloadWget.js","%u")

WshShell   =  new ActiveXObject("WScript.Shell");
$key       = 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders';
$name      = '{374DE290-123F-4565-9164-39C4925E467B}';
$downloads =  WshShell.RegRead($key+'\\'+$name);
$url       =  AkelPad.GetArgLine();
$a         =  AkelPad.GetAkelDir();
$bin       = $a+'\\..\\Get\\wget.exe'; // See: Note
$command   = '-S -N --no-if-modified-since --no-hsts -U=Mozilla --referer="'+$url+'" "'+$url+'" -P '+$downloads;
WshShell.Exec($bin+' '+$command);

// Note:
// Put there the actual path to the wget executable:
// - relative path to the AkepPad directory as in the script :: $bin = $a+'\\..\\Get\\wget.exe';
// - or literal path, something like                         :: $bin = 'd:\\Tools\\wget.exe';


The script uses wget.exe tool to download a URL into the Downloads folder.

Notes:
The script's intended use is from the URL menu.
The Downloads folder location is being retrieved from the Windows* registry.
A user has to put their actual path to the wget executable there in the script:
  • relative to the AkepPad directory like in the script :: $bin = $a + '\\..\\Get\\wget.exe';
  • or a literal one, something like :: $bin = 'd:\\Tools\\wget.exe'
The wget.exe command in the script is essentially, as follows:
wget.exe -S -N --no-if-modified-since --no-hsts -U=Mozilla --referer="$url" "$url" -P $downloads
This is pretty suitable for most user cases, but, of course, is not universal.
So feel free to modify it according to your specific needs, if any.

* This is a thing for more or less modern Windows.
Windows XP does not have a designated "Downloads" folder by default.
In such a case, a user might prefer to set a destination folder manually, for instance, putting a literal path for the $downloads variable, something of a kind:
  • $downloads = 'd:\\Path\\to\\My\\Custom\\Downloads\\Location\\';
  • $downloads = "%USERPROFILE%\\Downloads";

Re: Scripts collection

Posted: Sun Oct 27, 2024 7:24 pm
by dothen
Вертикальная разметка.

Code: Select all

// https://akelpad.sourceforge.net/forum/viewtopic.php?p=36535#p36535
// Version: 1.1 (2024.11.09)
// Author: dothen 
//
//
// Description(1033): Shows the vertical markup.
// Description(1049): Показывает вертикальную разметку.
//
// Usage:
// "VMarkup1" Call("Scripts::Main", 1, "VerticalMarkup.js") Icon(37)
// "VMarkup2" Call("Scripts::Main", 1, "VerticalMarkup.js", `-skip=10 -fill=1 -color=0x00C4CFCF`) Icon(37)
// "ClearVMarkup" Call("Scripts::Main", 1, "VerticalMarkup.js", `-clear=true`) Icon(37)

// Аргументы:
//      -skip     Число символов между линиями > 0 (по умолчанию 8).
//      -fill     Ширина линии в символах >= 0, если 0 то ширина = 1px (по умолчанию 0).
//      -color    Цвет линии в hex виде: 0x00BBGGRR (по умолчанию 0x00676767).
//      -clear    Если true то восстановить изначальный фон и левый отступ (по умолчанию false).
//
// Описание:
// Скрипт устанавливает фоновое изображение в виде вертикальных линий.
// Левый отступ автоматически устанавливается равным нулю.
// Имеет смысл применять с моноширинным шрифтом.

var hWndEdit = AkelPad.GetEditWnd();

if (!hWndEdit) WScript.Quit();

var nSkip   = AkelPad.GetArgValue("skip", 8);
var nFill   = AkelPad.GetArgValue("fill", 0);
var nColor  = AkelPad.GetArgValue("color", 0x00676767);
var ClearBk = AkelPad.GetArgValue("clear", false);

if (nSkip < 1 || nFill < 0)
{
    WScript.Echo("Warning:    -skip=" + nSkip + "  -fill=" + nFill);
    WScript.Quit();
}

var oSys = AkelPad.SystemFunction();
var hMainWnd = AkelPad.GetMainWnd();

if (ClearBk)
{
    RestoreBackground();
    RestoreMarginLeft();
    WScript.Quit();
}

SetMarginLeft(0);
SetBackgroundImage();


function SetBackgroundImage()
{
    var nHeight = AkelPad.SendMessage(hWndEdit, 3188 /*AEM_GETCHARSIZE*/, 0 /*AECS_HEIGHT*/, null);
    var nWidth  = AkelPad.SendMessage(hWndEdit, 3188 /*AEM_GETCHARSIZE*/, 1 /*AECS_AVEWIDTH*/, null);
    
    var lpaecolors = AkelPad.MemAlloc(68);
    
    AkelPad.MemCopy(lpaecolors, 0x00000008 /*AECLR_BASICBK*/, 3 /*DT_DWORD*/);
    AkelPad.SendMessage(hWndEdit, 3231 /*AEM_GETCOLORS*/, 0, lpaecolors);
    
    var BasicBk = AkelPad.MemRead(_PtrAdd(lpaecolors, 12), 3 /*DT_DWORD*/);
    
    var hDC     = oSys.Call("Gdi32::CreateCompatibleDC", 0);
    var hBitmap = oSys.Call("Gdi32::CreateBitmap", nWidth * (nSkip + nFill), nHeight, 1, 32, 0);
    var hBmOld  = oSys.Call("Gdi32::SelectObject", hDC, hBitmap);
    
    oSys.Call("Gdi32::SelectObject", hDC, oSys.Call("Gdi32::GetStockObject", 18 /*DC_BRUSH*/));
    oSys.Call("Gdi32::SelectObject", hDC, oSys.Call("Gdi32::GetStockObject", 19 /*DC_PEN*/));
    
    oSys.Call("Gdi32::SetDCPenColor", hDC, BasicBk);
    oSys.Call("Gdi32::SetDCBrushColor", hDC,BasicBk);
    oSys.Call("Gdi32::Rectangle", hDC, 0, 0, nWidth * nSkip, nHeight);
    
    if (nFill == 0)
    {
        oSys.Call("Gdi32::SetDCPenColor", hDC, nColor);
        oSys.Call("Gdi32::MoveToEx", hDC, nWidth * nSkip - 1, 0, null);
        oSys.Call("Gdi32::LineTo", hDC, nWidth * nSkip - 1, nHeight);
    }
    else
    {
        oSys.Call("Gdi32::SetDCPenColor", hDC, nColor);
        oSys.Call("Gdi32::SetDCBrushColor", hDC, nColor);
        oSys.Call("Gdi32::Rectangle", hDC, nWidth * nSkip, 0, nWidth * (nSkip + nFill), nHeight);
    }
    
    oSys.Call("Gdi32::SelectObject", hDC, hBmOld);
    oSys.Call("Gdi32::DeleteDC", hDC);
    AkelPad.MemFree(lpaecolors);
    
    AkelPad.SendMessage(hWndEdit, 3391 /*AEM_SETBACKGROUNDIMAGE*/, hBitmap, 128);
}

function RestoreBackground()
{
    var pBkImageFile      = AkelPad.SendMessage(hMainWnd, 1223 /*AKD_GETFRAMEINFO*/, 140 /*FI_BKIMAGEFILE*/, 0);
    var hBkImageBitmapCur = AkelPad.SendMessage(hWndEdit, 3390 /*AEM_GETBACKGROUNDIMAGE*/, 0, 0);
    var hBkImageBitmapOld = AkelPad.MemRead(_PtrAdd(pBkImageFile, 524 /*MAX_PATH * 2 + sizeof(int)*/), 2 /*DT_QWORD*/);

    if (hBkImageBitmapCur != hBkImageBitmapOld)
        AkelPad.SendMessage(hWndEdit, 3391 /*AEM_SETBACKGROUNDIMAGE*/, hBkImageBitmapOld, 128);
}

function RestoreMarginLeft()
{
    var oSet = AkelPad.ScriptSettings();
    var lpBuffer;
    var nMarginLeftFromIni = 0;

    if (oSet.Begin("", 0x41 /*POB_READ|POB_PROGRAM*/))
    {
        lpBuffer = oSet.Read("MarginsEditRect", 2 /*PO_BINARY*/);
        nMarginLeftFromIni = AkelPad.MemRead(lpBuffer, 3 /*DT_DWORD*/);
        AkelPad.MemFree(lpBuffer);
        oSet.End();
    }
    SetMarginLeft(nMarginLeftFromIni);
}

// Based on Instructor's code
// https://akelpad.sourceforge.net/forum/viewtopic.php?p=36551#p36551
function SetMarginLeft(nMarginLeft)
{
    //Get margins
    var lpMargins = AkelPad.SendMessage(hMainWnd, 1223 /*AKD_GETFRAMEINFO*/, 79 /*FI_RECTMARGINS*/, 0);
    var nMarginLeftCur = AkelPad.MemRead(_PtrAdd(lpMargins,  0), 3 /*DT_DWORD*/);
    
    var lpRect;
    var lpFrameInfo;
    
    if (nMarginLeftCur != nMarginLeft)
    {
        if (lpRect = AkelPad.MemAlloc(16 /*sizeof(RECT)*/))
        {
            oSys.Call("kernel32::RtlMoveMemory", lpRect, lpMargins, 16 /*sizeof(RECT)*/);
          
            //New left margin
            AkelPad.MemCopy(_PtrAdd(lpRect, 0) /*offsetof(RECT, left)*/, nMarginLeft, 3 /*DT_DWORD*/);
          
            //Set margins
            lpFrameInfo = AkelPad.MemAlloc(_X64?16:8 /*sizeof(FRAMEINFO)*/);
            AkelPad.MemCopy(_PtrAdd(lpFrameInfo, 0) /*offsetof(FRAMEINFO, nType)*/, 26 /*FIS_RECTMARGINS*/, 3 /*DT_DWORD*/);
            AkelPad.MemCopy(_PtrAdd(lpFrameInfo, _X64?8:4) /*offsetof(FRAMEINFO, dwData)*/, lpRect, 2 /*DT_QWORD*/);
            AkelPad.SendMessage(hMainWnd, 1220 /*AKD_SETFRAMEINFO*/, lpFrameInfo, 0);
          
            AkelPad.MemFree(lpRect);
        }
    }
}


Re: Scripts collection

Posted: Tue Nov 12, 2024 10:53 pm
by xOleg
MathCad "на минималках". Математические вычисления непосредственно в редактируемом тексте.

[+] Добавил подсветку вычислений средствами плагина Coder, если он установлен.
[+] Добавил возможность копировать результат вычислений в буфер обмена.

Code: Select all

//
// Description(1049): Математические вычисления непосредственно в редактируемом тексте.
// Description(1033): Mathematical calculations directly in the edited text.
//
// https://akelpad.sourceforge.net/forum/viewtopic.php?p=36562#p36562
// Author: xOleg
// Version: 3.4
//
// MathCad "на минималках". При запуске скрипт вытянет из текста выражение слева от
// курсора или под курсором, посчитает, и вставит ответ здесь же, в тексте. Выделять
// текст не обязательно, но возможно, если нужно точно указать границы выражения.
//
// После вычисления тело расчета (в MathCad'е это называется регионом) будет подсвечено
// средствами плагина "Coder" (если установлен) и окружено фигурными скобками.
//
// Когда нужна повышенная точность, можно поставить двойное равенство (==).
//     {PI} = 3.1415; {PI} == 3.14159265;  {E} = 2.7182; {E} == 2.71828183 
//
// Между цифрами и операторами допустим один пробел. Два и более пробела,
// соответственно, отделяют вычисление от остального текста. Разряды в цифрах можно
// разделить апострофами (') или пробелами.
//     {50'000 + 30'000} = 80'000;  {40 000 - 10 000} = 30'000 
//
// У больших чисел (от миллиона) дробная часть отбрасывается (по умолчанию, и если
// нет дробей в левой части от тысячи; зависит от настроек). Если она вдруг
// понадобится, есть возможность указать повышенную точность через двойное равенство.
// Десятичная точка оставлена как индикатор.
//     {5'000'000 / 1.3} = 3'846'153.;  {1'000'000.1234} = 1'000'000.1234 
//
// Поддерживается отображение результата в "денежном" ($=) и "процентном" (%=)
// форматах, при которых ответ в любом случае будет содержать два десятичных знака.
// В левой части выражения (той, что будет в фигурных скобках) возможны
// комментарии на кириллице.
//     {[350'000 руб. * .21% годовых] / 12 месяцев} $= 6125.00 руб. 
//
// Допустимы многострочные вычисления. Выражение в этом случае необходимо выделить.
//     {Категория А: 30'000.00 руб. + 20'000.00 + 10'000.00 + …
//      Категория Б: 20'000.00 руб. + 10'000.00 + …
//      Категория В: 10'000.00 руб.
//                                       } $= 100'000.00 руб. 
//
// При необходимости текст можно здесь же поправить и пересчитать.
//
// Для того чтобы откатить вычисления, т.е. произвести операцию, противоположную 
// основному функционалу, можно вызвать скрипт с аргументом seltype=-1.
//
// Всегда хотел утереть нос Аллену Раздову ;-)
//
// Вызов горячими клавишами (примеры):
//     Посчитать (рекомендую Ctrl + .): 
//         Call("Scripts::Main", 1, "MathCalc.js", `-selcolor=turquoise -seltype=2`)
//         Call("Scripts::Main", 1, "MathCalc.js", `-selcolor=off -sepdigits=0`)
//         Call("Scripts::Main", 1, "MathCalc.js", `-seltype=2`)
//     Посчитать + скопировать в буфер обмена (Ctrl + Shift + .):
//         Call("Scripts::Main", 1, "MathCalc.js", `selcolor=orange -clipboard=2`)
//         Call("Scripts::Main", 1, "MathCalc.js", `-clipboard`)
//     Откатить (Ctrl + Shift + ,):
//         Call("Scripts::Main", 1, "MathCalc.js", `-selcolor=off -seltype=-1`)
//         Call("Scripts::Main", 1, "MathCalc.js", `-seltype=-1`)
//
// Аргументы ((*) - по умолчанию):
//     -selcolor - Подсветка вычислений средствами плагина "Coder", если установлен.
//         color (turquoise, orange, etc) - Будет использован стандартный цвет (*).
//         #RRGGBB (#9BFFFF, #FFCD9B, etc) - Будет использован код цвета.
//         off - Подсветка отключена.
//     -clipboard - Скопировать результат вычисления также и в буфер обмена.
//         2 - Вычисления с копированием в буфер и без подсвечены раздельно (*).
//         1 - Одинаковая подсветка для всех операций.
//     -sepdigits - Указывает, как разделить разряды цифр в результате расчета.
//         1 - Всегда разделять апострофами (') разряды для больших чисел (*).
//         0 - Разделить, если апострофы уже есть в левой части выражения.
//     -bigones - Задает, отбрасывать ли дробную часть у больших чисел.
//         1 - У результата от миллиона дробная часть удаляется (*).
//         0 - Формат в соответствии с общими настройками.
//     -seltype - Определяет, как скрипт выделит тело выражения после расчета.
//         2 - Вычисленное выражение будет помещено в фигурные скобки (*).
//         1 - Выделит только подсветкой (Coder'а или системной).
//         0 - Без выделения (крайне не советую).
//

var pCalcCharsRegExp = "([\{\}\\[\\]\|\(\)\*\/\+\\-$€'%\\w\,\.\…\=]+[\xA0 ]?)+|[\\r\\n]";
var pSelColor = AkelPad.GetArgValue("selcolor", "turquoise");
var nSepDigits = Number(AkelPad.GetArgValue("sepdigits", "1"));
var nClpType = Number(AkelPad.GetArgValue("clipboard", "0"));
var nBigOnes = Number(AkelPad.GetArgValue("bigones", "1"));
var nSelType = Number(AkelPad.GetArgValue("seltype", "2"));

if (pSelColor.toLowerCase() == "off") pSelColor = "";
if (!AkelPad.GetEditWnd()) WScript.Quit();

var nSelStart = nCrtStart = nExpStart = AkelPad.GetSelStart();
var nSelEnd = AkelPad.GetSelEnd();
var pSelText = pSelDzen = AkelPad.GetSelText();

var pDefColor = "#9BFFFF", nId = 11; /*turquoise*/
if (nClpType == 2)
{   pDefColor = "#FFCD9B"; nId = 12; /*orange*/
}

var pEqlOper = " = ", pExpTail = pDefTail = " ", bSelect = false;
var pEvlText, nEvlText;

// ----------------
                                  // Извлекаем математическое выражение, если не выделено
if (!pSelText)
{   AkelPad.TextFind(0, pCalcCharsRegExp, 0x00180000 /*FRF_REGEXP|FRF_UP*/);
    nSelStart = AkelPad.GetSelStart(); AkelPad.SetSel(nSelStart, nSelStart);

    AkelPad.TextFind(0, pCalcCharsRegExp, 0x00080001 /*FRF_REGEXP|FRF_DOWN*/);
    nExpStart = AkelPad.GetSelStart();
}
else                                               // Обрабатываем, если таки выделено
{   AkelPad.SetSel(nSelStart, nSelStart);
    AkelPad.TextFind(0, "[^\\r\\n]*\\S", 0x00080001 /*FRF_REGEXP|FRF_DOWN*/);
    nSelStart = AkelPad.GetSelStart();

    AkelPad.SetSel(nSelStart, nSelStart);
    AkelPad.TextFind(0, "\\S", 0x00080001 /*FRF_REGEXP|FRF_DOWN*/);
    nExpStart = AkelPad.GetSelStart();

    if (nExpStart < nSelEnd)
    {   AkelPad.SetSel(nSelStart, nSelEnd);
    }
    else
    {   AkelPad.SetSel(nCrtStart, nSelEnd);
        WScript.Quit();
    }
}                                                  // Запоминаем параметры выделения

nSelStart = AkelPad.GetSelStart(); nSelEnd = AkelPad.GetSelEnd();
pSelText = pSelDzen = AkelPad.GetSelText();
                                                   // Сохраняем, какое у нас равенство
pEqlOper = pSelText.match(/([\$\€\%]?[\=]+)/g);
pEqlOper = " " + (pEqlOper ? pEqlOper[pEqlOper.length - 1] : "=") + " ";

if (pSelColor)                                     // Устанавливаем цвет подсветки
{   switch (pSelColor.toLowerCase())
    {   case "turquoise": case "бирюзовый":  pSelColor = "#9BFFFF"; nId = 11; break;
        case "orange":    case "оранжевый":  pSelColor = "#FFCD9B"; nId = 12; break;
        case "yellow":    case "желтый":     pSelColor = "#FFFF9B"; nId = 13; break;
        case "violet":    case "фиолетовый": pSelColor = "#BE7DFF"; nId = 14; break;
        case "green":     case "зеленый":    pSelColor = "#88E188"; nId = 15; break;
    }

    pSelColor = pSelColor.replace(/^[\#]*([0-9A-F]{6})[\S\s]*$/i, "#$1");
    if (!pSelColor.match(/^[\#][0-9A-F]{6}$/i))
        pSelColor = pDefColor;

}                                                  // Чистим захваченную ранее строку

if (nSepDigits) pSelText = pSelText.replace(/(\d)[ ](?=\d)/g, "$1'");

pSelText = pSelText.replace(/(\d)[\,](?=\d)/g, "$1.");
pSelText = pSelText.replace(/[\xA0]/g, " ");
                                                   // Отрабатываем "денежный" формат
if (pSelText.match(/[\$]+[\=]+/))
{   pSelText = pSelText.replace(/([\=]+[\s]*[\-]?[\d\'\.]+)[ ]*[а-я]+\./gi, "$1");

    switch (true)
    {   case Boolean(!pEqlOper.match(/[\$]+[\=]+/)): pExpTail = pDefTail; break;
        case Boolean(pSelText.match(/((бук|бкз)\.|буказоид)/i)):
                                                     pExpTail = " бкз."; break;
        case Boolean(pSelText.match(/(руб)(\.|л)/i)):
                                                     pExpTail = " руб."; break;
    }

    if (pExpTail != pDefTail) pExpTail += pDefTail;

}                                                  // Устанавливаем скрытые скобки

pSelText = pSelText.replace(/([\=]+[\s]*[\-]?[\d\'\.]+)([\s]*[\*\/])/g, "$1|$2");

                                                   // Убираем предыдущие вычисления

pSelText = pSelText.replace(/[\s]*[$€%]?[\=]+[\s]*([\-]?[\d\#\'\.]+|Error|$)/gi, "");

if (pSelText.match(/[\r\n]+[^\{\r\n]*[\}]/))
    pSelText = pSelText.replace(/([\r\n]+)[\s]/g, "$1");

while (pSelText.match(/[\{]([\S\s]*?)[\}]/))
    pSelText = pSelText.replace(/[\{]([\S\s]*?)[\s]*[\}]/, "$1");

pSelText = pSelText.replace(/[\s]+([\r\n]+|$)/g, "$1");
pSelText = pSelText.replace(/[\s]*[\#][\s]*$/, "");

                                                   // Обрабатываем переносы строк

pSelText = pSelText.replace(/([^\*\/\+\-\:\…\s])[\s]*([\r\n]+)/g, "$1 +$2");
pSelText = pSelText.replace(/([\*\/\+\-])[\s]*([\r\n]+)/g, "$1 …$2");

                                                   // Разворачиваем скрытые скобки
pSelText = pSelText.replace(/^([\|]+[\s]*)+/, "");
pSelText = pSelText.replace(/([\|])[\s]*(\d)/g, "$1 * $2");
pSelText = pSelText.replace(/[\s]+([\|])/g, "$1");

while (pSelText.match(/[\|]/))
    pSelText = pSelText.replace(/^([\s]*)([\S\s]*?)[\|]+/, "$1($2)");

if (!pSelText.match(/[0-9a-z]/i))                  // Выходим, если нечего считать
{   AkelPad.SetSel(nCrtStart, nCrtStart);
    WScript.Quit();
}                                                  // Удаляем текст пользователя

pEvlText = pSelText.replace(/([\$\€\%а-яё\…_]+[\:\,\.]?[\s]?)+/gi,"");
pEvlText = pEvlText.replace(/(\d)[\' ](?=\d)/g, "$1");

if (pEvlText.match(/\d{4}\.\d/)) nBigOnes = 0;     // Настраиваем для больших чисел

pEvlText = pEvlText.replace(/[\[]/g, "(");         // Корректируем скобки
pEvlText = pEvlText.replace(/[\]]/g, ")");

try                                                // Считаем...
{   with (Math)
        pEvlText = String(nEvlText = Number(eval(pEvlText).toFixed(8)));
    if (Math.abs(nEvlText) < 1e-6)
        pEvlText = "0";
}
catch (oError) 
{   pEvlText = "Error";
}
                                                   // Устанавливаем формат вывода
switch (true)
{   case Boolean(pEqlOper.match(/[\=]{2,}/)): break;
    case Boolean(pEqlOper.match(/[\$\€\%]+[\=]+/)):
        pEvlText = Number(pEvlText).toFixed(8);
        pEvlText = pEvlText.replace(/(\.\d{2})\d*$/, "$1");
        break;
    case nBigOnes && Boolean(pEvlText.match(/^[\-]?\d{7,}[\.]/)):
        pEvlText = pEvlText.replace(/(\.\d{0})\d*$/, "$1");
        break;
    case Boolean(pEvlText.match(/^[\-0]*\.0/)):
        pEvlText = pEvlText.replace(/(\.\d{8})\d*$/, "$1");
        break;
    default:
        pEvlText = pEvlText.replace(/(\.\d{4})\d*$/, "$1");
        break;
    }
                                                   // Расставляем апострофы в ответе
if (nSepDigits || pSelText.match(/[\']/))
{   if (pEvlText.match(/[\.]/))
        pEvlText = pEvlText.replace(/(\d{2})(\d{3}\.\d*)$/, "$1'$2");
    else
        pEvlText = pEvlText.replace(/(\d{2})(\d{3})$/, "$1'$2");

    while (pEvlText.match(/\d{4}\'/))
        pEvlText = pEvlText.replace(/(\d)(\d{3}\')/, "$1'$2");

}                                                  // Обрабатываем "дзен"-режим

if (pSelDzen.match(/([\S\s]*[\r\n]){2,}[\s]*[\#][\s]*$/))
{   if (pSelDzen.match(/[\*\/\-\=][\s]*([\r\n]+|$)/)) pEvlText = "Error";

    pSelDzen = pSelDzen.replace(/[\s]*[\#][\s]*$/, "");

    pSelText = pSelDzen;
}                                                  // Проверяем на ошибки операций

if (pEvlText.match(/[a-z]+/i)) { pEvlText = "Error"; pExpTail = pDefTail; }

if (nClpType)                                      // Копируем в буфер обмена
    AkelPad.SetClipboardText(pEvlText);
                                                   // Форматируем переносы строк
if (pSelText.match(/[\r\n]+/))
{   switch (nSelType)
    {   case 2: case 1: case 0:
            pSelText += evlPadding(pSelText, pEvlText, pExpTail);
            pExpTail += "\r\n";
            break;
        default:
            pSelText = pSelText.replace(/[\…]/g, "");
            pDefTail += "\r\n";
            break;
    }
    pSelColor = "";
}

function evlPadding(vExpress, pEval, pTail)
{   var pPadding = "\r\n", nPadLen = 0;

    vExpress = vExpress.replace(/[\s]+([\r\n]+|$)/g, "$1");
    vExpress = vExpress.match(/[^\r\n]+/g);
    
    for (var i = 0; i < vExpress.length; i++)
        if (nPadLen < vExpress[i].length)
            nPadLen = vExpress[i].length;

    nPadLen -= pEval.length + pTail.length + 6;
    for (i = 0; i < nPadLen; i++) pPadding += " ";

    return pPadding;
}
                                                   // Отрабатываем аргументы
switch (nSelType)
{   case 2: 
        pSelText = pSelText.replace(/^([\s]*)([\S\s]*)$/, "$1{$2}");
        pSelText = pSelText.replace(/([\r\n]+)/g, "$1 ");
        pSelText += pEqlOper + pEvlText + pExpTail;
        bSelect = false;
        break;
    case 1:
        pSelText += pEqlOper + pEvlText + pExpTail;
        bSelect = true;
        break;
    case 0:
        pSelText += pEqlOper + pEvlText + pExpTail;
        bSelect = false;
        pSelColor = "";
        break;
    default:
        pSelText += pDefTail;
        break;
}
                                                   // Выводим результат
AkelPad.ReplaceSel(pSelText, -1);

nSelStart = AkelPad.GetSelStart(); nSelEnd = AkelPad.GetSelEnd();

if (pSelColor && AkelPad.IsPluginRunning("Coder::HighLight"))
{   AkelPad.SetSel(nExpStart, nSelEnd - 1);
    AkelPad.Call("Coder::HighLight", 2, 0, pSelColor, 1, 0, nId);
    AkelPad.SetSel(nSelEnd, nSelEnd);
}
else
{   if (bSelect)
        AkelPad.SetSel(nSelStart, nSelEnd);
    else
        AkelPad.SetSel(nSelEnd, nSelEnd);
}


Re: Scripts collection

Posted: Mon Dec 02, 2024 7:02 am
by xOleg
Исправление "на лету" (без выделения, по горячей клавише) текста, набранного в неверной раскладке. Клавиатуру тоже переключит.

Code: Select all

//
// Description(1049): Исправление "на лету" текста, набранного в неверной раскладке.
// Description(1033): You don't need this.
//
// https://akelpad.sourceforge.net/forum/viewtopic.php?p=36613#p36613
// Author: xOleg
// Version: 1.8
//
// Переводит слово под курсором или слева от курсора в иную раскладку. Локальные
// страницы 1049 и 1033. Выделять не обязательно, но возможно, чтобы указать скрипту,
// что исправлять. Клавиатуру тоже переключит. Удобно повесить на какую-нибудь
// комбинацию с Backspace. Например, Shift + Backspace, если она у вас не занята.
//
// Вызов горячей клавишей:
//     Call("Scripts::Main", 1, "KeybMagic.js")
//
// Примеры:
//     Ghbdtn!  -> Привет!     Array[ш]; -> Array[i];
//     10ю12ю24 -> 10.12.24    "Вщддн"   -> "Dolly"
//     (Руддщ!) -> (Hello!)
//

var pEngCapsOn  = "QWERTYUIOP_\\{\\}ASDFGHJKL\\:\\\"ZXCVBNM\\<\\>\\~\\#\\?";
var pEngCapsOff = "qwertyuiop\\[\\]asdfghjkl\\;\\'zxcvbnm\\,\\.\\`\\|\\/";
var pRusCapsOn  = "ЙЦУКЕНГШЩЗ__Х_ЪФЫВАПРОЛД_Ж_ЭЯЧСМИТЬ_Б_Ю_Ё_№\\,";
var pRusCapsOff = "йцукенгшщз_х_ъфывапролд_ж_эячсмить_б_ю_ё\\/\\.";
var pCommon     = "\\!\\?\\%\\*\\/\\+\\-\\{\\}\\[\\]\\(\\)0123456789_";
    pCommon    += "\\\"\\'\\<\\>\\:\\;\\,\\.\\=";

var EngKeyboard = new Keyboard(pEngCapsOn + pEngCapsOff + pCommon, 1033);
var RusKeyboard = new Keyboard(pRusCapsOn + pRusCapsOff + pCommon, 1049);

function Keyboard(pLayout, nLocale)                        // Клавиатура, как она есть
{   this.layout = pLayout + " "; this.locale = nLocale;
    this.explayout = new RegExp("^[" + pLayout + "]+$");
    this.expcommon = new RegExp("^[" + pCommon + "]+$");

    this.Extend = function() { return new RegExp("^[" + this.layout + "]+$"); }

    this.Select = function(nCrtPosition)           // Ищем и выделяем, что исправлять
    {   var pSelText = "", i = 1, j = 1;
                                                   // Сперва ищем до конца слова
        do
        {   AkelPad.SetSel(nCrtPosition, nCrtPosition + i++);
            pSelText = AkelPad.GetSelText();
            if (pSelText.length < i - 1) break;
        }
        while (pSelText.match(this.explayout));

        nCrtPosition = nCrtPosition + i - 2;
                                                   // Теперь ищем до начала слова
        do
        {   if (nCrtPosition - j < 0) { j++; break; }
            AkelPad.SetSel(nCrtPosition - j++, nCrtPosition);
            pSelText = AkelPad.GetSelText();
        }
        while (pSelText.match(this.explayout));
                                                             // Отдаем результат
        AkelPad.SetSel(nCrtPosition - j + 2, nCrtPosition);
        pSelText = AkelPad.GetSelText();

        if (pSelText.match(this.expcommon))
        {   AkelPad.SetSel(nCrtPosition, nCrtPosition);
            pSelText = "";
        }

        return pSelText;

    }
}
                                                   // Блок определений и переменных
// ----------------

var nIsSelect = Number(AkelPad.GetArgValue("isselect", "0"));
if (nIsSelect > 0) nIsSelect = -1;

AkelPad.SetSel(AkelPad.GetSelStart(), AkelPad.GetSelEnd());
if (!AkelPad.GetEditWnd()) WScript.Quit();

var nCrtPlace = AkelPad.GetSelStart();
var pSelText = AkelPad.GetSelText();

var rExp = new RegExp("^[" + pCommon + "\\s\\r\\n]+$");
if(pSelText.match(rExp)) WScript.Quit();

var pSrcLayout = "", pTgtLayout = "";
var nTgtLocale = 1033;
var pNewText = "";

// ----------------
                                  // Пользователь выделил текст. Проверяем на буржуйскую
if (AkelPad.GetSelText())                                                  // раскладку
{   switch (true)
    {   case Boolean(pSelText.match(EngKeyboard.Extend())):
            nTgtLocale = RusKeyboard.locale; nIsSelect = -1;
            pSrcLayout = EngKeyboard.layout;
            pTgtLayout = RusKeyboard.layout;
            break;                                 // Проверяем на российскую раскладку

        case Boolean(pSelText.match(RusKeyboard.Extend())):
            nTgtLocale = EngKeyboard.locale; nIsSelect = -1;
            pSrcLayout = RusKeyboard.layout;
            pTgtLayout = EngKeyboard.layout;
            break;
    }
}
                                  // Пользователь не выделил текст. Ищем слово слева
if (!AkelPad.GetSelText())
{   AkelPad.TextFind(0, "([^\\xA0\\s]|[\\r\\n])", 0x00180000); //FRF_REGEXP|FRF_UP

    nCrtPlace = AkelPad.GetSelEnd();
    AkelPad.SetSel(nCrtPlace, nCrtPlace);
                                                   // Проверяем на буржуйскую раскладку
    switch (true)
    {   case Boolean(EngKeyboard.Select(nCrtPlace)):
            nTgtLocale = RusKeyboard.locale; nIsSelect = 0;
            pSrcLayout = EngKeyboard.layout;
            pTgtLayout = RusKeyboard.layout;
            break;                                 // Проверяем на российскую раскладку

        case Boolean(RusKeyboard.Select(nCrtPlace)):
            nTgtLocale = EngKeyboard.locale; nIsSelect = 0;
            pSrcLayout = RusKeyboard.layout;
            pTgtLayout = EngKeyboard.layout;
            break;
    }
}
                                                   // Исправляем...
pSelText = AkelPad.GetSelText();

for (var i = 0; i < pSelText.length; i++)
{   pNewText += pTgtLayout.charAt(pSrcLayout.indexOf(pSelText.charAt(i)));
}
                                                   // Корректируем...

rExp = new RegExp("[Х](.*?)Ъ(.?([^А-Я]|$))", "g");
while (pNewText.match(rExp)) pNewText = pNewText.replace(rExp, "{$1}$2");
rExp = new RegExp ("Ъ(.?([^А-Я]|$))", "g");
while (pNewText.match(rExp)) pNewText = pNewText.replace(rExp, "}$1");

rExp = new RegExp("[х](.*?)ъ(.?([^а-я]|$))", "g");
while (pNewText.match(rExp)) pNewText = pNewText.replace(rExp, "[$1]$2");
rExp = new RegExp ("ъ(.?([^а-я]|$))", "g");
while (pNewText.match(rExp)) pNewText = pNewText.replace(rExp, "]$1");

rExp = new RegExp("[Э](.*?[а-я]+[^\\s]?[\\\"]*)Э");
while (pNewText.match(rExp)) pNewText = pNewText.replace(rExp, "\"$1\"");
rExp = new RegExp ("([а-я]+[^\\s]?[\\\"]*)Э");
while (pNewText.match(rExp)) pNewText = pNewText.replace(rExp, "$1\"");

pNewText = pNewText.replace(/([а-я]+[\}\]\)\"\.]*)Ж/g, "$1:");
pNewText = pNewText.replace(/([а-я][\}\]\)\"]+)ж/g, "$1;");

pNewText = pNewText.replace(/([а-я][\}\]\)\"]+)б/g, "$1,");
pNewText = pNewText.replace(/([а-я][\}\]\)\"]+)ю/g, "$1.");

                                                   // Заменяем...
if (pNewText)
{   AkelPad.SendMessage(AkelPad.GetEditWnd(), 0x50, 0, nTgtLocale);
    AkelPad.ReplaceSel(pNewText, nIsSelect);
}

if(!nIsSelect)
    AkelPad.SetSel(nCrtPlace, nCrtPlace);


Re: Scripts collection

Posted: Sat Dec 21, 2024 8:06 pm
by ewild
Sum all numbers in a text/selection and place the resulting expression = sum under the text.
Mode one: sum numbers respecting the minus sign in front of a number as a part of the expression (default).
Mode two: sum all numbers regardless of the minus sign.
Video illustration: https://i.imgur.com/eRtKa55.mp4

Code: Select all

// https://akelpad.sourceforge.net/forum/viewtopic.php?t=240&p=36657#p36657
// description: sum all numbers in a text/selection and put the resulting expression = sum under the text
//  * mode one: numbers with the minus sign are negative (default)
//  * mode two: numbers are positive regardless of the minus sign
// version: 2024-12-24
// author: ewild
// examples:
// "Sum in-text numbers" Call("Scripts::Main",1,"mathText.js")
// "Sum in-text numbers (all as positive)" Call("Scripts::Main",1,"mathText.js","+")

$start=AkelPad.GetSelStart();
$end=AkelPad.GetSelEnd();
if ($start == $end){AkelPad.SetSel(0,-1);$cr='';} else {$cr='\r';}
$text=AkelPad.GetSelText();

$ArgLine=AkelPad.GetArgLine();

if ($text.match(/\d/g)){ // do math if at least one digit exists in a text/selection

if ($ArgLine){  // make sum expression regardless of the minus sign
$math=$text.match(/\d+(\.\d+)?/g).join('+'); // extract and join numbers
$math=$math.replace(/^[0]+(\d)/g,"$1").replace(/([+])[0]+(\d)/g,"$1$2");} // remove meaningless zeros

if (!$ArgLine){ // make sum expression retaining the minus sign
$math=$text.match(/-?\d+(\.\d+)?/g).join('+').replace(/[+][-]/g,"-");
$math=$math.replace(/^[-]*[0]+(\d)/g,"$1").replace(/([+-])[0]+(\d)/g,"$1$2");}

$text=$text+'\r'+$math+' = '+eval($math)+$cr; // add the expression and the calculated sum to the text

AkelPad.ReplaceSel($text);

AkelPad.SetSel($start,$start);

}

Re: Scripts collection

Posted: Tue May 27, 2025 5:38 pm
by yozhic
Поправка к скрипту AkelPadManualSettings.js от KDJ: добавлен новый параметр ScrollPastEOF

Code: Select all

--- orig/AkelPadManualSettings.js	Tue Jul 19 18:00:00 2016
+++ fixd/AkelPadManualSettings.js	Tue May 27 19:10:30 2025
@@ -2 +2 @@
-// Version: 2016-07-19
+// Version: 2016-07-19 fix 1
@@ -11,0 +12,3 @@
+//
+// Fixes:
+//   27.05.2025 - Added: ScrollPastEOF option
@@ -752,0 +756,62 @@
+var oScrollPastEOF =
+{
+  Def  :0,
+  Wnd  :[],
+  IDTXT:2001,
+  IDVAL:2002,
+  IDDEF:2003,
+
+  Initialize:function()
+  {
+    this.Val = SendMessage(hMainWnd, 1222 /*AKD_GETMAININFO*/, 119 /*MIS_SCROLLPASTEOF*/, 0);
+    this.Ini = this.Val;
+
+    this.Wnd[this.IDTXT]={C:'STATIC',    S:0x50000000, X:140, Y:15, W:640, H:13, Txt:'Vertical scroll beyond last line. As a percentage of the editing area height:',
+                                                                                 Rus:'Вертикальная прокрутка за пределы последней строки. В процентах от высоты области редактирования:'};
+    this.Wnd[this.IDVAL]={C:'COMBOBOX',  S:0x50210003, X:140, Y:35, W: 50, H:21};
+    this.Wnd[this.IDDEF]={C:'BUTTON',    S:0x50010003, X:140, Y:66, W:640, H:16, Txt:'By default: 0 - turned off.',
+                                                                                 Rus:'По умолчанию: 0 - отключено.'};
+  },
+
+  Apply:function()
+  {
+    SendMessage(hMainWnd, 1219 /*AKD_SETMAININFO*/, 119 /*MIS_SCROLLPASTEOF*/, this.Val);
+  },
+
+  SetVal:function()
+  {
+    SendMessage(this.Wnd[this.IDVAL].HWND, 0x014E /*CB_SETCURSEL*/, SendMessage(this.Wnd[this.IDVAL].HWND, 0x0158 /*CB_FINDSTRINGEXACT*/, -1, this.Val.toString()), 0);
+    SendMessage(this.Wnd[this.IDDEF].HWND, 241 /*BM_SETCHECK*/, this.Val == this.Def, 0);
+  },
+
+  InitDialog:function()
+  {
+    for (var i = 0; i < 101; ++i)
+      SendMessage(this.Wnd[this.IDVAL].HWND, 0x0143 /*CB_ADDSTRING*/, 0, i.toString());
+    if (this.Ini > 100)
+      SendMessage(this.Wnd[this.IDVAL].HWND, 0x0143 /*CB_ADDSTRING*/, 0, this.Ini.toString());
+  },
+
+  Command:function(nID, nCode, hWnd)
+  {
+    if (nID == this.IDVAL)
+    {
+      if (nCode == 1 /*CBN_SELCHANGE*/)
+      {
+        bChanged = 1;
+        SendMessage(hWnd, 0x0148 /*CB_GETLBTEXT*/, SendMessage(hWnd, 0x0147 /*CB_GETCURSEL*/, 0, 0), lpText);
+        this.Val = parseInt(AkelPad.MemRead(lpText, 1 /*DT_UNICODE*/));
+        SendMessage(this.Wnd[this.IDDEF].HWND, 241 /*BM_SETCHECK*/, this.Val == this.Def, 0);
+      }
+      else if (nCode == 8 /*CBN_CLOSEUP*/)
+        bCloseCB = 0;
+    }
+    else if ((nID == this.IDDEF) && SendMessage(hWnd, 240 /*BM_GETCHECK*/, 0, 0))
+    {
+      bChanged = 1;
+      this.Val = this.Def;
+      this.SetVal();
+    }
+  }
+};
+
@@ -1472,0 +1538 @@
+  ["ScrollPastEOF",      oScrollPastEOF],

Code: Select all

MIME-Version: 1.0
Content-Type: application/octet-stream; name="AkelPadManualSettings.7z"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="AkelPadManualSettings.7z"

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
LTtH3iHP2wEVBgEAIAAAAAAA