【AI插件開發】Notepad++ AI插件開發實踐(代碼篇):從Dock窗口集成到功能菜單實現

一、引言

上篇文章已經在Notepad++的插件開發中集成了選中即問AI的功能,這一篇文章將在此基礎上進一步集成,支持AI對話窗口以及常見的代碼功能菜單:

  • 顯示AI的Dock窗口,可以用自然語言向 AI 提問或要求執行任務
  • 選中代碼后使用,AI 會詳細解釋代碼功能
  • 需要 AI 幫助改進或修復代碼時使用
  • 自動生成代碼注釋
  • 選中即問,直接把選中內容丟給AI
  • 參數設置,基于插件配置切換AI平臺等參數,提供對話框設置及調整平臺參數

本篇的主要難點在于如何集成Dock窗口,是自己裸寫Window窗口,還是找第三方庫,或者從Notepad++的源碼中剝離相關代碼?

最終選擇的是從Notepad++的源碼中剝離相關代碼的方案,但是直接剝離很難,發現依賴越來越多,因此需要在剝離的基礎上做一些改造,刪減非必須的功能,比如NppDarkMode裁剪。

:項目已開源、鏡像,歡迎使用及指正

二、Notepad++的源碼中剝離Dock窗口

Notepad++文件

1. 剝離后的Dock文件列表

Common.h
Docking.h
DockingDlgInterface.h
dockingResource.h
dpiManagerV2.cpp
dpiManagerV2.h
NppDarkMode.cpp
NppDarkMode.h
StaticDialog.cpp
StaticDialog.h
Window.h

2. 要點說明

直接從Notepad++中拷貝上述文件代碼到項目中引用的話,會發現還要包含其他文件,然后試著把別的文件引入的時候,后面發現引入的文件越來越多,所以需要對部分文件進行裁剪,主要裁剪的代碼是NppDarkMode.cpp。 我把NppDarkMode.cpp的代碼全刪了,然后自己實現了項目中調用了的函數,采集后的NppDarkMode.cpp文件內容如下:

#include "NppDarkMode.h"enum class SystemVersion
{Unknown,Windows10,Windows11
};SystemVersion GetWindowsVersion()
{// 使用RtlGetVersion替代已廢棄的GetVersionExtypedef NTSTATUS(WINAPI* RtlGetVersionPtr)(PRTL_OSVERSIONINFOW);OSVERSIONINFOW osInfo = { 0 };HMODULE hMod = GetModuleHandleW(L"ntdll.dll");if (hMod){auto RtlGetVersion = reinterpret_cast<RtlGetVersionPtr>(GetProcAddress(hMod, "RtlGetVersion"));if (RtlGetVersion) {osInfo.dwOSVersionInfoSize = sizeof(osInfo);if (RtlGetVersion(&osInfo) == 0){ // STATUS_SUCCESS// Windows 11的版本號為10.0.22000+if (osInfo.dwMajorVersion == 10 &&osInfo.dwMinorVersion == 0){if (osInfo.dwBuildNumber >= 22000){return SystemVersion::Windows11;}else if (osInfo.dwBuildNumber >= 10240){return SystemVersion::Windows10;}}}}}return SystemVersion::Unknown;
}bool NppDarkMode::isWindows10() { return GetWindowsVersion() == SystemVersion::Windows10; }
bool NppDarkMode::isWindows11() { return GetWindowsVersion() == SystemVersion::Windows11; }
void NppDarkMode::setDarkTitleBar(HWND hwnd) {}

3. AI窗口實現

現在只需要引入DockingDlgInterface.h文件,實現一個基于DockingDlgInterface的窗口即可,其中AiAssistWnd.h

// AiAssistWnd.h
#pragma once
#include "DockingDlgInterface.h"
#include "PluginInterface.h"class AiAssistWnd : public DockingDlgInterface {
public:AiAssistWnd(HINSTANCE hInst, const NppData& nppData);~AiAssistWnd();// 必需實現的虛函數virtual void init();virtual INT_PTR run_dlgProc(UINT message, WPARAM wParam, LPARAM lParam);// 功能接口void updateModelList(const std::vector<std::wstring>& models);void appendAnswer(const std::wstring& answer);void clearConversation();private:void initControls();void layoutControls(int width, int height);void handleUserInput();// NppHINSTANCE _hInst;NppData _nppData;// 控件句柄HWND _hModelCombo = nullptr;HWND _hInputEdit = nullptr;HWND _hAnswerView = nullptr;// 配置參數const int CONTROL_MARGIN = 5;const int COMBO_HEIGHT = 25;const int INPUT_HEIGHT = 80;// 字體資源HFONT _hFont = nullptr;HFONT _hBoldFont = nullptr;// 控件ID定義enum ControlID {IDC_MODEL_COMBO = 2000,IDC_INPUT_EDIT,IDC_ANSWER_VIEW};
};

代碼實現:

// AiAssistWnd.cpp
#include "AiAssistWnd.h"
#include "resource.h"
#include <richedit.h>
#include <commctrl.h>
#include <format>AiAssistWnd::AiAssistWnd(HINSTANCE hInst, const NppData& nppData): DockingDlgInterface(IDD_DIALOG_AI_ASSIST), _hInst(hInst), _nppData(nppData)
{this->_hParent = _nppData._nppHandle;
}AiAssistWnd::~AiAssistWnd()
{if (_hFont) DeleteObject(_hFont);if (_hBoldFont) DeleteObject(_hBoldFont);
}void AiAssistWnd::init()
{DockingDlgInterface::init(_hInst, _hParent);// 注冊Dock窗口tTbData tbData = { 0 };DockingDlgInterface::create(&tbData);tbData.uMask = DWS_DF_CONT_RIGHT | DWS_ICONTAB;;tbData.pszModuleName = L"AI Assistant";;tbData.dlgID = _dlgID;::SendMessage(_nppData._nppHandle, NPPM_DMMREGASDCKDLG, 0, (LPARAM)&tbData);// 初始化UI控件initControls();// 設置初始大小RECT rc;GetClientRect(_hSelf, &rc);::SetWindowPos(_hSelf, nullptr, rc.left, rc.top, 300, rc.bottom, SWP_NOZORDER | SWP_NOMOVE);layoutControls(rc.right, rc.bottom);
}void AiAssistWnd::initControls()
{// 創建控件_hModelCombo = ::CreateWindowExW(0, WC_COMBOBOX, L"",CBS_DROPDOWNLIST | WS_CHILD | WS_VISIBLE | WS_TABSTOP,0, 0, 0, 0, _hSelf, (HMENU)IDC_MODEL_COMBO, _hInst, nullptr);_hInputEdit = ::CreateWindowExW(WS_EX_CLIENTEDGE, L"EDIT", L"",WS_CHILD | WS_VISIBLE | ES_MULTILINE | ES_AUTOVSCROLL | WS_VSCROLL,0, 0, 0, 0, _hSelf, (HMENU)IDC_INPUT_EDIT, _hInst, nullptr);// 使用RichEdit 4.1LoadLibraryW(L"Msftedit.dll");_hAnswerView = ::CreateWindowExW(WS_EX_CLIENTEDGE, MSFTEDIT_CLASS, L"",WS_CHILD | WS_VISIBLE | ES_MULTILINE | ES_READONLY | WS_VSCROLL | WS_HSCROLL,0, 0, 0, 0, _hSelf, (HMENU)IDC_ANSWER_VIEW, _hInst, nullptr);// 初始化字體_hFont = CreateFontW(14, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE,DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,DEFAULT_QUALITY, DEFAULT_PITCH, L"Segoe UI");_hBoldFont = CreateFontW(14, 0, 0, 0, FW_SEMIBOLD, FALSE, FALSE, FALSE,DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,DEFAULT_QUALITY, DEFAULT_PITCH, L"Segoe UI");// 應用字體SendMessageW(_hModelCombo, WM_SETFONT, (WPARAM)_hFont, TRUE);SendMessageW(_hInputEdit, WM_SETFONT, (WPARAM)_hFont, TRUE);SendMessageW(_hAnswerView, WM_SETFONT, (WPARAM)_hFont, TRUE);
}INT_PTR AiAssistWnd::run_dlgProc(UINT message, WPARAM wParam, LPARAM lParam)
{switch (message) {case WM_SIZE:if (wParam != SIZE_MINIMIZED) {RECT rc;GetClientRect(_hSelf, &rc);layoutControls(rc.right, rc.bottom);}return TRUE;case WM_COMMAND:if (HIWORD(wParam) == EN_MAXTEXT && LOWORD(wParam) == IDC_INPUT_EDIT) {handleUserInput();return TRUE;}break;case WM_NOTIFY:// 處理其他通知消息break;case WM_CTLCOLOREDIT:// 設置輸入框背景色if ((HWND)lParam == _hInputEdit) {SetBkColor((HDC)wParam, RGB(255, 255, 255));return (INT_PTR)GetStockObject(WHITE_BRUSH);}break;}return DockingDlgInterface::run_dlgProc(message, wParam, lParam);
}void AiAssistWnd::layoutControls(int width, int height)
{int yPos = CONTROL_MARGIN;int nHeight = CONTROL_MARGIN;// 回答區域int answerHeight = height - 3*CONTROL_MARGIN - COMBO_HEIGHT - INPUT_HEIGHT;::MoveWindow(_hAnswerView,CONTROL_MARGIN, yPos,width - 2 * CONTROL_MARGIN, answerHeight, TRUE);yPos += CONTROL_MARGIN + answerHeight;// 模型選擇框::MoveWindow(_hModelCombo,CONTROL_MARGIN, yPos,width - 2 * CONTROL_MARGIN, COMBO_HEIGHT, TRUE);yPos += COMBO_HEIGHT + CONTROL_MARGIN;// 輸入框::MoveWindow(_hInputEdit,CONTROL_MARGIN, yPos,width - 2 * CONTROL_MARGIN, INPUT_HEIGHT, TRUE);yPos += INPUT_HEIGHT + CONTROL_MARGIN;
}void AiAssistWnd::updateModelList(const std::vector<std::wstring>& models)
{SendMessageW(_hModelCombo, CB_RESETCONTENT, 0, 0);for (const auto& model : models) {SendMessageW(_hModelCombo, CB_ADDSTRING, 0, (LPARAM)model.c_str());}if (!models.empty()) {SendMessageW(_hModelCombo, CB_SETCURSEL, 0, 0);}
}void AiAssistWnd::appendAnswer(const std::wstring& answer)
{// 添加時間戳SYSTEMTIME st;GetLocalTime(&st);std::wstring timestamp = std::format(L"[{:02}:{:02}:{:02}] ",st.wHour, st.wMinute, st.wSecond);// 設置富文本格式CHARFORMAT2W cf = { sizeof(CHARFORMAT2W) };cf.dwMask = CFM_COLOR | CFM_BOLD;cf.crTextColor = RGB(0, 128, 0);cf.dwEffects = CFE_BOLD;SendMessageW(_hAnswerView, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&cf);SendMessageW(_hAnswerView, EM_REPLACESEL, FALSE, (LPARAM)timestamp.c_str());// 恢復默認格式cf.dwMask = CFM_COLOR;cf.crTextColor = RGB(0, 0, 0);cf.dwEffects = 0;SendMessageW(_hAnswerView, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&cf);SendMessageW(_hAnswerView, EM_REPLACESEL, FALSE, (LPARAM)answer.c_str());// 自動滾動到底部SendMessageW(_hAnswerView, WM_VSCROLL, SB_BOTTOM, 0);
}void AiAssistWnd::clearConversation()
{if (!IsWindow(_hAnswerView))return;// 使用SETTEXTEX結構清空內容SETTEXTEX st = {ST_DEFAULT,    // 標志位1200           // 使用UTF-16編碼};// 方法1:直接設置空文本(保留格式)SendMessageW(_hAnswerView, EM_SETTEXTEX, (WPARAM)&st, (LPARAM)L"");// 方法2:通過選擇全部刪除(更徹底)// SendMessageW(_hAnswerView, EM_SETSEL, 0, -1);   // 全選// SendMessageW(_hAnswerView, EM_REPLACESEL, 0, (LPARAM)L""); // 替換為空// 可選:重置滾動條位置SendMessageW(_hAnswerView, WM_VSCROLL, SB_TOP, 0);// 可選:清除Undo緩沖區SendMessageW(_hAnswerView, EM_EMPTYUNDOBUFFER, 0, 0);
}
void AiAssistWnd::handleUserInput()
{// 獲取輸入文本int len = GetWindowTextLengthW(_hInputEdit) + 1;std::wstring input(len, L'\0');GetWindowTextW(_hInputEdit, &input[0], len);input.resize(len - 1); // 移除結尾的null字符if (!input.empty()) {// TODO: 觸發AI處理邏輯appendAnswer(L"Received: " + input);// 清空輸入框SetWindowTextW(_hInputEdit, L"");}
}

三、主要菜單功能實現

1.定義菜單

// PluginDefinition.h
//-----------------------------------------------//
//-- STEP 2. DEFINE YOUR PLUGIN COMMAND NUMBER --//
//-----------------------------------------------//
//
// Here define the number of your plugin commands
//
const int nbFunc = 6;//
// Your plugin command functions
//
//
// 參數設置
void PluginConfig();
// 打開Ai助手窗口
void OpenAiAssistWnd();
// 解讀代碼
void ReadCode();
// 代碼優化
void OptimizeCode();
// 添加代碼注釋
void AddCodeComment();
// 選中即問
void AskBySelectedText();

2.初始化菜單

// PluginDefinition.cpp
//
// Initialization of your plugin commands
// You should fill your plugins commands here
void commandMenuInit()
{//--------------------------------------------////-- STEP 3. CUSTOMIZE YOUR PLUGIN COMMANDS --////--------------------------------------------//// with function :// setCommand(int index,                      // zero based number to indicate the order of command//            TCHAR *commandName,             // the command name that you want to see in plugin menu//            PFUNCPLUGINCMD functionPointer, // the symbol of function (function pointer) associated with this command. The body should be defined below. See Step 4.//            ShortcutKey *shortcut,          // optional. Define a shortcut to trigger this command//            bool check0nInit                // optional. Make this menu item be checked visually//            );// 初始化數據g_pNppImp = new NppImp(g_nppData);// 初始化菜單ShortcutKey* pSck = new ShortcutKey[nbFunc];g_pShortcutKeys = pSck;size_t nCid = 0;setCommand(nCid, L"參數配置", PluginConfig, NULL, false); ++nCid;pSck[nCid] = { false, true, false, 'K' };setCommand(nCid, L"顯示窗口", OpenAiAssistWnd, pSck + nCid, false); ++nCid;pSck[nCid] = { false, true, false, 'J' };setCommand(nCid, L"解讀代碼", ReadCode, pSck + nCid, false); ++nCid;pSck[nCid] = { false, true, false, 'Y' };setCommand(nCid, L"優化代碼", OptimizeCode, pSck + nCid, false); ++nCid;pSck[nCid] = { false, true, false, 'Z' };setCommand(nCid, L"代碼注釋", AddCodeComment, pSck + nCid, false); ++nCid;pSck[nCid] = { false, true, false, 'A' };setCommand(nCid, L"選中即問", AskBySelectedText, pSck + nCid, false); ++nCid;
}

3.打開AI窗口

// PluginDefinition.cpp
// 打開Ai助手窗口
void OpenAiAssistWnd()
{// 初始化Dock窗口if (g_pAiWnd == nullptr && g_hModule != nullptr && g_nppData._nppHandle != nullptr){g_pAiWnd = new AiAssistWnd((HINSTANCE)g_hModule, g_nppData);g_pAiWnd->init();}if (g_pAiWnd){g_pAiWnd->display(true);}
}

四、效果展示

1.插件菜單

插件菜單

2.AI窗口

Ai窗口

五、結語

到這里,已經在Notepad++中支持AI對話窗口了,已經有一點Cursor的AI編輯器的意思了,下一步將進一步完善功能實現,包括配置加載、配置窗口以及界面功能實現。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/75796.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/75796.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/75796.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

關聯容器-模板類pair數對

關聯容器 關聯容器和順序容器有著根本的不同:關聯容器中的元素是按關鍵字來保存和訪問的,而順序容器中的元素是按它們在容器中的位置來順序保存和訪問的。 關聯容器支持高效的關鍵字查找和訪問。 兩個主要的關聯容器(associative-container),set和map。 set 中每個元素只包…

京東運維面試題及參考答案

目錄 OSPF 實現原理是什么? 請描述 TCP 三次握手的過程。 LVS 的原理是什么? 闡述 Nginx 七層負載均衡的原理。 Nginx 與 Apache 有什么區別? 如何查看監聽在 8080 端口的是哪個進程(可舉例:netstat -tnlp | grep 8080)? OSI 七層模型是什么,請寫出各層的協議。 …

輸入框輸入數字且保持精度

在項目中如果涉及到金額等需要數字輸入且保持精度的情況下&#xff0c;由于輸入框是可以隨意輸入文本的&#xff0c;所以一般情況下可能需要監聽輸入框的change事件&#xff0c;然后通過正則表達式去替換掉不匹配的文本部分。 由于每次文本改變都會被監聽&#xff0c;包括替換…

使用 requests 和 BeautifulSoup 解析淘寶商品

以下將詳細解釋如何通過這兩個庫來實現按關鍵字搜索并解析淘寶商品信息。 一、準備工作 1. 安裝必要的庫 在開始之前&#xff0c;確保已經安裝了 requests 和 BeautifulSoup 庫。如果尚未安裝&#xff0c;可以通過以下命令進行安裝&#xff1a; bash pip install requests…

C#調用ACCESS數據庫,解決“Microsoft.ACE.OLEDB.12.0”未注冊問題

C#調用ACCESS數據庫&#xff0c;解決“Microsoft.ACE.OLEDB.12.0”未注冊問題 解決方法&#xff1a; 1.將C#采用的平臺從AnyCpu改成X64 2.將官網下載的“Microsoft Access 2010 數據庫引擎可再發行程序包AccessDatabaseEngine_X64”文件解壓 3.安裝解壓后的文件 點擊下載安…

【文獻閱讀】Vision-Language Models for Vision Tasks: A Survey

發表于2024年2月 TPAMI 摘要 大多數視覺識別研究在深度神經網絡&#xff08;DNN&#xff09;訓練中嚴重依賴標注數據&#xff0c;并且通常為每個單一視覺識別任務訓練一個DNN&#xff0c;這導致了一種費力且耗時的視覺識別范式。為應對這兩個挑戰&#xff0c;視覺語言模型&am…

【Kubernetes】StorageClass 的作用是什么?如何實現動態存儲供應?

StorageClass 使得用戶能夠根據不同的存儲需求動態地申請和管理存儲資源。 StorageClass 定義了如何創建存儲資源&#xff0c;并指定了存儲供應的配置&#xff0c;例如存儲類型、質量、訪問模式等。為動態存儲供應提供了基礎&#xff0c;使得 Kubernetes 可以在用戶創建 PVC 時…

Muduo網絡庫介紹

1.Reactor介紹 1.回調函數 **回調&#xff08;Callback&#xff09;**是一種編程技術&#xff0c;允許將一個函數作為參數傳遞給另一個函數&#xff0c;并在適當的時候調用該函數 1.工作原理 定義回調函數 注冊回調函數 觸發回調 2.優點 異步編程 回調函數允許在事件發生時…

Debian編譯安裝mysql8.0.41源碼包 筆記250401

Debian編譯安裝mysql8.0.41源碼包 以下是在Debian系統上通過編譯源碼安裝MySQL 8.0.41的完整步驟&#xff0c;包含依賴管理、編譯參數優化和常見問題處理&#xff1a; 準備工作 1. 安裝編譯依賴 sudo apt update sudo apt install -y \cmake gcc g make libssl-dev …

Git常用問題收集

gitignore 忽略文件夾 不生效 有時候我們接手別人的項目時&#xff0c;發現有的忽略不對想要修改&#xff0c;但發現修改忽略.gitignore后無效。原因是如果某些文件已經被納入版本管理在.gitignore中忽略路徑是不起作用的&#xff0c;這時候需要先清除本地緩存&#xff0c;然后…

編程哲學——TCP可靠傳輸

TCP TCP可靠傳輸 TCP的可靠傳輸表現在 &#xff08;1&#xff09;建立連接時三次握手&#xff0c;四次揮手 有點像是這樣對話&#xff1a; ”我們開始對話吧“ ”收到“ ”好的&#xff0c;我收到你收到了“ &#xff08;2&#xff09;數據傳輸時ACK應答和超時重傳 ”我們去吃…

【MediaPlayer】基于libvlc+awtk的媒體播放器

基于libvlcawtk的媒體播放器 libvlc下載地址 awtk下載地址 代碼實現libvlc相關邏輯接口UI媒體接口實例化媒體播放器注意事項 libvlc 下載地址 可以到https://download.videolan.org/pub/videolan/vlc/去下載一個vlc版本&#xff0c;下載后其實是vlc的windows客戶端&#xff0…

pulsar中的延遲隊列使用詳解

Apache Pulsar的延遲隊列支持任意時間精度的延遲消息投遞&#xff0c;適用于金融交易、定時提醒等高時效性場景。其核心設計通過堆外內存索引隊列與持久化分片存儲實現&#xff0c;兼顧靈活性與可擴展性。以下從實現原理、使用方式、優化策略及挑戰展開解析&#xff1a; 一、核…

單鏈表的實現 | 附學生信息管理系統的實現

目錄 1.前言&#xff1a; 2.單鏈表的相關概念&#xff1a; 2.1定義&#xff1a; 2.2形式&#xff1a; 2.3特點&#xff1a; 3.常見功能及代碼 &#xff1a; 3.1創建節點&#xff1a; 3.2頭插&#xff1a; 3.3尾插&#xff1a; 3.4頭刪&#xff1a; 3.5尾刪&#xff1a; 3.6插入…

java實用工具類Localstorage

public class LocalStorageUtil {//提供ThreadLocal對象,private static ThreadLocal threadLocalnew ThreadLocal();public static Object get(){return threadLocal.get();}public static void set(Object o){threadLocal.set(o);}public static void remove(){threadLocal.r…

LLM-大語言模型淺談

目錄 核心定義 典型代表 核心原理 用途 優勢與局限 未來發展方向 LLM&#xff08;Large Language Model&#xff09;大語言模型&#xff0c;指通過海量文本數據訓練 能夠理解和生成人類語言的深度學習模型。 核心定義 一種基于深度神經網絡&#xff08;如Transformer架…

【小兔鮮】day03 Home模塊與一級分類

【小兔鮮】day03 Home模塊與一級分類 1. Home-整體結構搭建和分類實現1.1 頁面結構 2. Home-banner輪播圖功能實現 1. Home-整體結構搭建和分類實現 1.1 頁面結構 分類實現 2. Home-banner輪播圖功能實現 輪播圖實現 在HomeBanner.vue中寫出輪播圖的結構 在apis目錄下新建h…

C++中的多態和模板

#include <iostream> #include <cstdlib> #include <ctime> #include <string>using namespace std;// 武器基類 class Weapon { public:virtual ~Weapon() {}virtual string getName() const 0; // 獲取武器名稱virtual int getAtk() const 0; …

Spring 概念

Spring 是一個功能強大、靈活且廣泛使用的 Java 企業級開發框架&#xff0c;它誕生于 2003 年&#xff0c;由 Rod Johnson 創建&#xff0c;初衷是簡化 Java EE 的開發過程。 一、Spring 是什么&#xff1f; 簡單來說&#xff1a; Spring 是一個輕量級的 Java 開發框架&#…

神經網絡之損失函數

引言&#xff1a;損失函數 &#xff08;Loss Function&#xff09;是機器學習和深度學習中非常重要的一個概念。用于衡量模型的預測值與真實值之間的差異&#xff0c;從而指導模型優化其參數以最小化這種差異。 一、損失函數作用 量化誤差&#xff1a;損失函數是將預測值和真實…