Page 2 of 3

Posted: Sun May 17, 2020 11:59 am
by DV
AZJIO wrote:флаг bOldWindows это в какой юникод не поддерживается? Ниже XP?
Да. Точнее, ниже 2000. Т.е. bOldWindows - это Windows 95, 98, Me.

Posted: Sun May 24, 2020 4:03 am
by AZJIO
Как передать в функцию параметры?
Например я вызываю "Coder::HighLight" с параметрами режим, цвет

Code: Select all

Call("Coder::HighLight", 2, "#71AE71", 0, 0, 0)
как мне принять эти параметры?
Получил указатель на элемент поля структуры, но как его считывать, как строку или как число, тут затык. То есть как определить тип данных в указателе до начала их чтения. Проверил что для стандартных плагов написанных на "Си" если вместо строки передать число и наоборот вместо числа - строку, то плаг не падает вместе с AkelPad, в моём же варианте падает.

Code: Select all

Procedure GetExtCallParam(*lParam, nIndex)
	If PeekI(*lParam) >= (nIndex + 1) * SizeOf(*lParam) ; если размер структуры больше или равен индексу умноженному на размер указателя структуры
		ProcedureReturn *lParam + nIndex * SizeOf(*lParam) ; возвращает указатель на поле структуры
	EndIf
	ProcedureReturn 0
EndProcedure

Procedure IsExtCallParamValid(*lParam, nIndex)
	If PeekI(*lParam) >= (nIndex + 1) * SizeOf(*lParam)
		ProcedureReturn #True
	EndIf
	ProcedureReturn #False
EndProcedure


; Внутреняя функция
; Пример вызова плагина "1test::test_Arg" с парамерами, например 12 или "привет".
ProcedureCDLL test_Arg(*pd.PLUGINDATA)
	Protected *nAction, nArg;, *Arg.ArgData
	If *pd\lParam
		*nAction = GetExtCallParam(*pd\lParam, 1)
		nArg = PeekI(*nAction) ; nArg это либо число, либо указатель на строку
		
		If nArg > 0 Or nArg < 13 ; если число больше 13, то не обязательно указатель
			MessageRequester("Строка", PeekS(nArg))
		Else
			MessageRequester("Число", Str(nArg))
		EndIf
	EndIf
EndProcedure



Code: Select all

if (IsExtCallParamValid(pd->lParam, 2))
nExtMenuIndex=(int)GetExtCallParam(pd->lParam, 2);
if (IsExtCallParamValid(pd->lParam, 3))
pSearchString=(unsigned char *)GetExtCallParam(pd->lParam, 3);

оригинал не содержит конвертирования, просто назначает формат (unsigned char *), при этом если указать в параметре число, то AkelPad не падает. Но как он будет читать число как строку, если нет нуль-терминированного символа NULL, он так всю память прочитает и должен упасть.

Попробовал использовать параметр в плаге ConvKey, параметр 2 - транслитерация

Posted: Sun May 24, 2020 11:29 am
by DV
В языке C (и затем в C++) строки являются NUL-terminated, т.е. для всех строковых констант вида "abc" и L"ABC" компилятор всегда добавляет "\0" в конце.
Передачу нескольких параметров в плагин можно в общем случае рассматривать как передачу нескольких переменных типа указатель или INT_PTR (т.е. каждый элемент имеет размер указателя: 4 байт для x86 и 8 байт для x64). Вот пример того, как плагин QSearch общается с плагином Coder:

Code: Select all

typedef struct sDLLECCODERSETTINGS_GETALIAS {
    UINT_PTR dwStructSize;
    INT_PTR nAction;
    HWND hWndEdit;
    AEHDOC hDoc;
    unsigned char* pszAlias;
} DLLECCODERSETTINGS_GETALIAS;

const char*    cszCoderSettingsA = "Coder::Settings";
const wchar_t* cszCoderSettingsW = L"Coder::Settings";

// Вызов функции плагина
static void CallPluginFuncW(const wchar_t* cszFuncW, void* pParams)
{
    PLUGINCALLSENDW pcsW;

    pcsW.pFunction = cszFuncW;
    pcsW.lParam = (LPARAM) pParams;
    pcsW.dwSupport = 0;

    SendMessageW( g_Plugin.hMainWnd, AKD_DLLCALLW, 0, (LPARAM) &pcsW );
}

// Вызов "Coder::Settings" с параметрами
static void CallCoderSettings(void* pstParams)
{
    if ( g_Plugin.bOldWindows )
    {
        CallPluginFuncA(cszCoderSettingsA, pstParams);
    }
    else
    {
        CallPluginFuncW(cszCoderSettingsW, pstParams);
    }
}

// Заполнение буфера pszAliasBufW
static void getCoderAliasW(wchar_t* pszAliasBufW)
{
    DLLECCODERSETTINGS_GETALIAS stParams;

    if ( pszAliasBufW )
        pszAliasBufW[0] = 0;

    stParams.dwStructSize = sizeof(DLLECCODERSETTINGS_GETALIAS);
    stParams.nAction = DLLA_CODER_GETALIAS;
    stParams.hWndEdit = NULL;
    stParams.hDoc = NULL;
    stParams.pszAlias = (unsigned char *) pszAliasBufW;

    CallCoderSettings( &stParams );
}

wchar_t szCoderAlias[MAX_CODERALIAS + 1];
getCoderAliasW(szCoderAlias);

Все элементы структуры DLLECCODERSETTINGS_GETALIAS являются либо указателями, либо INT_PTR, то есть имеют одинаковый размер. Именно так они передаются через стек.
В данном случае указание конкретных типов для каждого из элементов - фикция, сделанная чисто для удобства чтения кода. С тем же успехом мы могли бы указать тип void* или INT_PTR для всех элементов. Это потому, что на самом деле нам важно лишь выровнять расположение этих элементов в памяти так, чтобы оно соответствовало передаче параметров через стек (т.е. с выравниванием по размеру указателя).
Поясню эту мысль на примере: структура DLLECCODERSETTINGS_GETALIAS содержит элементы INT_PTR nAction и unsigned char* pszAlias. Для компилятора каждый из них - это просто 4 байт для x86 и 8 байт для x64. Интерпретация того, что один из них является числом (INT_PTR), а другой - указателем (unsigned char*) появляется исключительно в результате наших действий. Мы можем попытаться вычитать строку по адресу, указанному в nAction - и это либо приведёт к падению, либо вычитает случайный мусор. Мы можем использовать указатель (значение указателя) pszAlias как число - тогда значением этого числа будет адрес строки, на которую указывает pszAlias.
То есть, в общем случае, мы можем интерпретировать данные, полученные из плагина, или передаваемые в плагин, как угодно. Но эти данные будут иметь смысл (поддаваться верной интерпретации) только в том случае, когда наш код интерпретирует их точно так же, как вызванная функция плагина.

Posted: Sun May 24, 2020 1:39 pm
by AZJIO
DV
Я несколько раз переписывал функцию и уже выучил её в совершенстве, и вот к чему я пришёл:
Если мы задаём как интерпретировать параметр плагину, то здесь проблемы нет, потому что я знаю что я хочу получить. У нас функция GetExtCallParam возвращает указатель. Это указатель ссылается либо на INF (4/8 байтов) либо на указатель строки. Оба значения являются INT, то есть я не знаю число это или указатель, оно может быть и тем и тем.

Я говорил про защиту от дурака, если пользователь ввёл число, то я получаю число, а если пользователь указал строку, то я получаю указатель на строку. Если я ожидаю число 1 или 2, то если пользователь введёт 1 или 2 плаг выполнит по этим ожидаемым параметрам. Если пользователь введёт 3, то плаг сделает return, если пользователь введёт строку, которая возвратит указатель, который вряд ли будет 1 или 2, потому что начало памяти уже давно занято на несколько мегабайт, так что мы даже 100 не получим, а намного больше, соответственно плаг также сделает return.

А теперь другая ситуация, мы ожидаем строку, ожидаем указатель на строку, а пользователь ввёл какое то число, плаг это число интерпретирует как указатель и пытается получить с него данные. Тут прога может упасть. Я посмотрел код ContextMenu.c строка 679

Code: Select all

pSearchString=(unsigned char *)GetExtCallParam(pd->lParam, 3);
здесь мы получили указатель на строку в pSearchString, но не проверяем что там вводил пользователь и прога вполне может упасть, если пользователь ввел число. Но дальше в строке 688 указатель pSearchString скармливается функции MultiByteToWideChar. Не исключаю, что MultiByteToWideChar может как то определить что число переданное в MultiByteToWideChar не является указателем на строковые данные, возможно потому что находится вне области выделенной для AkelPad, поэтому AkelPad не падает. Но факт того что вводимые данные не проверяются я делаю вывод анализируя код. Просто нет ситуации при которой может это произойти.

Ранее я хотел сделать объединённую структуру, но она бы получилась при условии что я получаю указатель на строку или указатель на число, то есть я в любом случае бы получал указатель, тогда бы я начал интерпретацию данных, то есть я бы взял то поле структуры, которое соответствует типу, то есть я ожидаю строку я беру поле string и читаю его как строку не нуль-терминированную а по заданной ширине (строка-массив), далее если данные содержат бинарные символы я могу выкинуть предупреждение, что пользователь неверно указал в параметрах, если всё окей, но пользователь не получил того что хотел (ничего не произошло или произошло не так), то есть повод призадуматься, а правильно ли ввёл параметры, но в любом случае прога не падает, она просто интерпретирует какие то числа как строку. Но у нас ситуация другая, как я сказал выше, у нас не указатель на строку или указатель на число, у нас число как есть или указатель, а значит фактически я не могу сделать объединённую структуру на число и указатель строки, потому что в одном случае это конечные данные в другом ссылка.

Корректно ли сравнивать QSearch в данном случае, ведь он всегда читает с комбо строку. Если бы в параметрах указывалось бы число или строка типа для визуализации, а по факту я бы получил всегда указатель на строку, в которой содержится число, я бы мог его кончертировать в число, а по факту я не знаю получаю ли я число или получаю ли я указатель на строку. То что может вводится пользователем я раньше всегда рассматривал как строку и делал проверки приводя формат к нужному, а здесь я получаю через посредника, который ни каким флагом не определяет что передал мне. То есть я должен надеятся на пользователя, что у него хватает компетенции ввести параметры правильно, ну или неправильно и он сам гадает почему у него падает AkelPad, вылавливает строку которая к этому привела из сотни строк меню.

Если бы я получал всегда строку я бы проверл начинается ли текст с кавычек, содержит ли текст только цифры от 0 до 9, тогда бы я сам однозначно бы определил строка или число и конвертировал бы в число специальной функцией.

Posted: Sun May 24, 2020 4:55 pm
by DV
Такова природа работы со стеком. Сначала можно передать всё, что угодно, - а потом попытаться вычитать всё, что угодно.
Документация Coder-Rus.txt описывает передаваемые параметры, и следует передавать и принимать их точно так же: целые числа там, где ожидаются целые числа, и строки там, где ожидаются строки.
В своё время я задавал подобный вопрос Александру: может, имеет смысл добавить в AkelPad проверку параметров, передаваемых плагинами? На что получил ответ: AkelPad доверяет своим плагинам :)
Другая очевидная причина - быстродействие. Когда между редактором и его плагинами существует чёткий API, которому все следуют, то проверка типа каждого параметра является избыточной.

Posted: Thu May 28, 2020 10:27 am
by AZJIO
В общем добавил код в плаг ConvKey

Code: Select all

Protected lpIndex, nOffset1, nOffset2

SendMessage_(*pd\hWndEdit, #AEM_GETINDEX, #AEGI_CARETCHAR, @lpIndex)
MessageRequester("lpIndex 1", Str(lpIndex))

SendMessage_(*pd\hWndEdit, #AEM_GETINDEX, #AEGI_LINEBEGIN, @lpIndex)
MessageRequester("lpIndex 2", Str(lpIndex))
nOffset1 = SendMessage_(*pd\hWndEdit, #AEM_INDEXTORICHOFFSET, 0, @lpIndex)
MessageRequester("nOffset1", Str(nOffset1))

SendMessage_(*pd\hWndEdit, #AEM_GETINDEX, #AEGI_LINEEND, @lpIndex)
nOffset2 = SendMessage_(*pd\hWndEdit, #AEM_INDEXTORICHOFFSET, 0, @lpIndex) + 1
MessageRequester("nOffset1", Str(nOffset2))
ProcedureReturn
В строку 89 после вызова #EM_EXGETSEL64. Суть в следующем: сейчас плаг преобразует выделенное, но хотелось бы если не выделено, то выделить текст слева. Я сначала попробовал выделить имитацией хоткея "Ctrl+Shift+Стрелка влево", но у меня он срабатывает после завершения функции

Code: Select all

Procedure SendHotKeyCtrlShiftL(sleep = 20, sleep1=3)
	keybd_event_(#VK_SHIFT,0,#KEYEVENTF_EXTENDEDKEY,0)
	Delay(sleep1)
	keybd_event_(#VK_CONTROL,0,#KEYEVENTF_EXTENDEDKEY,0)
	Delay(sleep1)
	keybd_event_(#VK_LEFT, 0, #KEYEVENTF_EXTENDEDKEY, 0)
	Delay(sleep)
	keybd_event_(#VK_LEFT,0,#KEYEVENTF_KEYUP | #KEYEVENTF_EXTENDEDKEY,0)
	Delay(sleep1)
	keybd_event_(#VK_SHIFT,0,#KEYEVENTF_KEYUP | #KEYEVENTF_EXTENDEDKEY,0)
	Delay(sleep1)
	keybd_event_(#VK_CONTROL,0,#KEYEVENTF_KEYUP | #KEYEVENTF_EXTENDEDKEY,0)
EndProcedure
. Решил получить позицию курсора и либо получить текущую строку до курсора и в ней преобразовать последнее слово, либо считывать посимвольно влево и проверять каждый символ на начичие разделителя (пробела), потом получить диапазон текста и заменить на обработанный.
Столкнулся с непонятками, AkelPad.SendMessage и просто SendMessage от WinAPI это разные функции? То есть AkelPad.SendMessage имеет какую-либо собственную доработку, потому что у меня строки немного различаются и позиции, как будто например переносы строк не учитываются как символы. Но не важно пока я получаю не то что хотел. Я пытаюсь использовать константы "EM_..." надеясь что они поддерживаются SendMessage от WinAPI, а константы "AEM_..." как я понимаю для AkelPad.SendMessage. Вызов AEGI_LINEEND у меня вообще 1 раз сработал, а потом стал падать AkelPad, где бы ни стоял курсор.
Есть идеи как сделать? Как использовать AkelPad.SendMessage, как получить правильные строки и позиции, как лучше - получить строку или посимвольно считывать.

Posted: Thu May 28, 2020 2:58 pm
by DV
Рекомендую глянуть на исходники других плагинов - например, на SmartSel, который умеет по нажатию Home и End переходить на первый (OnEditHomeKeyDown) и последний (OnEditEndKeyDown) непробельный символ в строке. Там есть примеры использования EM_EXGETSEL64, AEM_GETSEL, EM_EXSETSEL64, AEM_SETSEL и других.
Скачивается отсюда:
http://akelpad.sourceforge.net/en/plugins.php

Posted: Thu May 28, 2020 3:56 pm
by AZJIO
DV
Пример получения текста есть в примере AkelDLL с EM_EXGETSEL64, но у меня условие ничего не выделено, то есть структура Min, Max содержать одну и ту же координату текста (смещение/позицию)
Почитал описание EM_LINEINDEX, там оказалось нужно -1, вроде получил я текст строки от начала строки до курсора.

Code: Select all

Protected lpIndex, nOffset1, nOffset2, nLine

; получить строку в которой находится указанный символ
nLine = SendMessage_(*pd\hWndEdit, #EM_EXLINEFROMCHAR, 0, gtr\cpMin)
MessageRequester("Строка в которой курсор", Str(nLine))

; получить позицию первого символа строки
nOffset1 = SendMessage_(*pd\hWndEdit, #EM_LINEINDEX, -1, @nLine)
MessageRequester("EM_LINEINDEX Позиция 1-го символа в строке", Str(nOffset1)) ; + #CRLF$ + Str(nLine)

gtr\cpMin = nOffset1
; gtr\cpMax = nOffset2
If SendMessage_(*pd\hMainWnd, #AKD_GETTEXTRANGEW, *pd\hWndEdit, @gtr)
	Selected_Text$ = PeekS(gtr\pText, -1, #PB_Unicode)
EndIf
MessageRequester("Строка до курсора", Selected_Text$)
ProcedureReturn
В AutoIt3 в вызове EM_LINEINDEX каким то образом оставил 0, думая, что он не используется, поэтому переписанный оттуда вариант у меня не работал.

Обновил ConvKey, теперь преобразовывает текст слева от курсора без необходимости выделения. Но в плане сделать ещё чтобы если слева от курсора пробел, то чтобы он игнорировался.

Posted: Fri May 29, 2020 7:22 pm
by AZJIO
Проверил ConvKey на Linux, если запуск из списка плагинов, то работает отлично, если сделал пункт меню или кнопку, то пишет что не найден файл плага ConvKey.dll на диске Z:\.
Из этого прихожу к выводу что надо как-то экпортировать меню, как это делает AkelPad из DLL. Как это сделано? Я просмотрел ресурсы плагов в них нет перевода и в языковых файлах DLL тоже нет перевода плагов.

Posted: Fri May 29, 2020 8:26 pm
by opk44
AZJIO wrote:Я просмотрел ресурсы плагов в них нет перевода и в языковых файлах DLL тоже нет перевода плагов.
Например, возьмем "...\AkelFiles\Plugs\Coder\Source\Coder.c"
Там вы найдете строки следующего вида:

Code: Select all

  if (wLangID == LANG_RUSSIAN)
...
    if (nStringID == STRID_ADDHIGHLIGHTWORDS)
      return L"\x0414\x043E\x043F\x043E\x043B\x043D\x044F\x0442\x044C\x0020\x0441\x043B\x043E\x0432\x0430\x043C\x0438\x0020\x0438\x0437\x0020\x0431\x0430\x0437\x044B\x0020\x0048\x0069\x0067\x0068\x004C\x0069\x0067\x0068\x0074\x0027\x0430\x0020\x0028\x043E\x0442\x043C\x0435\x0447\x0435\x043D\x044B\x0020\x0437\x0432\x0435\x0437\x0434\x043E\x0447\x043A\x043E\x0439\x0020\x002A\x0029";
Если пересопоставить коды символам, то получим

Code: Select all

    if (nStringID == STRID_ADDHIGHLIGHTWORDS)
      return L"Дополнять словами из базы HighLight'а (отмечены звездочкой *)";

Posted: Sat May 30, 2020 12:19 pm
by AZJIO
В плагах просто перевод либо русский язык либо английский? Получается плаги не мультиязычные?
Посмотрел вызовы определения языка для меню, надо поэкспериментировать, в структуре PLUGINDATA есть hPopupMenu, то есть я могу присоединить своё меню к hPopupMenu. Но мне хотелось бы найти вариант присоединения кнопки на панель, типа Menu("XBRACKETS"). В структуре NCONTEXTMENU нашёл флаг:

Code: Select all

bProcess.b;   ;;//TRUE   show context menu
значит плаг создаёт некое контекстное меню и передаёт в некую структуру указатель на это меню, которое AkelPad добавляет в шоуменю или распознаёт по функции Menu("XBRACKETS"), где XBRACKETS название плага. остаётся найти указатель и структуру в которую он передаётся.

Обновил ConvKey, добавил игнор пробела как и я хотел и добавил параметр автовыбора строки.

Posted: Wed Jun 17, 2020 12:36 am
by AZJIO
По мотивам этой темы решил сделать в виде плага. Но пока AkelPad падает при вызове внутри плага функции EnumChildWindows. А она нужна чтобы найти элементы окна справки, чтобы вставить текст, кликнуть кнопку и т.д.

Исправил, надо было передать второй параметр в вызываемой калбак-функции. Теперь работает.

Posted: Wed Jun 17, 2020 1:18 pm
by VladSh
AZJIO wrote:По мотивам этой темы решил сделать в виде плага. Но пока AkelPad падает при вызове внутри плага функции EnumChildWindows. А она нужна чтобы найти элементы окна справки, чтобы вставить текст, кликнуть кнопку и т.д.
А чего не использовать ChmKeyword.js? У меня так и работает, как во многих IDE - выделяешь ключевое слово, жмёшь Ctrl+F1 и открывается соотв. страница хелпа.
Единственное, что мне не нравится в этом скрипте, это то, что связка типов файлов с файлами справки находится внутри; я за то, чтобы вынести это в в json или ini.

Posted: Wed Jun 17, 2020 2:49 pm
by AZJIO
VladSh
Попробовал ChmKeyword.js, недостатки:
1. Справка открывается не так как если кликнуть по файлу, отсутствует список "Указатель". Для PureBasic указатель работает, но AutoIt3-справка версии 3.3.8.1 состоит из 4 файлов, один из которых объединяет 3 справки, может только такая структура справки вызвала проблему с "Указатель".
2. Справку надо закрывать для повторного просмотра, у меня же её можно свернуть и она разворачивается, это быстрее. Единственное у меня надо поправить чтобы разворачивалась не на весь экран, а в том виде в котором была свёрнута.
3. У меня в справке есть ссылки на скрипты, то есть я могу прямо из справки запускать скрипт-пример, ну или открывать его в редакторе, взависомости как у пользователя настроена команда Open в реестре для указанного файла. В ChmKeyword.js эта возможность ломается, пути не верные, либо не видит, либо не может из справки, открытой как дочернее окно запустить в своём родительском, потому что оно для него должно быть дочерним или сторонним. Эта проблема в том числе в IDE PureBasic, он открывает справки таким же образом и не может открыть примеры, а через мой exe-скрипт может, потому что он справку открывает как самостоятельное окно.
4. Из-за того что окно дочернее на панели задач кнопки группируются в одну, это не удобно.

Кстати я видимо ChmKeyword.js уже тестировал, так как он уже находился у меня в папке AkelFiles\Plugs\Scripts

Posted: Fri Jun 19, 2020 7:24 am
by AZJIO
Сделал захват слова под курсором, проверяя буквы до первого разделителя, оказалось есть функция EM_FINDWORDBREAK. Но по крайней мере в свой алгоритм я могу добавить символы не являющиеся разделителями для каждого языка индивидуально, указав в ini-файле параметр типа NotDelim = @#*.
Есть ещё хотелки, может добавить битовые флаги для вызова окна поиска по странице, чтобы подсветить слова; выбор влкадки "Поиск" вместо "Указатель", а хоткеи можно использовать Alt+F1, Shift+F1.