前節では、辞書や保存済みのファイルを分析し、説明文として表示しました。
しかし、本来表示するべきなのは、「保存済みのファイル」の分析結果ではなく、
編集中のファイルの分析結果である、というのが理想です。
秀丸エディタ ver 8.66からは編集中のデータをネイティブ層で直接取得することが可能となりました。
又、カーソルの位置もネイティブ層から直接取得が可能となりました。
#include <windows.h> #include "OutputDebugStream.h" #include <msclr/marshal.h> using namespace std; using namespace System; using namespace System::IO; using namespace System::Collections::Generic; using namespace System::Text; using namespace System::Text::RegularExpressions; using namespace System::Drawing; using namespace System::Windows::Forms; using namespace msclr::interop; public ref class ACHelpForm : public Form { public: static ACHelpForm^ f; // 自分自身の置き場 private: HWND hWnd; Label^ label; // 説明文用のラベル int nTickRemainCnt; static Timer^ timer; static String^ dllFullPath; static String^ dicFullPath; static Dictionary<String^, String^>^ dic; // 秀丸本体からExportされている関数をセットする。 static HMODULE hHidemaruExeInstance = GetModuleHandle(NULL); static HGLOBAL (WINAPI *GetTotalTextUnicode)(void) = (decltype(GetTotalTextUnicode))GetProcAddress(hHidemaruExeInstance, "Hidemaru_GetTotalTextUnicode"); static HGLOBAL (WINAPI *GetLineTextUnicode)(int nLineNo) = (decltype(GetLineTextUnicode))GetProcAddress(hHidemaruExeInstance, "Hidemaru_GetLineTextUnicode"); static BOOL (WINAPI *GetCursorPosUnicode)(int* pnLineNo, int* pnColumn) = (decltype(GetCursorPosUnicode))GetProcAddress(hHidemaruExeInstance, "Hidemaru_GetCursorPosUnicode"); static BOOL (WINAPI *CheckQueueStatus)(void) = (decltype(CheckQueueStatus))GetProcAddress(hHidemaruExeInstance, "Hidemaru_CheckQueueStatus"); public: ACHelpForm() { this->Visible = false; this->BackColor = System::Drawing::Color(::Color::White); SetFormAttr(); label = gcnew Label(); label->Top = 10; label->Left = 10; this->Controls->Add(label); // ディクショナリオブジェクト生成 dic = gcnew Dictionary<String^, String^>(); LoadDicFile(); LoadCurText(); } void LoadDicFile() { dllFullPath = System::Reflection::Assembly::GetExecutingAssembly()->Location; // 自分自身のディクションナリ dicFullPath = Path::ChangeExtension(dllFullPath, ".dic"); IO::StreamReader^ sr; try { sr = gcnew IO::StreamReader(dicFullPath, ::Encoding::GetEncoding(932)); // 説明文のファイルはsjis(cp932)と決め打った String^ line = ""; // 読み込みできる文字がなくなるまで繰り返す while ((line = sr->ReadLine()) != nullptr) { Regex^ re = gcnew Regex(R"(^(.+?)★(.+)$)"); // キーと説明文の境界線は「★」にしておく Match^ m = re->Match(line); if (m->Success) { String^ keyword = m->Groups[1]->Value; String^ details = m->Groups[2]->Value; // キーも値も両方有効 if (keyword && details) { details = details->Replace("◆", "\n"); // 「◆」は改行記号に変えておく dic[keyword] = details; } } } } catch (Exception^ e) { Windows::Forms::MessageBox::Show(e->Message); } finally { if (sr) { sr->Close(); } } } // 現在のカーソルのポジションを得る(1オリジン) Point^ GetCursorPos() { if (!GetCursorPosUnicode) { return gcnew Point(1, 1); } // ネイティブで秀丸エディタに問い合わせ int nLineNo = 0; int nColumn = 0; GetCursorPosUnicode(&nLineNo, &nColumn); // マネージドでPoint参照型にしておく Point^ p = gcnew Point(nLineNo, nColumn); return p; } void LoadCurText() { if (!GetTotalTextUnicode) { return; } Point^ p = GetCursorPos(); OutputDebugStream(L"カーソル位置 x:%d, y:%d\n", p->X, p->Y); String^ all = ""; // 現在編集中のテキストを得る HGLOBAL hGlobal = GetTotalTextUnicode(); if (hGlobal) { // ネイティブでピンポインタ状態で得る wchar_t* pwsz = (wchar_t*)GlobalLock(hGlobal); // マネージドにコピー all = marshal_as<System::String^>(pwsz); // ネイティブは解放 GlobalUnlock(hGlobal); GlobalFree(hGlobal); } try { Regex^ re = gcnew Regex("((^#.+?\n)+)sub\\s+([^\\s]+)", RegexOptions::Multiline); // 1行としてみなして MatchCollection^ mc = re->Matches(all); for each(Match^ m in mc) { GroupCollection^ groups = m->Groups; String^ keyword = groups[3]->Value; String^ details = groups[1]->Value; // キーも値も両方有効 if (keyword && details) { dic[keyword] = details; } } } catch (Exception^) { } } public: void Update(HWND hWnd, int iListBoxSelectedIndex, String^ strListBoxSelectedItem, int iItemHeight) { this->hWnd = hWnd; this->SuspendLayout(); AdjustToAutoComp(); // フォームのサイズや位置を再配置 // それに応じてラベルのサイズが決まる label->Width = this->Width - label->Left * 2; label->Height = this->Height - label->Top * 2; label->Font = gcnew System::Drawing::Font("MS 明朝", (float)iItemHeight*0.7f); // 1.5倍ぐらい try { label->Text = dic[strListBoxSelectedItem]; } catch (Exception^) { label->Text = ""; } this->ResumeLayout(); CreateDelayTimer(); } protected: // 入力補完ウィンドウの右に、幅約2倍、高さ160pxへと自分自身を移動 void AdjustToAutoComp() { RECT rect; GetWindowRect(this->hWnd, &rect); this->Left = rect.right + 24; this->Top = rect.top; this->Width = (rect.right - rect.left) * 2; this->Height = 160; DrawRectangle(); } // フォームの外淵に沿って四角を描画 void DrawRectangle() { Graphics^ g = this->CreateGraphics(); Pen^ pen = gcnew Pen(::Color::Gray, 1); g->DrawRectangle(pen, 0, 0, this->Width - 1, this->Height - 1); delete pen; delete g; } // 入力補完などで状態が切り替わったら、6ミリx100回ぐらいはトライして、状態を追従し続ける。 // 状態追従の安定化の一環 void CreateDelayTimer() { if (timer) { timer->Stop(); } timer = gcnew Timer(); timer->Interval = 6; timer->Tick += gcnew EventHandler(this, &ACHelpForm::TimerTick); timer->Start(); nTickRemainCnt = 100; } void TimerTick(Object^ sender, EventArgs^ e) { AdjustToAutoComp(); if (label->Text->Length > 0) { // 単語が有効であれば this->Show(); } else { this->Hide(); } nTickRemainCnt--; if (nTickRemainCnt < 0) { timer->Stop(); } } public: void StopTimer() { if (timer) { timer->Stop(); } } protected: void SetFormAttr() { //タイトルバーを消す this->ControlBox = false; this->Text = ""; this->FormBorderStyle = ::FormBorderStyle::None; } // フォーム表示時にアクティブにならないようにする property bool ShowWithoutActivation { virtual bool get() override { return true; } } // このフォームがクリックなどされた時にアクティブにならないようにする。 virtual void WndProc(Message %m) override { if (m.Msg == WM_MOUSEACTIVATE) { m.Result = (IntPtr)MA_NOACTIVATE; return; } Form::WndProc(m); } }; extern "C" __declspec(dllexport) int OnCreate(HWND hWnd, LPCTSTR szFileName) { // ウィンドウの作成 if (ACHelpForm::f == nullptr || ACHelpForm::f->IsDisposed) { ACHelpForm::f = gcnew ACHelpForm(); } return TRUE; } extern "C" __declspec(dllexport) int OnListBoxSelectedIndexChanged(HWND hWnd, int iListBoxSelectedIndex, LPCTSTR szListBoxSelectedItem, int iItemHeight) { ACHelpForm::f->Update(hWnd, iListBoxSelectedIndex, gcnew String(szListBoxSelectedItem), iItemHeight); return TRUE; } extern "C" __declspec(dllexport) int OnDestroy(HWND hWnd) { // ウィンドウの破棄 if (ACHelpForm::f) { ACHelpForm::f->StopTimer(); ACHelpForm::f->Close(); } // 明示的に解放 if (ACHelpForm::f) { delete ACHelpForm::f; } return TRUE; }
「LoadCurText」まわりが今回の注目点でしょう。
コンストラクタにて、「Hidemaru.exe」本体からExportされている関数群をセットしています。
セットした関数を利用して、「カーソルの位置」や「現在編集中のテキスト」を取得しています。
// 秀丸本体からExportされている関数をセットする。 static HMODULE hHidemaruExeInstance = GetModuleHandle(NULL); static HGLOBAL (WINAPI *GetTotalTextUnicode)(void) = (decltype(GetTotalTextUnicode))GetProcAddress(hHidemaruExeInstance, "Hidemaru_GetTotalTextUnicode"); static HGLOBAL (WINAPI *GetLineTextUnicode)(int nLineNo) = (decltype(GetLineTextUnicode))GetProcAddress(hHidemaruExeInstance, "Hidemaru_GetLineTextUnicode"); static BOOL (WINAPI *GetCursorPosUnicode)(int* pnLineNo, int* pnColumn) = (decltype(GetCursorPosUnicode))GetProcAddress(hHidemaruExeInstance, "Hidemaru_GetCursorPosUnicode"); static BOOL (WINAPI *CheckQueueStatus)(void) = (decltype(CheckQueueStatus))GetProcAddress(hHidemaruExeInstance, "Hidemaru_CheckQueueStatus"); ・・・ // 現在のカーソルのポジションを得る(1オリジン) Point^ GetCursorPos() { if (!GetCursorPosUnicode) { return gcnew Point(1, 1); } // ネイティブで秀丸エディタに問い合わせ int nLineNo = 0; int nColumn = 0; GetCursorPosUnicode(&nLineNo, &nColumn); // マネージドでPoint参照型にしておく Point^ p = gcnew Point(nLineNo, nColumn); return p; } void LoadCurText() { if (!GetTotalTextUnicode) { return; } Point^ p = GetCursorPos(); OutputDebugStream(L"カーソル位置 x:%d, y:%d\n", p->X, p->Y); String^ all = ""; // 現在編集中のテキストを得る HGLOBAL hGlobal = GetTotalTextUnicode(); if (hGlobal) { // ネイティブでピンポインタ状態で得る wchar_t* pwsz = (wchar_t*)GlobalLock(hGlobal); // マネージドにコピー all = marshal_as<System::String^>(pwsz); // ネイティブは解放 GlobalUnlock(hGlobal); GlobalFree(hGlobal); } try { Regex^ re = gcnew Regex("((^#.+?\n)+)sub\\s+([^\\s]+)", RegexOptions::Multiline); // 1行としてみなして MatchCollection^ mc = re->Matches(all); for each(Match^ m in mc) { GroupCollection^ groups = m->Groups; String^ keyword = groups[3]->Value; String^ details = groups[1]->Value; // キーも値も両方有効 if (keyword && details) { dic[keyword] = details; } } } catch (Exception^) { } }
再度コンパイルし、「HmAutoCompleteExPlug.dll」を秀丸ディレクトリにコピーしましょう。
再び、.plファイルを読み込んで、何か入力補完を出し、単語を選択してみましょう。
以下のように、「本当の意味での編集中」のコメント説明文が表示されたでしょうか。