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);
}