您的位置:首页 > 运维架构

SharpDevelop浅析_3_Internationalization & TextEditor 国际化、文档编辑器、语法高亮显示……

2008-12-11 09:21 477 查看
SharpDevelop浅析_3_Internationalization & TextEditor

国际化、文档编辑器、语法高亮显示……


1、Demo界面及功能解释
2、SharpDevelop的Internationalization的使用
3、SharpDevelop的Internationalization的实现分析
4、SharpDevelop的文档管理的基本概念
5、SharpDevelop的SyntaxHighlighting配置文件的定义
6、SharpDevelop的TextEditor控件的实现概述
7、待分析的部分
8、总结
[b]Demo下载[/b]

1、Demo界面及功能解释
启动后,打开文档(默认支持.cs, .js, .java, .aspx等类型文件的语法高亮显示,详见ICSharpCode.TextEditor/Resources/SyntaxModes.xml)、切换语言界面如下:



切换为中文语言环境后的界面如下:



功能说明:可以实时改变语言环境;提供对常用编程语言的编辑:支持语法高亮显示、括号匹配、设置书签;尚未提供查找/替换、代码折叠、代码提示/自动完成等功能。

2、SharpDevelop的Internationalization的使用
多语言的实现就是在显示时根据键获取相应语言环境下的键值(Dictionary<key,value>),因此在编写程序时应该引用键,而非直接书写要显示的字符。一般地,应用程序的显示包括菜单、状态栏、提示字符等,因此Demo中的主菜单在配置文件(参见Basic.addin)中使用label = "${res:Menu.File.Open}",退出应用程序的提示字符使用string s = StringParser.Parse("${res:Info.Exit}");。ICSharpCode.Core.dll支持语言环境的实时修改(修改后语言设置不需重启应用程序),因此要在应用程序订阅语言环境改变的事件,相关代码如下:




语言环境的修改及事件响应
1

//a, 设置语言
2

//引自SharpPad项目的Dialogs/SelectCulturePanel.cs
3

public override bool ReceiveDialogMessage(DialogMessage message)
4





{
5



if (message == DialogMessage.OK)

{
6



if (SelectedCulture != null)

{
7

PropertyService.Set("CoreProperties.UILanguage", SelectedCulture);
8

}
9

}
10

return true;
11

}
12

//返回当前窗体用户选择的语言设置(从显示国家国旗的ListView控件)
13



string SelectedCulture

{
14



get

{
15



if (listView.SelectedItems.Count > 0)

{
16

return listView.SelectedItems[0].SubItems[1].Text;
17

}
18

return null;
19

}
20

}
21

//b, 刷新当前应用程序
22

//引自SharpPad项目的SharpPad.cs
23

void IniFrm()
24





{
25

//


26

_menuStrip = new MenuStrip();
27

MenuService.AddItemsToMenu(_menuStrip.Items, this, "/michael/myMenus");
28

this.Controls.Add(_menuStrip);
29


30

PropertyService.PropertyChanged += new PropertyChangedEventHandler(PropertyService_PropertyChanged);
31



ResourceService.LanguageChanged += delegate

{
32

//更新菜单项的Text显示
33

foreach (ToolStripItem item in _menuStrip.Items)
34





{
35

if (item is IStatusUpdate)
36





{
37

((IStatusUpdate)item).UpdateText();
38

}
39

}
40

};
41

}
42

//引自ICSharpCode.Core的src/AddinTree/Addin/DefaultDoozers/MenuItem/Gui/IStatusUpdate.Core
43

using System;
44

namespace ICSharpCode.Core
45





{
46

public interface IStatusUpdate
47





{
48

void UpdateStatus();
49

void UpdateText();
50

}
51

}

另外,可以看到Demo中的选项命令窗口也采用了插件模式来构造窗体(实现细节就不多谈了),容器窗体是TreeViewOption,插件窗体如:SelectCulturePanel, SelectStylePanel, DemoNothing,真是“扩展--无处不在”呀。SharpDevelop源码中的这些窗体的成员控件是通过.xfrm配置文件配置,窗体继承自XmlUserControl来根据配置文件生成控件,有一定的灵活性。
新的语言包资源文件放在/data/resources/目录下(如StringResources.cn-gb.resources),注意应用程序默认语言选项以及在未找到相关语言资源时都是引用Entry中的myRes.resx资源文件;语言声明文件是/data/resources/languages/LanguageDefinition.xml,其格式如下:




LanguageDefinition.xml
1

<Languages>
2

<Languages name="Chinese (GB)" code="cn-gb" page="" icon="chinalg.png" />
3

<Languages name="German" code="de" page="" icon="germany.png" />
4

<Languages name="English" code="en" page="" icon="uk.png" />
5

</Languages>
SharpPad中动态显示可选语言项的分析类是LanguageService.cs和Language.cs,此处就不多解释了。

3、SharpDevelop的Internationalization的实现分析
SharpDevelop的多语言支持的键值对是通过本地资源文件存储的,当Demo中更改语言环境设置时,引发ICSharpCode.Core.dll中的事件及方法顺序如下:
PropertyService类的属性更新引发PropertyChanged事件 -> ResourceService响应接收到的事件并重新加载保存在内存中的资源键值对,然后引发LanguageChanged事件(由使用端接收并作相关处理,如Demo中的SharpPad.cs中的事件订阅/处理)属性更新的相关代码如下:




属性更改
1

//PropertyService类实际是提供了对Properties类的包装,因此直接看Properties类中的代码:
2

//引自ICSharpCode.Core/src/Services/PropertService/Properties.cs
3

public void Set<T>(string property, T value)
4





{
5

T oldValue = default(T);
6



if (!properties.ContainsKey(property))

{
7

properties.Add(property, value);
8



} else

{
9

oldValue = Get<T>(property, value);
10

properties[property] = value;
11

}
12

OnPropertyChanged(new PropertyChangedEventArgs(this, property, oldValue, value));
13

}
14

protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
15





{
16



if (PropertyChanged != null)

{
17

PropertyChanged(this, e);
18

}
19

}
20

public delegate void PropertyChangedEventHandler(object sender, PropertyChangedEventArgs e);
21

public event PropertyChangedEventHandler PropertyChanged;
资源服务类的事件响应代码如下:




ResourceService类的事件响应
1

//引自ICSharpCode.Core/src/Services/ResourceService/ResourceService.cs
2

//首先在类的初始化中订阅属性更改事件:
3

PropertyService.PropertyChanged += new PropertyChangedEventHandler(OnPropertyChange);
4

//类级别变量:
5

static Hashtable localStrings = null;
6

static Hashtable localIcons = null;
7

public static event EventHandler LanguageChanged;
8

//事件响应:
9

static void OnPropertyChange(object sender, PropertyChangedEventArgs e)
10





{
11



if (e.Key == uiLanguageProperty && e.NewValue != e.OldValue)

{
12

LoadLanguageResources((string)e.NewValue);
13

if (LanguageChanged != null)
14

LanguageChanged(null, e);
15

}
16

}
17

static void LoadLanguageResources(string language)
18





{
19

try
20





{
21

Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo(language);
22

}
23

catch (Exception)
24





{
25

try
26





{
27

Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo(language.Split('-')[0]);
28

}
29



catch (Exception)

{ }
30

}
31


32

localStrings = Load(stringResources, language);
33

if (localStrings == null && language.IndexOf('-') > 0)
34





{
35

localStrings = Load(stringResources, language.Split('-')[0]);
36

}
37


38

localIcons = Load(imageResources, language);
39

if (localIcons == null && language.IndexOf('-') > 0)
40





{
41

localIcons = Load(imageResources, language.Split('-')[0]);
42

}
43


44

localStringsResMgrs.Clear();
45

localIconsResMgrs.Clear();
46

currentLanguage = language;
47

foreach (ResourceAssembly ra in resourceAssemblies)
48





{
49

ra.Load();
50

}
51

}
52

static Hashtable Load(string name, string language)
53





{
54

return Load(resourceDirectory + Path.DirectorySeparatorChar + name + "." + language + ".resources");
55

}
56

static Hashtable Load(string fileName)
57





{
58



if (File.Exists(fileName))

{
59

Hashtable resources = new Hashtable();
60

ResourceReader rr = new ResourceReader(fileName);
61



foreach (DictionaryEntry entry in rr)

{
62

resources.Add(entry.Key, entry.Value);
63

}
64

rr.Close();
65

return resources;
66

}
67

return null;
68

}

4、SharpDevelop的文档管理的基本概念
代码编写工具的核心是代码编辑窗口(如打开一个.cs文件的窗口),如何在应用程序中存储该窗口内的字符内容?有些文件可能有上千行,统计下来可能有过万个字符,如果使用string对象存储字符内容,显然是不能满足性能要求,而且要实现语法高亮显示的话,还需要区分存储TextWord和相应颜色……
先看一下字符管理的基本要求:




ITextBufferStrategy接口
1

//引自ICSharpCode.TextEditor/src/Document/TextBufferStrategy/ITextBufferStrategy.cs
2

namespace ICSharpCode.TextEditor.Document
3





{
4



/**//// <summary>
5

/// Interface to describe a sequence of characters that can be edited.
6

/// </summary>
7

public interface ITextBufferStrategy
8





{
9



/**//// <value>
10

/// The current length of the sequence of characters that can be edited.
11

/// </value>
12



int Length

{
13

get;
14

}
15


16



/**//// <summary>
17

/// Inserts a string of characters into the sequence.
18

/// </summary>
19

/// <param name="offset">
20

/// offset where to insert the string.
21

/// </param>
22

/// <param name="text">
23

/// text to be inserted.
24

/// </param>
25

void Insert(int offset, string text);
26


27



/**//// <summary>
28

/// Removes some portion of the sequence.
29

/// </summary>
30

/// <param name="offset">
31

/// offset of the remove.
32

/// </param>
33

/// <param name="length">
34

/// number of characters to remove.
35

/// </param>
36

void Remove(int offset, int length);
37


38



/**//// <summary>
39

/// Replace some portion of the sequence.
40

/// </summary>
41

/// <param name="offset">
42

/// offset.
43

/// </param>
44

/// <param name="length">
45

/// number of characters to replace.
46

/// </param>
47

/// <param name="text">
48

/// text to be replaced with.
49

/// </param>
50

void Replace(int offset, int length, string text);
51


52



/**//// <summary>
53

/// Fetches a string of characters contained in the sequence.
54

/// </summary>
55

/// <param name="offset">
56

/// Offset into the sequence to fetch
57

/// </param>
58

/// <param name="length">
59

/// number of characters to copy.
60

/// </param>
61

string GetText(int offset, int length);
62


63



/**//// <summary>
64

/// Returns a specific char of the sequence.
65

/// </summary>
66

/// <param name="offset">
67

/// Offset of the char to get.
68

/// </param>
69

char GetCharAt(int offset);
70


71



/**//// <summary>
72

/// This method sets the stored content.
73

/// </summary>
74

/// <param name="text">
75

/// The string that represents the character sequence.
76

/// </param>
77

void SetContent(string text);
78

}
79

}
SharpDevelop采用的策略是使用带Gap的字符,Gap的长度小于预定规格时,按约定增加一定长度(有些类似于SqlServer的表空间管理?),插入/修改/删除字符时只是修改Gap的长度和位置和移动部分字符,核心函数如下:




GapTextBufferStrategy类
1

//引自ICSharpCode.TextEditor/src/Document/TextBufferStrategy/GapTextBufferStrategy.cs
2

//类级别变量:
3

char[] buffer = new char[0];
4

int gapBeginOffset = 0;
5

int gapEndOffset = 0;
6



/**//// <summary>
7

/// 小于此长度时增长Buffer/Gap
8

/// </summary>
9

int minGapLength = 32;
10



/**//// <summary>
11

/// 每次需要增长时,增长此长度的Gap
12

/// </summary>
13

int maxGapLength = 256;
14

//重要函数:
15

public void SetContent(string text)
16





{
17



if (text == null)

{
18

text = String.Empty;
19

}
20

buffer = text.ToCharArray();
21

gapBeginOffset = gapEndOffset = 0;
22

}
23

public void Insert(int offset, string text)
24





{
25

Replace(offset, 0, text);
26

}
27

public void Remove(int offset, int length)
28





{
29

Replace(offset, length, String.Empty);
30

}
31

public void Replace(int offset, int length, string text)
32





{
33



if (text == null)

{
34

text = String.Empty;
35

}
36


37

// Math.Max is used so that if we need to resize the array
38

// the new array has enough space for all old chars
39

PlaceGap(offset + length, Math.Max(text.Length - length, 0));
40

text.CopyTo(0, buffer, offset, text.Length);
41

gapBeginOffset += text.Length - length;
42

}
43

void PlaceGap(int offset, int length)
44





{
45

int deltaLength = GapLength - length;
46

// 如果Gap的长度足够大,则只是作移动相关字符的处理
47



if (minGapLength <= deltaLength && deltaLength <= maxGapLength)

{
48

int delta = gapBeginOffset - offset;
49

// check if the gap is already in place
50



if (offset == gapBeginOffset)

{
51

return;
52



} else if (offset < gapBeginOffset)

{
53

int gapLength = gapEndOffset - gapBeginOffset;
54

Array.Copy(buffer, offset, buffer, offset + gapLength, delta);
55



} else

{ //offset > gapBeginOffset
56

Array.Copy(buffer, gapEndOffset, buffer, gapBeginOffset, -delta);
57

}
58

gapBeginOffset -= delta;
59

gapEndOffset -= delta;
60

return;
61

}
62


63

// 否则,需要新分析buffer的大小,并修改offset等位置数值
64

int oldLength = GapLength;
65

int newLength = maxGapLength + length;
66

int newGapEndOffset = offset + newLength;
67

char[] newBuffer = new char[buffer.Length + newLength - oldLength]; //新分配后将有maxGapLength长度的Gap
68


69



if (oldLength == 0)

{
70

Array.Copy(buffer, 0, newBuffer, 0, offset);
71

Array.Copy(buffer, offset, newBuffer, newGapEndOffset, newBuffer.Length - newGapEndOffset);
72



} else if (offset < gapBeginOffset)

{
73

int delta = gapBeginOffset - offset;
74

Array.Copy(buffer, 0, newBuffer, 0, offset);
75

Array.Copy(buffer, offset, newBuffer, newGapEndOffset, delta);
76

Array.Copy(buffer, gapEndOffset, newBuffer, newGapEndOffset + delta, buffer.Length - gapEndOffset);
77



} else

{
78

int delta = offset - gapBeginOffset;
79

Array.Copy(buffer, 0, newBuffer, 0, gapBeginOffset);
80

Array.Copy(buffer, gapEndOffset, newBuffer, gapBeginOffset, delta);
81

Array.Copy(buffer, gapEndOffset + delta, newBuffer, newGapEndOffset, newBuffer.Length - newGapEndOffset);
82

}
83


84

buffer = newBuffer;
85

gapBeginOffset = offset;
86

gapEndOffset = newGapEndOffset;
87

}

有了基本的数据容器,核心问题已经解决了,但是在绘制界面时直接使用上面的类,显然不够方便,于是就定义了LineSegment和TextWord类来分别存储行、单词,注意这两个类存储的只是int类型的offset和length信息,而不存储字符或字符串对象。注意TextWord类中有个HighlightColor类型的变量用以存储语法高亮显示信息。
有了上面的这些类,便可以组合起来补充些其它信息对外提供服务了,IDocument接口存在的目的即在于此,它封装了ITextEditorProperties(是否显示空格/Tab/Eol/HRuler等)、UndoStack、ITextBufferStrategy、IHighlightingStrategy、FoldingManager、BookmarkManager等属性对象。

5、SharpDevelop的SyntaxHighlighting配置文件的定义
是时候对语法高亮显示作一些分析了,此处不详述其实现,而重点分析其配置定义,从中亦可以猜出部分实现。
相关配置文件均保存在ICSharpCode.TextEditor项目/Resource/目录下
首先SyntaxMode.xml文件中显示了已定义的文件类型及其声明文件位置,其解析类参见/src/Document/HighlightingStrategy/SyntaxModes/FileSyntaxModeProvider.cs




SyntaxMode.xml
1

<SyntaxModes version="1.0">
2

<Mode file = "ASPX.xshd"
3

name = "ASP/XHTML"
4

extensions = ".asp;.aspx;.asax;.asmx"/>
5


6

<Mode file = "BAT-Mode.xshd"
7

name = "BAT"
8

extensions = ".bat"/>
9


10

<Mode file = "CPP-Mode.xshd"
11

name = "C++.NET"
12

extensions = ".c;.h;.cc;.C;.cpp;.hpp"/>
13


14

<Mode file = "CSharp-Mode.xshd"
15

name = "C#"
16

extensions = ".cs"/>
17


18

<Mode file = "XML-Mode.xshd"
19

name = "XML"
20

extensions = ".xml;.xsl;.xslt;.xsd;.manifest;.config;.addin;.xshd;.wxs;.proj;.csproj;.vbproj;.ilproj;.booproj;.build;.xfrm;.targets;.xaml;.xpt;.xft;.map;.wsdl;.disco"/>
21

</SyntaxModes>
取CSharp-Mode.xshd(注:xshd是Xml Syntax Highlighting Definition的缩写)为例,查看其定义:




CSharp-Mode.xshd
1

<?xml version="1.0"?>
2

<SyntaxDefinition name = "C#" extensions = ".cs">
3

<Properties>
4

<Property name="LineComment" value="//"/>
5

</Properties>
6

<Digits name = "Digits" bold = "false" italic = "false" color = "DarkBlue"/>
7

<RuleSets>
8

<RuleSet ignorecase="false">
9

<Delimiters>&<>~!%^*()-+=|/#/{}[]:;"' , .?</Delimiters>
10

<Span name = "PreprocessorDirectives" rule = "PreprocessorSet" bold="false" italic="false" color="Green" stopateol = "true">
11

<Begin>#</Begin>
12

</Span>
13

<Span name = "BlockComment" rule = "CommentMarkerSet" bold = "false" italic = "false" color = "Green" stopateol = "false">
14

<Begin>/*</Begin>
15

<End>*/</End>
16

</Span>
17

<KeyWords name = "Punctuation" bold = "false" italic = "false" color = "DarkGreen">
18

<Key word = "?" />
19

<Key word = "," />
20

<Key word = "." />
21

<Key word = ";" />
22

<Key word = "(" />
23

<Key word = ")" />
24

<Key word = "[" />
25

<Key word = "]" />
26

<Key word = "{" />
27

<Key word = "}" />
28

<Key word = "+" />
29

<Key word = "-" />
30

<Key word = "/" />
31

<Key word = "%" />
32

<Key word = "*" />
33

<Key word = "<" />
34

<Key word = ">" />
35

<Key word = "^" />
36

<Key word = "=" />
37

<Key word = "~" />
38

<Key word = "!" />
39

<Key word = "|" />
40

<Key word = "&" />
41

</KeyWords>
42


43

<KeyWords name = "AccessKeywords" bold="true" italic="false" color="Black">
44

<Key word = "this" />
45

<Key word = "base" />
46

</KeyWords>
47


48

<KeyWords name = "OperatorKeywords" bold="true" italic="false" color="DarkCyan">
49

<Key word = "as" />
50

<Key word = "is" />
51

<Key word = "new" />
52

<Key word = "sizeof" />
53

<Key word = "typeof" />
54

<Key word = "true" />
55

<Key word = "false" />
56

<Key word = "stackalloc" />
57

</KeyWords>
58

</RuleSet>
59

</RuleSets>
60

</SyntaxDefinition>
上面的文件公摘选了部分标签,其中
<Property>标签定义了指定名称属性的指定值
<Digits>标签定义了数值的字体显示样式
<Delimiters>定义了分隔单词的字符
<Span>定义了包含在此指定Begin/End中的字符的显示样式,注意没有<End>标签的一般设stopateol(stop at end-of-line)为true
<KeyWords>指定了其子集<Key>声明的单词的显示样式

6、SharpDevelop的TextEditor控件的实现概述
接下来的任务是要显示和支持用户输入了,直接使用TextBox或RichTextBox好像都不太现实,效率上也必定有不少损失,于是SharpDevelop的方式是直接继承自Control, Panel, UserControl 的方式来实现编辑控件(参见ICSharpCode.TextEditor项目/src/Gui/...)。
首先使用TextEditorControlBase(继承自UserControl)封装当前文件路径、Encoding、IDocument对象、ITextEditorProperties对象、快捷键列表(Dictionary<Keys, IEditAction>类型)的变量,提供LoadFile()、SaveFile()等重要方法。
TextEditorControl类继承自上面的类,并声明了Panel、Splitter、TextAreaControl、PrintDocument控件,其中显示文件的核心控件是TextAreaControl,该控件在此类中被声明了两个变量,默认只有一个primaryTextArea显示,支持切分为两个窗口的显示,当有两个窗口时,Splitter控件才有效;PrinDocument控件用以打印输出内容;该类的另一个重要功能是提供了UnDo()、Redo()方法。
TextAreaControl(继承自Panel)控件,封装了TextArea、VScrollBar、HScrollBar控件,其中TextArea负责文件数据显示,另外两个控件负责文件内容大于可见尺寸时的滚动条服务。
TextArea(继承自Control)封装了TextView, IconBarMargin, GutterMargin, FoldMargin, SelectionManager, Caret 等控件或类对象,其中前四个控件均继承自AbstractMargin,代表区域对象,各自负责字符区域(较大的文件内容绘制区)、图标区域(如Bookmark图标所在列)、Gutter区域(如行号)、折叠控制区域的绘制,SelectionManager用以控制选中项,Caret用以控制光标位置调整和显示。

下面是一些我在读代码的过程中有过的疑问及解答:
a, 加载文件时发生了什么?
答:加载文件时控件根据文件的后缀名选择了相应的高亮显示策略,然后读取文件的内容并生成相应的GapTextBufferStrategy, LineSegment, TextWord 等对象,并且对所有的TextWord对象的HighlightColor类型成员变量完成分析赋值(用以在Paint函数中显示),相关代码如下:




LoadFile()相关代码
1

//取自TextEditorControlBase.cs
2

public void LoadFile(string fileName, bool autoLoadHighlighting, bool autodetectEncoding)
3





{
4

BeginUpdate();
5

document.TextContent = String.Empty;
6

document.UndoStack.ClearAll();
7

document.BookmarkManager.Clear();
8



if (autoLoadHighlighting)

{
9

//根据文件扩展名判断并赋值高亮显示的策略
10

document.HighlightingStrategy = HighlightingStrategyFactory.CreateHighlightingStrategyForFile(fileName);
11

}
12


13



if (autodetectEncoding)

{
14

Encoding encoding = this.Encoding;
15

//赋值
16

Document.TextContent = Util.FileReader.ReadFileContent(fileName, ref encoding, this.TextEditorProperties.Encoding);
17

this.Encoding = encoding;
18



} else

{
19



using (StreamReader reader = new StreamReader(fileName, this.Encoding))

{
20

Document.TextContent = reader.ReadToEnd();
21

}
22

}
23


24

this.FileName = fileName;
25

OptionsChanged();
26

Document.UpdateQueue.Clear();
27

EndUpdate();
28


29

Refresh();
30

}
31

//引自DefaultDocument.cs
32

ITextBufferStrategy textBufferStrategy = null;
33

ILineManager lineTrackingStrategy = null;
34



public string TextContent

{
35



get

{
36

return GetText(0, textBufferStrategy.Length);
37

}
38



set

{
39

Debug.Assert(textBufferStrategy != null);
40

Debug.Assert(lineTrackingStrategy != null);
41

OnDocumentAboutToBeChanged(new DocumentEventArgs(this, 0, 0, value));
42

//赋值
43

textBufferStrategy.SetContent(value);
44

//赋值 && 分析并完成高亮分析的赋值
45

lineTrackingStrategy.SetContent(value);
46


47

OnDocumentChanged(new DocumentEventArgs(this, 0, 0, value));
48

OnTextContentChanged(EventArgs.Empty);
49

}
50

}
51

//引自DefaultLineManager.cs
52

public void SetContent(string text)
53





{
54

lineCollection.Clear();
55



if (text != null)

{
56

textLength = text.Length;
57

// 生成LineSegment集合
58

CreateLines(text, 0, 0);
59

// 高亮分析
60

RunHighlighter();
61

}
62

}
b, 控件如何响应键盘事件?
答:对于方向键及快捷功能键通过预定义的实现IEditAction接口的类响应(执行功能,不影响字符内容);其它字母/数字键直接输入,同时执行更新Folding, Bookmark, Higlighting等属性信息。相关代码:




键盘事件响应
1

//功能键的定义(引自TextEditorControlBase.cs):
2

protected Dictionary<Keys, IEditAction> editactions = new Dictionary<Keys, IEditAction>();
3

protected TextEditorControlBase()
4





{
5

GenerateDefaultActions();
6

HighlightingManager.Manager.ReloadSyntaxHighlighting += new EventHandler(ReloadHighlighting);
7

}
8

void GenerateDefaultActions()
9





{
10

editactions[Keys.Left] = new CaretLeft();
11

editactions[Keys.Left | Keys.Shift] = new ShiftCaretLeft();
12

editactions[Keys.Left | Keys.Control] = new WordLeft();
13

editactions[Keys.Left | Keys.Control | Keys.Shift] = new ShiftWordLeft();
14

editactions[Keys.Right] = new CaretRight();
15

editactions[Keys.Right | Keys.Shift] = new ShiftCaretRight();
16

editactions[Keys.Right | Keys.Control] = new WordRight();
17

editactions[Keys.Right | Keys.Control | Keys.Shift] = new ShiftWordRight();
18

editactions[Keys.Up] = new CaretUp();
19

editactions[Keys.Up | Keys.Shift] = new ShiftCaretUp();
20

editactions[Keys.Up | Keys.Control] = new ScrollLineUp();
21

editactions[Keys.Down] = new CaretDown();
22

editactions[Keys.Down | Keys.Shift] = new ShiftCaretDown();
23

editactions[Keys.Down | Keys.Control] = new ScrollLineDown();
24


25

editactions[Keys.Insert] = new ToggleEditMode();
26

editactions[Keys.Insert | Keys.Control] = new Copy();
27

editactions[Keys.Insert | Keys.Shift] = new Paste();
28

editactions[Keys.Delete] = new Delete();
29

editactions[Keys.Delete | Keys.Shift] = new Cut();
30

editactions[Keys.Home] = new Home();
31

editactions[Keys.Home | Keys.Shift] = new ShiftHome();
32

editactions[Keys.Home | Keys.Control] = new MoveToStart();
33

editactions[Keys.Home | Keys.Control | Keys.Shift] = new ShiftMoveToStart();
34

editactions[Keys.End] = new End();
35

editactions[Keys.End | Keys.Shift] = new ShiftEnd();
36

editactions[Keys.End | Keys.Control] = new MoveToEnd();
37

editactions[Keys.End | Keys.Control | Keys.Shift] = new ShiftMoveToEnd();
38

editactions[Keys.PageUp] = new MovePageUp();
39

editactions[Keys.PageUp | Keys.Shift] = new ShiftMovePageUp();
40

editactions[Keys.PageDown] = new MovePageDown();
41

editactions[Keys.PageDown | Keys.Shift] = new ShiftMovePageDown();
42


43

editactions[Keys.Return] = new Return();
44

editactions[Keys.Tab] = new Tab();
45

editactions[Keys.Tab | Keys.Shift] = new ShiftTab();
46

editactions[Keys.Back] = new Backspace();
47

editactions[Keys.Back | Keys.Shift] = new Backspace();
48


49

editactions[Keys.X | Keys.Control] = new Cut();
50

editactions[Keys.C | Keys.Control] = new Copy();
51

editactions[Keys.V | Keys.Control] = new Paste();
52


53

editactions[Keys.A | Keys.Control] = new SelectWholeDocument();
54

editactions[Keys.Escape] = new ClearAllSelections();
55


56

editactions[Keys.Divide | Keys.Control] = new ToggleComment();
57

editactions[Keys.OemQuestion | Keys.Control] = new ToggleComment();
58


59

editactions[Keys.Back | Keys.Alt] = new Actions.Undo();
60

editactions[Keys.Z | Keys.Control] = new Actions.Undo();
61

editactions[Keys.Y | Keys.Control] = new Redo();
62


63

editactions[Keys.Delete | Keys.Control] = new DeleteWord();
64

editactions[Keys.Back | Keys.Control] = new WordBackspace();
65

editactions[Keys.D | Keys.Control] = new DeleteLine();
66

editactions[Keys.D | Keys.Shift | Keys.Control] = new DeleteToLineEnd();
67


68

editactions[Keys.B | Keys.Control] = new GotoMatchingBrace();
69

}
70

internal IEditAction GetEditAction(Keys keyData)
71





{
72



if (!editactions.ContainsKey(keyData))

{
73

return null;
74

}
75

return (IEditAction)editactions[keyData];
76

}
77

//功能键的响应(取自TextArea.cs):
78

protected override bool ProcessDialogKey(Keys keyData)
79





{
80

return ExecuteDialogKey(keyData) || base.ProcessDialogKey(keyData);
81

}
82

public bool ExecuteDialogKey(Keys keyData)
83





{
84

// try, if a dialog key processor was set to use this
85



if (DoProcessDialogKey != null && DoProcessDialogKey(keyData))

{
86

return true;
87

}
88


89



if (keyData == Keys.Back || keyData == Keys.Delete || keyData == Keys.Enter)

{
90



if (TextEditorProperties.UseCustomLine == true)

{
91



if (SelectionManager.HasSomethingSelected)

{
92

if (Document.CustomLineManager.IsReadOnly(SelectionManager.SelectionCollection[0], false))
93

return true;
94



} else

{
95

int curLineNr = Document.GetLineNumberForOffset(Caret.Offset);
96

if (Document.CustomLineManager.IsReadOnly(curLineNr, false) == true)
97

return true;
98

if ((Caret.Column == 0) && (curLineNr - 1 >= 0) && keyData == Keys.Back &&
99

Document.CustomLineManager.IsReadOnly(curLineNr - 1, false) == true)
100

return true;
101



if (keyData == Keys.Delete)

{
102

LineSegment curLine = Document.GetLineSegment(curLineNr);
103

if (curLine.Offset + curLine.Length == Caret.Offset &&
104



Document.CustomLineManager.IsReadOnly(curLineNr + 1, false) == true)

{
105

return true;
106

}
107

}
108

}
109

}
110

}
111


112

// if not (or the process was 'silent', use the standard edit actions
113

IEditAction action = motherTextEditorControl.GetEditAction(keyData);
114

AutoClearSelection = true;
115



if (action != null)

{
116

motherTextEditorControl.BeginUpdate();
117



try

{
118



lock (Document)

{
119

// 执行相关的功能操作
120

action.Execute(this);
121



if (SelectionManager.HasSomethingSelected && AutoClearSelection /**//*&& caretchanged*/)

{
122



if (Document.TextEditorProperties.DocumentSelectionMode == DocumentSelectionMode.Normal)

{
123

SelectionManager.ClearSelection();
124

}
125

}
126

}
127



} finally

{
128

motherTextEditorControl.EndUpdate();
129

Caret.UpdateCaretPosition();
130

}
131

return true;
132

}
133

return false;
134

}
135


136

//输入键的响应(取自TextArea.cs):
137

protected override void OnKeyPress(KeyPressEventArgs e)
138





{
139

base.OnKeyPress(e);
140

SimulateKeyPress(e.KeyChar);
141

e.Handled = true;
142

}
143

public void SimulateKeyPress(char ch)
144





{
145



if (Document.ReadOnly)

{
146

return;
147

}
148


149



if (TextEditorProperties.UseCustomLine == true)

{
150



if (SelectionManager.HasSomethingSelected)

{
151

if (Document.CustomLineManager.IsReadOnly(SelectionManager.SelectionCollection[0], false))
152

return;
153

} else if (Document.CustomLineManager.IsReadOnly(Caret.Line, false) == true)
154

return;
155

}
156


157



if (ch < ' ')

{
158

return;
159

}
160


161



if (!HiddenMouseCursor && TextEditorProperties.HideMouseCursor)

{
162

HiddenMouseCursor = true;
163

Cursor.Hide();
164

}
165

CloseToolTip();
166


167

motherTextEditorControl.BeginUpdate();
168

// INSERT char
169



if (!HandleKeyPress(ch))

{
170



switch (Caret.CaretMode)

{
171

case CaretMode.InsertMode:
172

InsertChar(ch);
173

break;
174

case CaretMode.OverwriteMode:
175

ReplaceChar(ch);
176

break;
177

default:
178

Debug.Assert(false, "Unknown caret mode " + Caret.CaretMode);
179

break;
180

}
181

}
182


183

int currentLineNr = Caret.Line;
184

int delta = Document.FormattingStrategy.FormatLine(this, currentLineNr, Document.PositionToOffset(Caret.Position), ch);
185


186

motherTextEditorControl.EndUpdate();
187



if (delta != 0)

{
188

// this.motherTextEditorControl.UpdateLines(currentLineNr, currentLineNr);
189

}
190

}
191

//输入键通过底层的方法输入字符和更改高亮显示,如下分析(以Insert为例,取自DefaultDocument.cs):
192

public void Insert(int offset, string text)
193





{
194



if (readOnly)

{
195

return;
196

}
197

OnDocumentAboutToBeChanged(new DocumentEventArgs(this, offset, -1, text));
198

DateTime time = DateTime.Now;
199

//增加字符
200

textBufferStrategy.Insert(offset, text);
201


202

time = DateTime.Now;
203

//更新LineSegment, TextWord对象
204

lineTrackingStrategy.Insert(offset, text);
205


206

time = DateTime.Now;
207


208

undoStack.Push(new UndoableInsert(this, offset, text));
209


210

time = DateTime.Now;
211

OnDocumentChanged(new DocumentEventArgs(this, offset, -1, text));
212

}
213

// 追踪到DefaultLineManager.cs中Insert()方法实际上调用到Replace(offset,length,string.Empty)方法:
214

public void Replace(int offset, int length, string text)
215





{
216

int lineNumber = GetLineNumberForOffset(offset);
217

int insertLineNumber = lineNumber;
218



if (Remove(lineNumber, offset, length))

{
219

--lineNumber;
220

}
221


222

lineNumber += Insert(insertLineNumber, offset, text);
223


224

int delta = -length;
225



if (text != null)

{
226

delta = text.Length + delta;
227

}
228


229



if (delta != 0)

{
230

AdaptLineOffsets(lineNumber, delta);
231

}
232

//启用高亮显示分析 ----- 注:此时的 markLines 仅存储变化的相关行,而SetContent时存储的是所有行,因此只是按需分析
233

RunHighlighter();
234

}
c, 文件字符的绘制究竟是在何处?
答:在TextView.cs中的public override void Paint(Graphics g, Rectangle rect)函数中,重要函数:PaintLinePart(),辅助绘制函数:DrawDocumentWord(), DrawBracketHighlight(), DrawSpaceMarker(), DrawVerticalRuler() 等

d, 括号匹配在何处被定义和捕捉更新?
答:TextArea.cs中的List<BracketHighlightingSheme> bracketshemes = new List<BracketHighlightingSheme>();变量存储在查找的匹配项,SearchMatchingBracket()方法中搜索并更新匹配项的显示,相关代码如下:




括号匹配
1

//引自TextArea.cs
2

List<BracketHighlightingSheme> bracketshemes = new List<BracketHighlightingSheme>();
3

Caret caret;
4

public TextArea(TextEditorControl motherTextEditorControl, TextAreaControl motherTextAreaControl)
5





{
6

// 省略无关代码
7

bracketshemes.Add(new BracketHighlightingSheme('{', '}'));
8

bracketshemes.Add(new BracketHighlightingSheme('(', ')'));
9

bracketshemes.Add(new BracketHighlightingSheme('[', ']'));
10


11

caret.PositionChanged += new EventHandler(SearchMatchingBracket);
12

// 省略无关代码
13

}
14

void SearchMatchingBracket(object sender, EventArgs e)
15





{
16



if (!TextEditorProperties.ShowMatchingBracket)

{
17

textView.Highlight = null;
18

return;
19

}
20

bool changed = false;
21



if (caret.Offset == 0)

{
22



if (textView.Highlight != null)

{
23

int line = textView.Highlight.OpenBrace.Y;
24

int line2 = textView.Highlight.CloseBrace.Y;
25

textView.Highlight = null;
26

UpdateLine(line);
27

UpdateLine(line2);
28

}
29

return;
30

}
31



foreach (BracketHighlightingSheme bracketsheme in bracketshemes)

{
32

// if (bracketsheme.IsInside(textareapainter.Document, textareapainter.Document.Caret.Offset)) {
33

Highlight highlight = bracketsheme.GetHighlight(Document, Caret.Offset - 1);
34



if (textView.Highlight != null && textView.Highlight.OpenBrace.Y >=0 && textView.Highlight.OpenBrace.Y < Document.TotalNumberOfLines)

{
35

//取消旧匹配项的高亮显示
36

UpdateLine(textView.Highlight.OpenBrace.Y);
37

}
38



if (textView.Highlight != null && textView.Highlight.CloseBrace.Y >=0 && textView.Highlight.CloseBrace.Y < Document.TotalNumberOfLines)

{
39

//取消旧匹配项的高亮显示
40

UpdateLine(textView.Highlight.CloseBrace.Y);
41

}
42

textView.Highlight = highlight;
43



if (highlight != null)

{
44

changed = true;
45

break;
46

}
47

// }
48

}
49



if (changed || textView.Highlight != null)

{
50

int line = textView.Highlight.OpenBrace.Y;
51

int line2 = textView.Highlight.CloseBrace.Y;
52



if (!changed)

{
53

textView.Highlight = null;
54

}
55

//更新显示新匹配项 OpenBrace
56

UpdateLine(line);
57

//更新显示新匹配项 CloseBrace
58

UpdateLine(line2);
59

}
60

}

7、待分析的部分
本篇讨论暂未涉及如下(有价值?)内容的分析:
SyntaxHighlighting实现分析
BookmarkManager, FoldingManager, FormattingManager等
Paint()处理中坐标分析及转换

8、总结
本篇分析对应的《Dissecting a C# Application Inside SharpDevelop》书中的章节:
Chapter 7: Internationalization
Chapter 8: Document Management
Chapter 9: Syntax Highlighting
Chapter 11: Writing the Editor Control

对于该Demo中尚未实现的代码折叠、字符串的查找/替换功能将在下个礼拜读了10,12,13,14章后作进一步分析
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐