[转]SharpDevelop浅析_3_文档编辑器、语法高亮显示

SharpDevelop浅析_3_文档编辑器、语法高亮显示

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
3public 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控件)
13string 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
23void 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
43using System;
44namespace 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
3public 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}
14protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
15{
16    if (PropertyChanged != null{
17        PropertyChanged(this, e);
18    }
19}
20public delegate void PropertyChangedEventHandler(object sender, PropertyChangedEventArgs e);
21public event PropertyChangedEventHandler PropertyChanged;


资源服务类的事件响应代码如下:
ResourceService类的事件响应
1//引自ICSharpCode.Core\src\Services\ResourceService\ResourceService.cs
2//首先在类的初始化中订阅属性更改事件:
3PropertyService.PropertyChanged += new PropertyChangedEventHandler(OnPropertyChange);
4//类级别变量:
5static Hashtable localStrings null;
6static Hashtable localIcons  null;
7public static event EventHandler LanguageChanged;
8//事件响应:
9static 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}
17static 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}
52static Hashtable Load(string name, string language)
53{
54    return Load(resourceDirectory + Path.DirectorySeparatorChar + name ".+ language ".resources");
55}
56static 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
2namespace 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//类级别变量:
3char[] buffer new char[0];
4int gapBeginOffset 0;
5int gapEndOffset  0;
6/// <summary>
7/// 小于此长度时增长Buffer/Gap
8/// </summary>
9int minGapLength 32;
10/// <summary>
11/// 每次需要增长时,增长此长度的Gap
12/// </summary>
13int maxGapLength 256;
14//重要函数:
15public void SetContent(string text)
16{
17    if (text == null{
18        text = String.Empty;
19    }
20    buffer = text.ToCharArray();
21    gapBeginOffset = gapEndOffset 0;
22}
23public void Insert(int offset, string text)
24{
25    Replace(offset, 0, text);
26}
27public void Remove(int offset, int length)
28{
29    Replace(offset, length, String.Empty);
30}
31public 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}
43void 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
2public 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
32ITextBufferStrategy  textBufferStrategy  null;
33ILineManager          lineTrackingStrategy null;
34public 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(this00, value));
42                //赋值
43        textBufferStrategy.SetContent(value);
44                //赋值 && 分析并完成高亮分析的赋值
45        lineTrackingStrategy.SetContent(value);
46       
47        OnDocumentChanged(new DocumentEventArgs(this00, value));
48        OnTextContentChanged(EventArgs.Empty);
49    }
50}
51//引自DefaultLineManager.cs
52public void SetContent(string text)
53{
54    lineCollection.Clear();
55    if (text != null{
56        textLength = text.Length;
57                // 生成LineSegment集合
58        CreateLines(text, 00);
59                // 高亮分析
60        RunHighlighter();
61    }
62}


b, 控件如何响应键盘事件?
答:对于方向键及快捷功能键通过预定义的实现IEditAction接口的类响应(执行功能,不影响字符内容);其它字母/数字键直接输入,同时执行更新Folding, Bookmark, Higlighting等属性信息。相关代码:
键盘事件响应
  1//功能键的定义(引自TextEditorControlBase.cs):
  2protected Dictionary<Keys, IEditAction> editactions new Dictionary<Keys, IEditAction>();
  3protected TextEditorControlBase()
  4{
  5    GenerateDefaultActions();
  6    HighlightingManager.Manager.ReloadSyntaxHighlighting += new EventHandler(ReloadHighlighting);
  7}
  8void 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}
70internal IEditAction GetEditAction(Keys keyData)
71{
72    if (!editactions.ContainsKey(keyData)) {
73        return null;
74    }
75    return (IEditAction)editactions[keyData];
76}
77//功能键的响应(取自TextArea.cs):
78protected override bool ProcessDialogKey(Keys keyData)
79{
80    return ExecuteDialogKey(keyData) || base.ProcessDialogKey(keyData);
81}
82public 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 >= 0&& keyData == Keys.Back &&
99                    Document.CustomLineManager.IsReadOnly(curLineNr 1false== 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 1false== 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):
137protected override void OnKeyPress(KeyPressEventArgs e)
138{
139    base.OnKeyPress(e);
140    SimulateKeyPress(e.KeyChar);
141    e.Handled true;
142}
143public 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):
192public 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)方法:
214public 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
2List<BracketHighlightingSheme> bracketshemes  new List<BracketHighlightingSheme>();
3Caret            caret;
4public 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}
14void 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 >=&& textView.Highlight.OpenBrace.Y < Document.TotalNumberOfLines) {
35            //取消旧匹配项的高亮显示
36            UpdateLine(textView.Highlight.OpenBrace.Y);
37        }
38        if (textView.Highlight != null && textView.Highlight.CloseBrace.Y >=&& 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()处理中坐标分析及转换
posted @ 2013-01-15 16:26  安度  阅读(562)  评论(0编辑  收藏  举报