news 2026/10/2 8:18:34

Notepad3 源码重构实战:从 13K 行单体文件拆分 Notepad3Util 与 MsgCommand() 调度器

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Notepad3 源码重构实战:从 13K 行单体文件拆分 Notepad3Util 与 MsgCommand() 调度器
  • 桌面应用
  • 开发工具

【免费下载链接】Notepad3

Notepad like text editor based on the Scintilla source code. Notepad3 based on code from Notepad2 and MiniPath on code from metapath. Download Notepad3:

项目地址:https://gitcode.com/GitHub_Trending/no/Notepad3
点击查看免费下载

导读

本文基于 Notepad3 仓库中的重构计划文档 plans/notepad3_refactoring.md,完整梳理该项目对核心模块src/Notepad3.c的大型重构:包括将 12,985 行的单体文件拆分为独立工具模块Notepad3Util.c/.h,以及把 2,994 行的MsgCommand()巨型 switch 改造为 73 行薄调度器。读者将掌握大型 C 语言 Win32 项目"行为不变前提下拆分巨型文件"的完整方法论——从模块边界划分、风险分级、源码级调用点迁移,到构建验证与冒烟测试策略。


一、重构背景:单体文件的维护困境

1.1 重构前的核心数据

根据 plans/notepad3_refactoring.md 的 Context 部分,重构前src/Notepad3.c是项目中最大的源文件,规模数据如下:

指标重构前数值
Notepad3.c总行数12,985 行
静态函数数量约 55 个
静态变量数量约 60 个
MsgCommand()行数2,994 行
MsgCommand()switch case 数360+ 个

这些代码横跨多个互不相关的关注点:自动滚动(Auto-Scroll)、文件观察(File Observation)、位图加载(Bitmap Loading)、文本输入辅助(Text Input Helpers)、TinyExpr 表达式求值等,全部堆叠在同一文件中。

1.2 四大重构动机

文档明确列出了推动本次重构的四点动机:

  1. 可导航性(Navigability):在 13K 行的文件中查找代码效率低下;逻辑上成组的辅助函数应当归属各自独立的模块。
  2. 可维护性(Maintainability):MsgCommand()单体巨型函数使得难以对单个命令组进行推理和修改。
  3. 封装性(Encapsulation):自动滚动、文件观察、TinyExpr 等无关子系统的静态变量全部处于全局文件作用域,掩盖了它们真实的作用域边界。
  4. 一致性(Consistency):项目中其他模块(如 src/Edit.c、src/Styles.c、src/Dialogs.c)已经遵循清晰的.c/.h成对文件边界,Notepad3.c是例外。

1.3 不拆什么:刻意保留在 Notepad3.c 的代码

重构计划特别强调"What NOT to extract",以下代码被刻意保留在原文件,因为它们与整个应用生命周期或全局状态深度耦合:

  • _InitGlobals()/_CleanUpResources()—— 核心应用生命周期函数,触及所有子系统。
  • 消息队列辅助函数(_MQ_AppendCmd、MQ_ExecuteNext)—— 与定时器系统和 UI 更新紧密集成。
  • UI 更新辅助函数(_UpdateStatusbarDelayed、_UpdateToolbarDelayed、_UpdateTitlebarDelayed)—— 依赖消息队列和复杂全局状态。
  • _EditSubclassProc()—— Scintilla 子类化胶水代码,应当紧邻MainWndProc。
  • ParseCmdLnOption()—— 命令行解析,与程序启动流程归属一处。
  • MsgInitMenu()—— 读取 20+ 个状态变量来启用/禁用菜单项,本质上是全局应用状态。

这一"明确划定不拆范围"的决策,恰恰是重构成功的关键——不是所有代码都适合拆分,只有内聚性足够强的子系统才值得独立成模块。


二、已完成的 Part 2:MsgCommand() 薄调度器改造

2.1 从 2,994 行 switch 到 73 行分发链

文档记录MsgCommand()从2,994 行的巨型 switch 重构为73 行的薄调度器,将 360+ 个 case 委派给10 个静态处理器函数(仍保留在Notepad3.c中)。这一改造已在当前仓库中落地,见 src/Notepad3.c。

调度器实际结构(对应 src/Notepad3.c)如下:

LRESULT MsgCommand(HWND hwnd, UINT umsg, WPARAM wParam, LPARAM lParam) { // Language/theme menu range checks (inline) // Timer/notification cases (inline, return immediately) switch(iLoWParam) { case SCEN_CHANGE: ... return FALSE; ... } // Handler dispatch chain if (_HandleFileCommands(hwnd, umsg, wParam, lParam)) { return FALSE; } if (_HandleEncodingCommands(...)) { return FALSE; } ... return DefWindowProc(hwnd, umsg, wParam, lParam); }

2.2 10 个处理器函数的职责划分

文档给出的处理器分工表(当前仓库中 10 个 handler 已全部实现,行号见 src/Notepad3.c):

HandlerCase 数覆盖的命令范围
_HandleFileCommands~27IDM_FILE_*—— 打开/保存/打印/收藏夹/grepWin(源码位置 src/Notepad3.c)
_HandleEncodingCommands~10IDM_ENCODING_*、IDM_LINEENDINGS_*(src/Notepad3.c)
_HandleEditBasicCommands~30IDM_EDIT_UNDO..CMD_VK_INSERT—— 撤销/重做/剪切/复制/粘贴/缩进(src/Notepad3.c)
_HandleEditLineManipulation~42IDM_EDIT_ENCLOSESELECTION..IDM_EDIT_INSERT_GUID—— 行修改/排序/合并/大小写(src/Notepad3.c)
_HandleEditTextTransform~45IDM_EDIT_LINECOMMENT..IDM_EDIT_HEX2CHAR—— 注释/编解码/转义/十六进制(src/Notepad3.c)
_HandleEditFind~21IDM_EDIT_FINDMATCHINGBRACE..IDM_EDIT_GOTOLINE—— 查找/替换/书签(src/Notepad3.c)
_HandleViewAndSettingsCommands~99IDM_VIEW_*、IDM_SET_*—— 视图/设置/渲染(src/Notepad3.c)
_HandleHelpCommands~5IDM_HELP_*、IDM_SETPASS(src/Notepad3.c)
_HandleCmdCommands~90CMD_*—— 键盘快捷键/导航/窗口定位(src/Notepad3.c)
_HandleToolbarCommands~30IDT_*—— 通过s_ToolbarDispatch[]查找表分发(src/Notepad3.c)

2.3 调度链的短路语义

每个处理器遵循统一约定:返回true表示已处理,返回false则交给下一个处理器尝试。从 src/Notepad3.c 的分发链可以看到,这是一个典型的"责任链(Chain of Responsibility)"模式:命令 ID 按文件 → 编码 → 编辑基础 → 行操作 → 文本变换 → 查找 → 视图设置 → 帮助 → 快捷键 → 工具栏的顺序依次尝试匹配,全部未命中才落入DefWindowProc。

2.4 工具栏分发表:消灭 40 个重复 case

重构前有 40 个重复的IDT_*工具栏 case,重构后用静态查找表s_ToolbarDispatch[]取代(见 src/Notepad3.c)。表中包含29 个标准条目,将工具栏命令 ID 映射为菜单命令 ID,例如:

static const struct { unsigned idt; unsigned idm; } s_ToolbarDispatch[] = { { IDT_FILE_NEW, IDM_FILE_NEW }, { IDT_FILE_OPEN, IDM_FILE_OPEN }, { IDT_FILE_SAVE, IDM_FILE_SAVE }, { IDT_EDIT_UNDO, IDM_EDIT_UNDO }, { IDT_EDIT_CUT, IDM_EDIT_CUT }, { IDT_GREP_WIN_TOOL, IDM_GREP_WIN_SEARCH }, { IDT_VIEW_WORDWRAP, IDM_VIEW_WORDWRAP }, { IDT_VIEW_CHASING_DOCTAIL, IDM_VIEW_CHASING_DOCTAIL }, { IDT_VIEW_PIN_ON_TOP, IDM_SET_ALWAYSONTOP }, // ... 其余条目 };

查找表分发的核心逻辑(src/Notepad3.c):遍历表,命中后先通过IsCmdEnabled()检查菜单命令是否可用,可用则SendWMCommand()转发,不可用则SimpleBeep()提示。

另外保留2 个特殊 case(不经过查找表):

  • IDT_EDIT_COPY:当复制命令不可用时降级为 COPYALL(复制全部内容);
  • IDT_EDIT_CLEAR:当清除命令不可用时降级为SciCall_ClearAll()。

这种"查找表 + 特例兜底"的组合,既消除了重复代码,又保留了原有异常分支的行为语义,是重构中"行为零变化"原则的典型体现。


三、已完成的 Part 1(Phases 1-3):Notepad3Util 模块

3.1 新模块总览

Part 1 的前三个阶段已全部完成,新增了 src/Notepad3Util.c(349 行)与 src/Notepad3Util.h(50 行)两个文件。项目文件 src/Notepad3.vcxproj 与 src/Notepad3.vcxproj.filters 中已注册<ClCompile>和<ClInclude>条目,CLAUDE.md的核心模块表与.github/copilot-instructions.md的核心模块列表也同步更新。

该模块被定位为"从 Notepad3.c 提取的实用函数集合",头文件 src/Notepad3Util.h 通过#include "TypeDefs.h"和#include "SciCall.h"保持与核心类型的关联。

3.2 Phase 1:位图/图片加载(低风险,约 100 行)

Phase 1 移入的三个位图加载函数:

函数职责
NP3Util_LoadBitmapFile()从文件加载工具栏位图并校验尺寸合法性
NP3Util_CreateScaledImageListFromBitmap()从位图创建 DPI 缩放的图像列表
NP3Util_XXX_CreateScaledImageListFromBitmap()使用固定NUMTOOLBITMAPS的旧版变体

同时,NUMTOOLBITMAPS宏从Notepad3.c迁移到 src/Notepad3Util.h,当前值为31。

从源码看(src/Notepad3Util.c),NP3Util_LoadBitmapFile()的实现逻辑是:先通过Path_IsExistingFile()检查文件存在,再用LoadImage(..., LR_CREATEDIBSECTION | LR_LOADFROMFILE)加载,随后用GetObject()读取位图尺寸,校验bmWidth >= height * NUMTOOLBITMAPS——即位图宽度必须能容纳 31 个高度为 16 像素的图标单元,否则弹出IDS_MUI_ERR_BITMAP警告框;文件不存在则弹出IDS_MUI_ERR_LOADFILE错误提示。

而两个*CreateScaledImageListFromBitmap()函数(src/Notepad3Util.c)展示了 DPI 适配的关键细节:

UINT const dpi = Scintilla_GetWindowDPI(hWnd); if (!Settings.DpiScaleToolBar || (dpi == USER_DEFAULT_SCREEN_DPI)) { return himl; // default DPI, we are done } // Scale button icons/images int const scx = ScaleIntToDPI(hWnd, cx); int const scy = ScaleIntToDPI(hWnd, cy); HIMAGELIST hsciml = ImageList_Create(scx, scy, ILC_COLOR32 | ILC_MASK | ILC_HIGHQUALITYSCALE, ...);

即:仅在Settings.DpiScaleToolBar开启且系统 DPI 非 96 时才执行缩放,逐个图标通过ImageList_GetIcon(..., ILD_TRANSPARENT | ILD_PRESERVEALPHA | ILD_SCALE)提取后再加入缩放后的图像列表——这解释了Settings.DpiScaleToolBar配置项在底层图像处理上的实际作用。

3.3 Phase 2:自动换行配置(低风险,约 100 行)

移入的三个换行配置函数:

函数职责
NP3Util_SetWrapStartIndent()根据Settings.WordWrapIndent设置换行起始缩进
NP3Util_SetWrapIndentMode()设置换行缩进模式(same/indent/deep/fixed)
NP3Util_SetWrapVisualFlags(HWND)设置换行视觉标志符号

从 src/Notepad3Util.c 可以看到WordWrapIndent配置值(1-7)的完整语义映射:

void NP3Util_SetWrapStartIndent(void) { int i = 0; switch (Settings.WordWrapIndent) { case 1: i = 1; break; // 固定 1 字符 case 2: i = 2; break; // 固定 2 字符 case 3: i = 1 * (iIndentWidth ? iIndentWidth : iTabWidth); break; // 1×缩进/制表宽度 case 4: i = 2 * (iIndentWidth ? iIndentWidth : iTabWidth); break; // 2×缩进/制表宽度 default: break; } SciCall_SetWrapStartIndent(i); }

而NP3Util_SetWrapIndentMode()(src/Notepad3Util.c)将WordWrapIndent == 5/6/7分别映射为 Scintilla 的SC_WRAPINDENT_SAME/SC_WRAPINDENT_INDENT/SC_WRAPINDENT_DEEPINDENT,其余值则走NP3Util_SetWrapStartIndent()并设置SC_WRAPINDENT_FIXED。可见Settings.WordWrapIndent是一个 1-7 的多模式枚举:1/2 为固定字符数缩进、3/4 为按制表宽度倍数缩进、5/6/7 为 Scintilla 内建模式。

NP3Util_SetWrapVisualFlags()(src/Notepad3Util.c)则负责Settings.ShowWordWrapSymbols与Settings.WordWrapSymbols的解析:当WordWrapSymbols为 0 时自动回退为 22,然后按个位数字(1=行尾箭头贴文本、2=行尾箭头贴边缘)和十位数字(1=行首箭头贴文本、2=行首箭头贴边缘)组合出SC_WRAPVISUALFLAG_*标志。

3.4 Phase 3:自动滚动(中低风险,约 200 行)

Phase 3 将中键自动滚动(Firefox 风格的连续滚动)完整移入Notepad3Util:

  • 6 个静态变量迁移:s_bAutoScrollMode、s_bAutoScrollHeld、s_dwAutoScrollStartTick、s_ptAutoScrollOrigin、s_ptAutoScrollMouse、s_dAutoScrollAccumY——全部封装进 src/Notepad3Util.c,不再污染Notepad3.c的全局作用域。
  • 4 个常量迁移:AUTOSCROLL_TIMER_MS(30)、AUTOSCROLL_DEADZONE(15)、AUTOSCROLL_DIVISOR(60.0)、AUTOSCROLL_CLICK_THRESHOLD_MS(200),定义于 src/Notepad3Util.h。
  • 核心滚动逻辑:NP3Util_AutoScrollStart/Stop()、NP3Util_AutoScrollTimerProc()。
  • 状态访问器:NP3Util_IsAutoScrollMode()、NP3Util_IsAutoScrollHeld()、NP3Util_GetAutoScrollStartTick()、NP3Util_SetAutoScrollHeld()、NP3Util_AutoScrollUpdateMouse()——专供_EditSubclassProc读取/修改状态。

滚动算法细节(src/Notepad3Util.c)值得关注:定时器每 30ms 触发一次,先计算鼠标当前 Y 与起始 Y 的差值deltaY,死区(15 像素)内的移动不产生滚动;超出死区后,速度与超出量成正比(speed = (deltaY - deadzone) / 60.0),累积在s_dAutoScrollAccumY中,累积量达到整行数时调用SciCall_LineScroll()滚动并扣除已滚动的部分。这种"速度随距离递增 + 余数累积"的设计实现了平滑的加速度滚动效果。

3.5 Part 1 的净收益

文档记录:拆分后Notepad3.c从12,985 行降至 12,713 行。

3.6 修改文件清单

Part 1/Part 2 涉及的文件改动:

  • src/Notepad3.c —— 删除被提取的代码、更新调用点、新增#include "Notepad3Util.h"
  • src/Notepad3Util.c —— 新建实现文件
  • src/Notepad3Util.h —— 新建头文件
  • src/Notepad3.vcxproj —— 新增<ClCompile>与<ClInclude>条目
  • src/Notepad3.vcxproj.filters —— 新增 Source Files / Header Files 过滤器条目
  • CLAUDE.md —— 更新核心模块表、新增 Menu/Command Architecture 章节
  • .github/copilot-instructions.md—— 更新核心模块列表

四、未完成的 Phases 4-6:下一步迁移计划

文档详细规划了剩余三个阶段的迁移方案(当前仓库中这些函数仍在Notepad3.c内,尚未迁移)。

4.1 Phase 4:TinyExpr 表达式求值(约 130 行,中等风险)

待迁移的静态变量(src/Notepad3.c):

  • s_dExpression(double)—— 最近一次表达式求值结果
  • s_iExprError(te_int_t)—— 最近一次表达式错误码

待迁移函数与目标名:

当前函数新函数当前行号规模
_EvalTinyExpr(bool qmark)NP3Util_EvalTinyExpr(bool)2968~150
_InterpMultiSelectionTinyExpr(te_int_t*)NP3Util_InterpMultiSelectionTinyExpr(te_int_t*)11238~50

需要新增的访问器:

  • NP3Util_GetLastExpression()—— 返回s_dExpression(由_UpdateStatusbarDelayed读取)
  • NP3Util_GetLastExprError()—— 返回s_iExprError(由_UpdateStatusbarDelayed读取)

需要更新的 3 处调用点:

  • 行 7874(文档记为 6839):_EvalTinyExpr(false)—— 位于_HandleCmdCommands的CMD_ENTER_RETURNcase
  • 行 10057(文档记为 8921):_EvalTinyExpr(true)—— 位于_MsgNotifyFromEdit,输入?字符时触发
  • 行 11601(文档记为 10399):s_dExpression = _InterpMultiSelectionTinyExpr(&s_iExprError)—— 位于_UpdateStatusbarDelayed

依赖:Settings.EvalTinyExprOnSelection、Encoding_SciCP、SciCall_*(均通过头文件)、te_interp()(需在 Notepad3Util.c 中#include "tinyexpr/tinyexpr.h")、AllocMem/FreeMem(通过Helpers.h)。

风险点:状态栏代码目前直接读取s_dExpression/s_iExprError,迁移后必须改用 getter;te_interp()作用于 Scintilla 的原始字符缓冲区,对编码敏感,但迁移本身机制上是机械的。

4.2 Phase 5:文本输入辅助(约 300 行,中等风险)

待迁移的静态变量(行 189):

  • s_SelectionBuffer(char*)—— 用于自动闭合括号/引号跟踪的动态分配缓冲区

待迁移函数:

当前函数新函数当前行号规模
_HandleAutoIndent(int)NP3Util_HandleAutoIndent(int)9401~45
_HandleAutoCloseTags()NP3Util_HandleAutoCloseTags()9448~58
_SaveSelectionToBuffer()NP3Util_SaveSelectionToBuffer()9508~16
_EncloseSelectionBuffer(char,char)NP3Util_EncloseSelectionBuffer(char,char)9526~17
_HandleInsertCheck(SCNotification*)NP3Util_HandleInsertCheck(...)9545~89
_HandleDeleteCheck(SCNotification*)NP3Util_HandleDeleteCheck(...)9636~60

明确跳过:_IsIMEOpenInNoNativeMode()(行 9696)是#if 0包裹的死代码,不迁移。

需要新增的生命周期函数:

  • NP3Util_TextInputInit()—— 分配s_SelectionBuffer;在MsgCreate中调用
  • NP3Util_TextInputCleanup()—— 释放s_SelectionBuffer;在_CleanUpResources中调用

需要更新的 5 处调用点(均位于_MsgNotifyFromEdit,见 src/Notepad3.c):

  • 行 9745:_HandleInsertCheck(scn)
  • 行 9752:_SaveSelectionToBuffer()
  • 行 9768:_HandleDeleteCheck(scn)
  • 行 10048:_HandleAutoIndent(ich)
  • 行 10053:_HandleAutoCloseTags()

依赖:Settings.AutoIndent、Settings.AutoCloseQuotes、Settings.AutoCloseBrackets、Settings.AutoCloseTags(全局配置)、SciCall_*/Sci_*(头文件)、EditReplaceSelection()(通过 src/Edit.h)、AllocMem/FreeMem/ReAllocMem/SizeOfMem(通过 src/Helpers.h)。

风险分析(文档明确指出):这些函数运行在 Scintilla 通知的热路径上(SCN_MODIFIED、SCN_CHARADDED),但每次按键只调用一次而非逐字符批量调用,因此函数调用开销可忽略。主要风险在于确保s_SelectionBuffer的生命周期始终正确。

4.3 Phase 6:文件观察(约 450 行,高风险,留到最后)

待迁移的静态变量(行 531):

  • s_FileChgObsvrData(FCOBSRVDATA_T)—— 包含事件句柄(hEventFileChanged、hEventFileDeleted)、文件元数据(fdCurFile)、代次计数器(iObservationGeneration,采用InterlockedCompareExchange/InterlockedIncrement的 seqlock 模式)、后台工作线程句柄。在 Notepad3.c 中有 48 处非注释引用。

待迁移函数:

当前函数新函数当前行号规模
IsFileReadOnly()NP3Util_IsFileReadOnly()519~15
IsFileChangedFlagSet()NP3Util_IsFileChangedFlagSet()535~4
IsFileDeletedFlagSet()NP3Util_IsFileDeletedFlagSet()540~4
RaiseFlagIfCurrentFileChanged()NP3Util_RaiseFlagIfCurrentFileChanged()545~50
ResetFileObservationData(bool)NP3Util_ResetFileObservationData(bool)596~20
IsFileVarLogFile()NP3Util_IsFileVarLogFile()—~10
_ResetFileWatchingMode()NP3Util_ResetFileWatchingMode()—~10
NotifyIfFileHasChanged()NP3Util_NotifyIfFileHasChanged()—~20
WatchTimerProc(...)NP3Util_WatchTimerProc(...)—~15
LogRotateTimerProc(...)NP3Util_LogRotateTimerProc(...)—~25
AtomicSaveTimerProc(...)NP3Util_AtomicSaveTimerProc(...)—~50

需要新增的生命周期函数:

  • NP3Util_FileObservationInit()—— 创建事件句柄;替换InitInstance()中约 1843-1852 行的代码
  • NP3Util_FileObservationCleanup()—— 销毁工作线程与事件句柄;替换_CleanUpResources()中约 824-833 行的代码
  • NP3Util_GetFileObservationData()—— 返回PFCOBSRVDATA_T指针,供InstallFileWatching()访问结构体

循环依赖及解决方案:定时器回调会反向调用Notepad3.c中的函数——AtomicSaveTimerProc调用InstallFileWatching(false)和FileSave(FSF_SaveAlways);LogRotateTimerProc调用PostWMCommand(Globals.hwndMain, IDM_VIEW_CHASING_DOCTAIL)和InstallFileWatching(true);_ResetFileWatchingMode调用CheckCmd(GetMenu(...))。文档给出的解决方案是:Notepad3Util.c已#include声明了这些函数的 src/Notepad3.h,由链接器解析跨模块调用——这与Edit.c调用FileLoad()的模式完全相同。

主要调用点更新(约 48 处):

  • InitInstance()—— 事件创建改为NP3Util_FileObservationInit()(src/Notepad3.c)
  • _CleanUpResources()—— 清理改为NP3Util_FileObservationCleanup()(src/Notepad3.c)
  • InstallFileWatching()—— 直接结构体字段访问改为NP3Util_GetFileObservationData()->(src/Notepad3.c)
  • MsgFileChangeNotify()—— 读取标志、重置观察数据
  • _UpdateTitlebarDelayed()—— 调用IsFileChangedFlagSet()/IsFileDeletedFlagSet()
  • MsgInitMenu()—— 调用IsFileReadOnly()
  • _HandleViewAndSettingsCommands、_HandleCmdCommands—— 各种标志检查

线程安全注意事项:代次计数器使用InterlockedCompareExchange/InterlockedIncrement实现 seqlock 模式(后台工作线程 vs UI 线程)。从源码看(src/Notepad3.c),RaiseFlagIfCurrentFileChanged()正是通过"读前取 generation → 比较文件元数据 → 读后再取 generation 比对"的方式检测fdCurFile在并发ResetFileObservationData期间是否发生撕裂读取。迁移结构体不改变线程安全性,但文档特别提醒:NP3Util_GetFileObservationData()返回裸指针,调用者不得在可能触发重新分配的操作之间缓存该指针。

风险评级:高—— 48 处引用点(多为机械改名),但InstallFileWatching()直接操纵结构体字段(工作线程启动/取消、事件等待),SetTimer()调用中的定时器过程函数指针必须同步更新,线程正确性是关键。


五、验证策略:如何证明"行为零变化"

重构的核心承诺是"不改变任何行为",文档为此设计了分阶段验证策略:

5.1 每阶段三重验证

  1. 构建验证:至少执行Build\Build_x64.cmd Debug,确保无编译和链接错误;
  2. Diff 审计:执行git diff,确认改动是纯机械移动,不含任何逻辑变化;
  3. 按命令组冒烟测试:
    • Phase 4(TinyExpr):选中1+2后按?键,验证结果被插入;用列选择检查状态栏表达式显示;
    • Phase 5(文本输入):输入"验证自动闭合引号;输入{验证自动闭合括号;在if (...) {后按 Enter 验证自动缩进;输入<div>验证自动闭合</div>;在""上按 Backspace 验证成对删除;
    • Phase 6(文件观察):在其他编辑器中修改文件 → Notepad3 必须提示重新加载;启用日志跟踪模式(Ctrl+Shift+L)→ 验证自动刷新;测试原子保存(Settings2.AtomicFileSave=1);测试文件删除检测;快速开关文件 → 验证无定时器泄漏。

5.2 全量验证

全部阶段完成后执行Build\BuildAll.cmd Release,覆盖全部 4 个平台(对应 Build/BuildAll.cmd 及 Build 目录下的 x64/Win32/ARM64 构建脚本)。


六、预期最终结果:重构前后的量化对比

文档给出的最终预估指标:

指标重构前重构后(全部阶段完成)
Notepad3.c行数12,985~11,700
Notepad3Util.c行数0~1,250
MsgCommand()行数2,994~73(薄调度器)
工具栏 switch case40 个重复查找表
Notepad3.c静态函数数~55~35

七、可复用的重构方法论总结

从 plans/notepad3_refactoring.md 和当前仓库源码中,可以提炼出适用于大型 C 语言 Win32 项目的行为保持型重构要点:

  1. 先划边界,再动手:明确"拆什么"与"不拆什么"。生命周期函数、UI 状态密集型函数、与消息循环深度耦合的代码应留在原文件;只有内聚性强、状态自包含的子系统(位图、换行、自动滚动、表达式求值、文本输入、文件观察)才适合提取。
  2. 按风险分级排序:低风险先行(位图加载、换行配置),中风险居中(自动滚动、TinyExpr、文本输入),高风险留到最后(文件观察,涉及 48 处引用和线程安全)。
  3. 每阶段独立验证:构建 + diff 审计 + 针对该功能组的冒烟测试,把大规模重构分解为可独立回滚的小步骤。
  4. 状态封装成访问器:被提取模块的静态变量不暴露全局符号,通过NP3Util_IsAutoScrollMode()这类访问器函数供外部读取,保持封装性;生命周期由Init/Cleanup函数管理。
  5. 巨型 switch 转责任链:360+ case 的MsgCommand()拆成 10 个按命令族划分的处理器,配合短路返回语义;重复的工具栏 case 用查找表 + 特例兜底替代,既消除重复又保留分支语义。
  6. 循环依赖靠头文件与链接器消解:跨模块回调(如AtomicSaveTimerProc→InstallFileWatching)通过#include "Notepad3.h"声明 + 链接器解析,与项目现有模式(Edit.c调用FileLoad())保持一致。

截至当前仓库状态,Part 1(Phases 1-3)与 Part 2 已落地(Notepad3Util.c/.h已存在并被项目文件引用,10 个 handler 与s_ToolbarDispatch[]已实现在 src/Notepad3.c 中);Phases 4-6 的迁移对象仍位于Notepad3.c原位置,属于文档规划的后续工作。

  • 桌面应用
  • 开发工具

【免费下载链接】Notepad3

Notepad like text editor based on the Scintilla source code. Notepad3 based on code from Notepad2 and MiniPath on code from metapath. Download Notepad3:

项目地址:https://gitcode.com/GitHub_Trending/no/Notepad3
点击查看免费下载

相关推荐

上一篇:EIP-1014 Skinny CREATE2 深度解读:确定性合约地址、Gas 计量与 Constantinople 落地
下一篇:gpui-kit 无样式单行输入控件 Input 完整指南:状态模型、掩码校验与数字步进

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 8:18:17

015-0601-numpy与pandas-numpy与pandas-series

环境搭建 Anaconda 什么是Anaconda Anaconda官网地址:Advance AI with Open Source | Anaconda 简单来说,Anaconda = Python + 包和环境管理器(Conda)+ 常用库 + 集成工具。它适合那些需要快速搭建数据科学或机器学习开发环境的用户。Anaconda和Python相当于是汽车和发…

作者头像 李华
网站建设 2026/10/2 8:16:14

OpenRig cross-host架构深度解析:在多台机器上运行一个Agent团队

OpenRig cross-host架构深度解析&#xff1a;在多台机器上运行一个Agent团队 【免费下载链接】openrig Multi-agent harness that runs Claude Code and Codex together as one system 项目地址: https://gitcode.com/GitHub_Trending/op/openrig OpenRig 是一个开源的多…

作者头像 李华