【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.mzph.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;损失函数是将预测值和真实…