65.9K
CodeProject 正在变化。 阅读更多。
Home

一个关于如何使用 RichEdit50W 实现语法高亮等的想法

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.79/5 (19投票s)

2015 年 9 月 16 日

CPOL

4分钟阅读

viewsIcon

25626

downloadIcon

1586

带有语法高亮等的 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_LBUTTONDOWNWM_MOUSEMOVEWM_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 中所有需要正确处理的数据类型以及分散在许多地方的变量声明和函数原型的头文件非常复杂。在这里,汇编更加有序。

结论

您可以按原样使用。该应用程序可能并非没有错误。我相信我已经对文件和代码进行了结构化,以便可以轻松地将项目改编成任何语言。

© . All rights reserved.