一个关于如何使用 RichEdit50W 实现语法高亮等的想法
带有语法高亮等的 Masm 代码编辑器。
引言
在过去的 15 年里,我一直以业余爱好者的身份用汇编语言编程。我最近一直在从事的项目是基于 Microsoft RichEdit 控件的编辑器。虽然我接触过 C/C++ 语言,但我从未真正学过它。因此,我计划尝试用 C/C++ 也编写编辑器代码。我在此文中报告了这一点。
Microsoft Macro Assembler (MASM)
汇编器项目是用 MASM 语言编写的。对于感兴趣的人,可以在我的网站 (minor28.divdev.se) 上阅读 ASM 代码。这两个项目的文件的函数和变量在很大程度上是一致的,因此很容易理解。
编辑器
我开始使用 RichEdit20
。它对于大文件来说效果不理想,所以我改用了 RichEdit50W
。该应用程序可以打开多个文档,但不是 MDI 项目。所有文档只使用一个控件。
一些应用程序数据存储在注册表中 (HKEY_CURRENT_USER\Software\MINOR28\RichEditor)。
还有一个用于设置颜色、字体和关键字的对话框。最大撤销次数设置为 100,但未编程为可更改。
文件
- RichEditor.cpp
- ApplicationData.cpp
- SubRichEditProc.cpp
- OptionDlgProc.cpp
- OptionTabs.cpp
文档
文档是打开文件的文本内容。文档的显示和隐藏是通过 respective 文档的按钮执行的。隐藏的文档会根据以下结构临时存储
typedef struct tagARCHIVE{
UINT DocId;
LPSTR pRTF;
INT firstvisibleline;
CHARRANGE cr;
UINT margin;
CHAR FileName[MAX_PATH];
LPDOCUNDOS pDocUndos;
}ARCHIVE, *LPARCHIVE;
文件
- Document.cpp
- Tab.cpp
行号显示
行号和大纲是在 RichEdit
进程命令 WM_PAINT
上绘制的。在 RichEdit
客户区的左侧预留了一个数字边距和一个大纲边距。为每个边距创建一个位图来绘制行号和大纲标记。
文件
- DrawLineNumbersAndOutlinings.cpp
大纲
隐藏和显示选中文本是 RichEdit
控件的内置功能。大纲是应用程序处理此功能的方式。在这种情况下,有三种类型的关键字可以触发隐藏/显示操作,即“Proc - endp
”、“.if - .endif
”和“.while - .endw
”。
每个文档都有一个缓冲区来存储大纲数据。
typedef struct tagOUTLINING{
DWORD Type; //1=proc, 2=.if, 3=.while
DWORD compressed; //0=expanded, nonezero=compressed
DWORD nestLevel;
CHARRANGE cr;
UINT compLines; //number of compressed lines
}OUTLINING, *LPOUTLINING;
typedef struct tagDOCOUTLININGS{
UINT cOutlinings;
LPOUTLINING pOutlinings;
INT docID;
UINT maxNum;
}DOCOUTLININGS, *LPDOCOUTLININGS;
文件
- DrawLineNumbersAndOutlinings.cpp
- OutliningMarkers.cpp
缩进标记
缩进标记绘制在文本区域上方的透明位图上。此位图是使用 GDI+ API 创建和绘制的。每次绘制操作后向 RichEdit
命令 WM_PRINTCLIENT
发送一个 PostMessage
将会绘制这些标记。
文件
- DrawIndentlines.cpp
语法高亮
我使用控件自己的富文本格式来为关键字着色。我从一个空白文档中检索 RTF 头部,并用可以为各种关键字选择的颜色表来完成头部。所有文本都以 RTF 格式插入。
关键字存储在四个单独的文本文件中,关键字之间用空格分隔。启动应用程序时,所有关键字都会加载到一个字典中。
typedef struct tagKEY{
LPSTR pKey; //pointer to dictionary key string
LPSTR pValue; //pointer to dictionary value string
}KEY, *LPKEY;
typedef struct tagDICTIONARY{
LPKEY pKeys; //pointer to dictionary keys and values jump table
LPBYTE pKeyList; //pointer to dictionary key list
LPBYTE pValuesList; //pointer to dictionary value list
UINT cItems; //number of dictionary items
}DICTIONARY, *LPDICTIONARY;
文本通过扫描器和词法分析器转换为 RTF,扫描器和词法分析器定位每个关键字,然后标记提供程序用正确的颜色标记这些标记。
typedef struct tagTOKEN{
DWORD _Type;
COLORREF TxtCol;
DWORD Style;
LPSTR pText;
CHARRANGE cr;
DWORD Len;
}TOKEN, *LPTOKEN;
文件
- CreateDictionary.cpp
- FillDictionary.cpp
- TokenProvider.cpp
- Lexer.cpp
- RTFHeader.cpp
- Scanners.cpp
撤销/重做
这是一个内置功能。然而,在此应用程序中,由于每个操作涉及多个步骤,因此它被禁用了。因此,每个文档都将有一个自定义的撤销/重做操作缓冲区。
每个文档都有这样的结构
typedef struct tagDOCUNDOS{
UINT docID;
UINT isDirty;
UINT cUndo;
LPUNDO pUndo;
UINT cRedo;
LPUNDO pRedo;
}DOCUNDOS, *LPDOCUNDOS;
每个撤销/重做操作都有这样的结构
typedef struct tagUNDO{
DWORD action;
CHARRANGE inCr; //starting charrange
CHARRANGE undoCr; //charrange before action
CHARRANGE redoCr; //charrange after action
CHARRANGE outCr;
LPSTR pUndoText; //pointer to selected text to be replaced
LPSTR pRedoText; //pointer to new text to replace
struct tagUNDO * pDropUndo; //pointer to drop undo structure
}UNDO, *LPUNDO;
文件
- Undo.cpp
拖放文件
这也是一个内置功能。通过将 fAccept
参数设置为 TRUE
调用函数 DragAcceptFiles
,使其能够处理来自文件管理器 (File Manager) 的 WM_DROPFILES
消息。
拖放
这是一个内置功能。出于与撤销/重做相同的原因,此功能也被禁用,并创建了一个自定义的拖放。拖放基于 COM 技术。如果控件已注册进行 OLE 拖放操作,则会撤销注册,然后使用自定义的 IDropTarget
接口进行注册。
::OleInitialize(0);
if (::RegisterDragDrop(hRED1,(LPDROPTARGET)&pDropTarget)
==DRAGDROP_E_ALREADYREGISTERED){
::RevokeDragDrop(hRED1);
::RegisterDragDrop(hRED1,(LPDROPTARGET)&pDropTarget);
}
控件窗口消息 WM_LBUTTONDOWN
、WM_MOUSEMOVE
和 WM_LBUTTONUP
将执行 OLE dragdrop
操作。
if (::DoDragDrop((LPDATAOBJECT)&pDataObject,(LPDROPSOURCE)&pDropSource,
DROPEFFECT_COPY | DROPEFFECT_MOVE,&pdwEffect) == DRAGDROP_S_DROP){
if (pdwEffect == DROPEFFECT_MOVE && External==1){
//delete moving text from the original position
::SendMessage(hRED1,WM_CHAR,VK_DELETE,0);
}
}
dragdrop
操作在自定义的 dragdrop
接口方法中处理。
文件
- IDataObject.cpp
- IDataSource.cpp
- IDropTarget.cpp
- IEnumFORMATETC.cpp
- IUndknow.cpp
- DrawDragDropMarker.cpp
拖动文档
此功能与 dragdrop
操作无关。如果拖动文档按钮,可能会出现三种不同的光标
拖动操作在编辑器窗口内。无效果。
拖动操作在另一个打开的编辑器窗口内。文档将被传输到新的编辑器。
拖动操作在任何编辑器窗口外部。将打开一个新的编辑器并将文档传输到该编辑器。
typedef struct tagDOCUMENTEXPORT{
INT WinX1;
INT WinY1;
INT firstvisibleline;
CHARRANGE cr;
UINT margin;
CHAR FileName[MAX_PATH];
}DOCUMENTEXPORT, *LPDOCUMENTEXPORT;
汇编 vs C/C++
我认为这两种语言写起来很相似。在 C 中进行计算比在汇编中容易得多。但是,我认为 C 中所有需要正确处理的数据类型以及分散在许多地方的变量声明和函数原型的头文件非常复杂。在这里,汇编更加有序。
结论
您可以按原样使用。该应用程序可能并非没有错误。我相信我已经对文件和代码进行了结构化,以便可以轻松地将项目改编成任何语言。