简介:Notepad++ v8.6.6 源代码包面向希望深入理解成熟文本编辑器实现细节的开发者与学习者,尤其适合研究 Windows 程序设计、文本编辑控件与插件架构的中高级程序员。压缩包共约 2000 个文件,整体 11.48MB,以 C++ 源码为主,包含 261 个 h 头文件、182 个 hpp、132 个 cpp 与 103 个 cxx,另有 198 个 xml 语法定义、147 个 styled 与 147 个 folded 样式配置,以及 224 个 ico、73 个 bmp 等界面资源,并附带 37 个 py 脚本、29 个 rc 资源脚本和 6 个 vcxproj 工程文件,覆盖从核心编辑逻辑到构建配置的完整结构。目前已有 877 人学习下载。通过研读可掌握基于 Windows API 的消息处理机制、Scintilla 控件的接口调用与扩展方式、多语言语法高亮规则定义,以及插件加载、管理与通信模块的设计思路,为二次开发或自建编辑器提供可借鉴的工程范例。
1. 拿到 Notepad++ v8.6.6 源码先别急着编译:这份资源到底能解决什么
很多人第一次拿到 Notepad++ v8.6.6 源代码,第一反应是双击 .sln 然后按 F5,结果半小时后卡在某个第三方库的路径报错上。这份资源不是「下载即用的编辑器安装包」,而是一套完整的 Windows 原生 C++ 工程源码,包含 Scintilla 编辑控件、Lexilla 词法分析器、插件宿主框架以及官方那套基于 Win32 API 的界面逻辑。它能解决的核心问题很具体:你想给编辑器加一个自定义语法高亮、想改掉某个反直觉的快捷键行为、想研究一个成熟 Win32 应用怎么组织多语言资源,或者单纯想验证「这个功能到底是在哪一层实现的」。适合有 C++ 基础、用过 Visual Studio、能看懂资源脚本和消息循环的从业者。如果你只是想找个能写代码的编辑器,直接装官方版就行,源码这条路是给要动刀的人准备的。
2. 工程结构与依赖链:先搞清楚哪些目录能动、哪些碰不得
2.1 顶层目录的职责划分
解压源码后,根目录下几个文件夹的边界非常清晰。PowerEditor是主工程所在,里面又分src和visual.net两部分,前者放核心实现,后者放各版本 Visual Studio 的解决方案文件。scintilla和lexilla是编辑控件与词法分析器的源码,Notepad++ 对它们做了少量定制,但大部分逻辑保持上游同步。boostregex是正则表达式引擎的封装,tinyxml用于解析配置和语言定义文件。还有一个nppPlugins目录,里面是官方插件的示例代码,不是主程序的一部分,但它是理解插件接口最直接的入口。
我一般会先看PowerEditor/src下的文件命名规律:NppMain.cpp是入口,Notepad_plus.cpp是主窗口逻辑,ScintillaEditView.cpp封装了编辑视图的操作,Buffer.cpp管理文档缓冲区。这几个文件基本决定了你改功能时的落点。如果你要加一个菜单项,改的是Notepad_plus.cpp和资源文件;如果要改语法解析行为,得去lexilla里找对应的 lexer。
2.2 依赖库的版本与编译顺序
源码包里通常自带预编译的第三方库,但版本必须和工程配置匹配。常见做法是打开PowerEditor/visual.net下的解决方案后,先检查Additional Include Directories和Additional Library Directories是否指向了正确的相对路径。如果路径里出现了绝对路径(比如某台机器的用户目录),说明这份源码在打包前没有做路径清理,你需要手动改成相对路径。
编译顺序上,Scintilla和Lexilla应该先于主工程编译,因为它们产出的是静态库或动态库,主工程链接时依赖这些产物。Visual Studio 的解决方案配置里通常已经设好了项目依赖,但如果你只打开了主工程的 vcxproj 而没有加载整个解决方案,就会遇到「找不到 Scintilla.lib」这类错误。
# 在源码根目录下确认关键子模块是否存在 ls PowerEditor/src/NppMain.cpp ls scintilla/src/ScintillaBase.cxx ls lexilla/src/Lexilla.cxx ls boostregex/boost/regex.hpp这几行命令的作用是快速验证源码完整性。NppMain.cpp是主程序入口,ScintillaBase.cxx是编辑控件核心,Lexilla.cxx是词法分析器入口,boost/regex.hpp是正则库头文件。如果任何一个缺失,说明压缩包不完整或者解压时丢了文件,后续编译必然失败。
提示:不要试图用 MinGW 或 Clang 直接编译这套源码。官方工程深度绑定了 MSVC 的工具链和 Windows SDK 的特定版本,换编译器会触发大量 Win32 API 兼容问题,新手很容易在这里翻车。
3. 从零编译到可运行:Visual Studio 配置与首次构建
3.1 工具链版本选择与工程升级
Notepad++ v8.6.6 的官方构建环境是 Visual Studio 2022,平台工具集是 v143,Windows SDK 版本在项目属性里写死为 10.0。如果你装的是 VS2019,打开解决方案时会提示「重定向项目」,点确认后工具集变成 v142,大部分代码能编过,但个别 C++17 特性可能报错。我的建议是直接用 VS2022,省去版本适配的麻烦。
打开PowerEditor/visual.net/Notepad_plus.sln后,先别急着构建。在解决方案资源管理器里右键主工程,查看「属性」→「C/C++」→「常规」→「附加包含目录」,确认里面引用的路径都是相对路径且指向了源码包内的目录。如果看到类似C:\Users\xxx\Downloads\...的路径,全部删掉,换成$(SolutionDir)..\..\scintilla\include这种形式。
3.2 编译配置与常见报错处理
配置管理器里通常有 Debug、Release、Unicode Debug、Unicode Release 几种。第一次编译建议选 Release x64,因为 Debug 模式下 Scintilla 的断言会频繁弹出,干扰判断。构建时如果报「无法打开源文件 boost/regex.hpp」,说明 boostregex 的包含路径没配对;如果报「LNK1181 无法打开输入文件 Scintilla.lib」,说明 Scintilla 项目没有先编译或者输出目录不一致。
// 在 Notepad_plus.cpp 中定位主窗口初始化逻辑的典型片段 // 这段代码决定了菜单、工具栏和编辑区的创建顺序 BOOL Notepad_plus::init(HINSTANCE hInst, HWND hWnd) { _hInst = hInst; _hSelf = hWnd; // 先创建编辑视图,再挂载菜单和工具栏 if (!_scintillaView.init(_hInst, _hSelf)) return FALSE; if (!_mainMenu.init(_hInst, _hSelf)) return FALSE; // 语言定义文件从 %APPDATA%\Notepad++ 加载 _nativeLangSpeaker.init(_hInst, _hSelf); return TRUE; }这段代码的逻辑是:主窗口初始化时,先创建 Scintilla 编辑视图,再初始化菜单,最后加载语言资源。参数hInst是应用实例句柄,hWnd是主窗口句柄。如果你在改启动流程,比如想延迟加载某个插件,就应该在这个函数里调整调用顺序。注意_nativeLangSpeaker.init会去读用户目录下的配置文件,如果目录不存在,程序会静默失败而不是崩溃,这是排查「界面语言不对」问题的关键点。
编译成功后,输出目录下会生成Notepad++.exe和若干 DLL。直接双击运行,如果弹出「缺少 Scintilla.dll」,把 Scintilla 项目产出的 DLL 复制到同一目录即可。如果界面能出来但菜单是英文的,检查nativeLang目录是否被正确复制到了输出目录。
注意:不要用管理员权限运行编译产物去测试插件加载,某些插件会往注册表写东西,污染你的开发环境。用普通权限跑,出问题了好清理。
4. 改功能与加插件:源码级定制的两条路径
4.1 直接修改主程序逻辑
如果你要改的是编辑器自身行为,比如把「关闭当前标签」的快捷键从 Ctrl+W 改成 Alt+W,落点在Notepad_plus.cpp的快捷键映射表里。搜索IDM_FILE_CLOSE或者Close相关的命令 ID,找到_accelerator数组或者ShortcutMapper的初始化代码。改完之后不需要动资源文件,重新编译主工程即可。
另一个常见需求是调整默认编码。Notepad++ 默认新建文件是 UTF-8 无 BOM,如果你想改成 GBK,得去Buffer.cpp里找Buffer::setEncoding的调用点,或者在NppParameters.cpp里改默认配置项。这类改动的影响面比较大,因为编码设置会联动影响语法高亮和文件保存行为,改完必须用多语言文本做回归测试。
// 在 NppParameters.cpp 中修改默认新建文件的编码 // 原逻辑通常是 BUFFER_ENC_UTF8,改成 BUFFER_ENC_ANSI 即为系统默认编码 void NppParameters::setDefaultNewDocEncoding(encodingType enc) { _defaultNewDocEncoding = enc; // 这个值会在 Buffer::init 时被读取 // 如果改成 ANSI,中文 Windows 下新建文件就是 GBK }参数enc是枚举类型,常见取值有BUFFER_ENC_UTF8、BUFFER_ENC_ANSI、BUFFER_ENC_UTF8_BOM。改这个值只影响新建文档,不影响已打开文件的编码识别。如果你发现改完没生效,检查是不是在config.xml里已经有用户级配置覆盖了默认值。
4.2 通过插件接口扩展功能
不想动主程序源码的话,插件是更干净的路径。nppPlugins目录下有一个NppPluginDemo,它展示了插件必须导出的几个函数:isUnicode、getName、getFuncsArray、beNotified、messageProc、setInfo。其中setInfo是宿主程序调用插件的入口,插件在这里保存NppData结构体,后续通过nppData._nppHandle和nppData._scintillaMainHandle来操作主程序和编辑控件。
// 插件入口:宿主程序通过这个函数把 NppData 传给插件 extern "C" __declspec(dllexport) void setInfo(NppData notpadPlusData) { nppData = notpadPlusData; // 保存宿主句柄,后续所有操作都依赖它 // 如果这里没保存,后面调用命令会静默失败 commandMenuInit(); }这段代码的关键在于NppData结构体,它包含三个句柄:主窗口句柄、主编辑区 Scintilla 句柄、第二编辑区 Scintilla 句柄。很多新手插件写完发现菜单出来了但点击没反应,就是因为setInfo里没有正确保存nppData,或者保存了但在多视图模式下用错了 Scintilla 句柄。常见做法是在beNotified里处理NPPN_TBMODIFICATION通知时再注册菜单,而不是在setInfo里直接注册。
提示:插件 DLL 的位数必须和主程序一致。64 位 Notepad++ 只能加载 64 位插件,32 位插件放进去不会报错,但菜单项永远不会出现。这是排查「插件装了没反应」的第一检查项。
5. 避坑与排查:源码编译和插件加载的五个血泪经验
5.1 现象:编译报错「无法打开包括文件: “Scintilla.h”」
原因:主工程的附加包含目录没有指向scintilla/include,或者 Scintilla 项目没有先编译导致头文件没被复制到公共输出目录。解决:在解决方案里确认 Scintilla 项目的「输出目录」和主工程的「附加库目录」一致,然后把scintilla/include手动加到主工程的包含路径里。
5.2 现象:程序启动后界面空白,菜单栏有但编辑区不显示
原因:Scintilla 的 DLL 没有和 exe 放在同一目录,或者 DLL 的位数不对。解决:检查输出目录下是否有Scintilla.dll和Lexilla.dll,用dumpbin /headers确认位数。如果 DLL 存在但界面还是空白,可能是 Scintilla 初始化时CreateWindow失败了,在ScintillaEditView::init里加日志输出窗口类注册结果。
5.3 现象:改了语言定义文件但语法高亮没变化
原因:Notepad++ 优先读取%APPDATA%\Notepad++\下的用户配置,源码目录里的nativeLang和langs.xml只是默认值。解决:要么删掉用户目录下的配置让程序重新生成,要么在启动参数里加-noPlugin -nosession并手动指定配置目录。我一般会在开发时把%APPDATA%\Notepad++整个重命名,强制程序走源码目录的默认配置。
5.4 现象:插件菜单出现了,但点击后主程序无响应
原因:插件在beNotified里处理通知时阻塞了主线程,或者messageProc里对WM_COMMAND的处理没有返回值。解决:插件里所有耗时操作都应该丢到独立线程,beNotified只做轻量级的状态更新。另外messageProc的返回值必须是TRUE或FALSE,返回0会被宿主当成未处理,导致消息循环卡住。
5.5 现象:Release 编译通过但运行崩溃,Debug 下正常
原因:Release 模式下编译器做了优化,某些未初始化的变量或越界访问在 Debug 下被断言拦住了,Release 下直接踩内存。解决:用 VS 的「调试」→「窗口」→「异常设置」勾选所有 C++ 异常,然后在 Release 配置下开启调试信息(/Zi)和禁用优化(/Od)逐步定位。常见元凶是Buffer类里的指针在文档关闭后没有置空,后续访问野指针。
6. 验证定制是否生效:一套可复用的回归检查清单
改完源码或写完插件后,别只靠「打开看一眼」来判断成功。我习惯用一套固定的检查流程,覆盖功能、边界和兼容性三个层面。下面这张表是我每次编译新版本后都会跑一遍的项目,你可以直接抄。
| 检查项 | 操作 | 预期结果 | 失败时看哪里 |
|---|---|---|---|
| 主窗口启动 | 双击 exe | 界面完整,无缺失 DLL 弹窗 | 输出目录 DLL 列表 |
| 新建文件编码 | Ctrl+N 后输入中文保存 | 默认编码符合你的修改目标 | NppParameters默认值 |
| 语法高亮 | 打开 .py 和 .cpp 文件 | 关键字着色正常 | lexilla对应 lexer |
| 插件加载 | 把插件 DLL 放入 plugins 目录 | 菜单出现且可点击 | setInfo是否保存句柄 |
| 多视图切换 | 右键标签→移动到另一视图 | 两个编辑区独立工作 | Scintilla 句柄选择逻辑 |
| 大文件打开 | 打开一个 50MB 日志 | 不卡死,滚动流畅 | Scintilla 的缓冲策略 |
这张表里最容易翻车的是「插件加载」和「多视图切换」。插件加载失败往往不是代码问题,而是 DLL 放错了目录——Notepad++ 只认plugins子目录下的同名文件夹里的 DLL,直接扔在根目录是不认的。多视图切换的坑在于,很多插件只保存了主 Scintilla 句柄,当用户把文档移到第二视图后,插件操作的是隐藏的那个编辑区,看起来就像「功能失效了」。正确做法是在每次执行命令前,通过NPPM_GETCURRENTSCINTILLA消息获取当前活跃的 Scintilla 句柄。
// 获取当前活跃的 Scintilla 句柄,避免多视图下操作错对象 int which = -1; ::SendMessage(nppData._nppHandle, NPPM_GETCURRENTSCINTILLA, 0, (LPARAM)&which); HWND curScintilla = (which == 0) ? nppData._scintillaMainHandle : nppData._scintillaSecondHandle; // 后续所有编辑操作都用 curScintilla,而不是固定的主句柄这段代码的核心是NPPM_GETCURRENTSCINTILLA消息,它返回 0 表示主视图活跃,返回 1 表示第二视图活跃。参数which是输出变量,必须传地址。拿到curScintilla后再调用::SendMessage(curScintilla, SCI_GETTEXTLENGTH, 0, 0)之类的编辑控件消息,才能保证操作的是用户当前看到的那个编辑区。这个习惯我从第一次写插件时就被坑过——当时插件在单视图下一切正常,用户一开双视图就反馈「按钮没反应」,查了一下午才发现是句柄用错了。
从那以后我每次改完源码或者写完插件,都会强制走一遍上面那张表,尤其是多视图和大文件这两项。源码定制这件事,编译通过只是起点,行为符合预期才算落地。希望帮到你。
本文还有配套的精品资源,点击获取