zoukankan      html  css  js  c++  java
  • [转]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()处理中坐标分析及转换
  • 相关阅读:
    aspjpeg 组件在asp中的使用
    C# 使用 fckeditor 上传文件中文名乱码的问题---转
    我来挑战主页绑定,浏览器被绑架之终极方案!
    nginx简易配置
    树莓派安装中文输入法
    树莓派4超频至2.0GHz
    python3 requests使用连接池
    python3 语言特性5
    git日常使用
    python3 时间格式化
  • 原文地址:https://www.cnblogs.com/anduinlothar/p/2861434.html
Copyright © 2011-2022 走看看