自动缩进代码中括号的算法

4
我正在开发一个代码编辑器(WinForms),我想知道如何实现特定的{和}功能,特别是使用括号(打开和关闭)自动缩进,就像实际的代码编辑器一样。
例如:
---|> { 和 }
就像这个1: enter image description here 编辑器是一个名为rtb的richtextbox。

我假设你可以编写一个递归函数,在用户关闭最后一个右花括号时启动,并一直遍历,直到找到一个左花括号。并在每个迭代阶段附加所需的缩进/制表符。 - S.N
@BrianRogers 抱歉,先生,我目前还没有关于缩进的代码,只有关于高亮的代码 :/ - Elegiac
@saravanan,我知道,但那不是我需要的,先生。我需要有关缩进的代码。 - Elegiac
2
你有看过 Richtextbox 中的 SelectionIndent 属性吗?这是官方网站链接:http://msdn.microsoft.com/zh-cn/library/system.windows.forms.richtextbox.selectionindent.aspx - Saravanan
@Elegiac,你确定要这样做吗?在尝试解决你的问题后,我发现有多个需要处理的事情,例如这个 - WiiMaxx
显示剩余7条评论
2个回答

5

好的,我的解决方案有缺陷,但是足以让您了解它的工作原理。

{
        {
                {
                        }
                }
        }

以下是我的代码

public partial class Form1 : Form
{
    private bool FLAG_Selftimer = false;
    private bool FLAG_KeyPressed = false;
    private int pos = 0;
    public Form1()
    {
        InitializeComponent();
    }

    private void richTextBox1_TextChanged(object sender, EventArgs e)
    {
        var rtb = sender as RichTextBox;
        var point = rtb.SelectionStart;

        if (!FLAG_Selftimer)
        {
            rtb.Text = ReGenerateRTBText(rtb.Text);
            FLAG_KeyPressed = false;
        }
        else
        {
            point ++;
            FLAG_Selftimer = false;
        }

        rtb.SelectionStart = point;
    }



    private string ReGenerateRTBText(string Text)
    {
        string[] text = Regex.Split(Text,"\n");

        int lvl = 0;
        string newString = "";
        foreach (string line in text)
        {
            line.TrimStart(' ');
            newString += indentation(lvl) + line.TrimStart(' ') + "\n";
            if (line.Contains("{"))
                lvl++;
            if (line.Contains("}"))
                lvl--;
        }

        FLAG_Selftimer = true;
        return (!FLAG_KeyPressed) ? newString : newString.TrimEnd('\n');
    }

    private string indentation(int IndentLevel)
    {
        string space = "";
        if(IndentLevel>0)
            for (int lvl = 0; lvl < IndentLevel; lvl++)
            {
                    space += " ".PadLeft(8);
            }

        return space;
    }

    private void richTextBox1_KeyPress(object sender, KeyPressEventArgs e)
    {
        FLAG_KeyPressed = true;
    }
}

我希望你能从中受益。

1
非常感谢您的帮助!虽然当我输入文本并按下空格时会出现错误,但是我已经明白了。在一周没有更好的答案之后,我将接受这个答案。再次感谢您的帮助! - Elegiac
@Elegiac,你需要一些逻辑来确定光标位置(rtb.SelectionStart),有些按键不会触发KeyPressEvent,这也会带来一些麻烦,这是一个非常棘手的问题 :-) - WiiMaxx
注意,先生。希望我能在截止日期之前完成这个任务,否则我就凉了 :/ - Elegiac

2

在阅读和使用代码之前,请仔细阅读以下内容:

  1. 我没有足够的时间来编写更好的代码。我只是尽量为您写了一个示例
  2. 我只是以简单的方式编写了这些代码,没有使用面向对象编程(OOP)
  3. 您可以使用枚举属性OOP的其他内容来改进这些代码。
  4. 您可以改进这些代码的逻辑;并且您可以使用多线程实现更好的性能
  5. 这个示例不是完整的。我只是实现了一个自动缩进示例,用于“分号 (;)”字符。

我应该为使用这些代码给出一些提示

  1. rtbCodes是示例项目中窗体上的RichTextBox控件的名称
  2. frmCodeEditor是示例项目中窗体的名称

您可以从以下地址下载示例项目:

4Shared -> Auto-Indention for Code Editor

SendSpace -> Auto-Indention for Code Editor

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace WindowsFormsApplication2
{
    public partial class frmCodeEditor : Form
    {
        char[] chrTracingKeyChars = new char[] { ';', '}', '\n' };
        char[] chrCheckingKeyChars = new char[] { '{', '(' };
        Point ptCurrentCharPosition;
        bool bolCheckCalling = false;
        int intInitialCursorPosition = 0;
        int intRemainingCharsOfInitialText = 0;
        int intNextCharIndex = 0;
        int intPrevCharIndex = 0;

        public frmCodeEditor()
        {
            InitializeComponent();
        }

        private void richTextBox1_TextChanged(object sender, EventArgs e)
        {
            AutoIndention(rtbCodes);
        }

        /// <summary>
        /// Implements Auto-Indention.
        /// </summary>
        /// <param name="rtb">A RichTextBox control</param>
        private void AutoIndention(RichTextBox rtb)
        {
            char chrLastChar = GetChar(rtb);

            if (chrLastChar == chrTracingKeyChars[0])
            {
                intRemainingCharsOfInitialText = rtb.TextLength - rtb.SelectionStart;
                intInitialCursorPosition = rtb.SelectionStart;
                ImplementIndentionForSemicolon(rtb);
            }
            else if (chrLastChar == chrTracingKeyChars[1])
            {
                ImplementIndentionForRightCurlyBracket(rtb);
            }
            else if (chrLastChar == chrTracingKeyChars[2])
            {
                ImplementIndentionForNewLineCharacter(rtb);
            }
        }

        /// <summary>
        /// Specifies current char based on the cursor position.
        /// </summary>
        /// <param name="rtb">A RichTextBox control</param>
        /// <returns>Returns a char.</returns>
        private char GetChar(RichTextBox rtb)
        {
            return GetChar(rtb.SelectionStart, rtb);
        }

        /// <summary>
        /// Specifies a char based on the specified index.
        /// </summary>
        /// <param name="intCharIndex">A char index</param>
        /// <param name="rtb">A RichTextBox control</param>
        /// <returns>Returns a char.</returns>
        private char GetChar(int intCharIndex, RichTextBox rtb)
        {
            if (intCharIndex != rtb.TextLength)
            {
                ptCurrentCharPosition = rtb.GetPositionFromCharIndex(intCharIndex - 1);
            }
            else
            {
                ptCurrentCharPosition = rtb.GetPositionFromCharIndex(intCharIndex);
            }
            return rtb.GetCharFromPosition(ptCurrentCharPosition);
        }

        /// <summary>
        /// Specifies current line number based on the cursor position.
        /// </summary>
        /// <param name="rtb">A RichTextBox control</param>
        /// <returns>Returns the line number.</returns>
        private int GetLineNumber(RichTextBox rtb)
        {
            return GetLineNumber(rtb.GetFirstCharIndexOfCurrentLine(), rtb);
        }

        /// <summary>
        /// Specifies the line number based on the specified index.
        /// </summary>
        /// <param name="intCharIndex">A char index</param>
        /// <param name="rtb">A RichTextBox control</param>
        /// <returns>Returns the line number.</returns>
        private int GetLineNumber(int intCharIndex, RichTextBox rtb)
        {
            return rtb.GetLineFromCharIndex(intCharIndex);
        }

        /// <summary>
        /// Implements indention for semicolon ";" character.
        /// </summary>
        /// <param name="rtb">A RichTextBox control</param>
        private void ImplementIndentionForSemicolon(RichTextBox rtb)
        {
            Dictionary<char, int> dicResult = IsExistCheckingKeyChars(rtb);
            if (dicResult[chrCheckingKeyChars[0]] != -1)
            {
                int intIndentionLevel = CheckingIndentionLevel(dicResult[chrCheckingKeyChars[0]], rtb);
                ImplementIndention(dicResult[chrCheckingKeyChars[0]], intIndentionLevel, rtb);
            }
        }

        private void ImplementIndentionForRightCurlyBracket(RichTextBox rtb)
        {

        }

        private void ImplementIndentionForNewLineCharacter(RichTextBox rtb)
        {

        }

        /// <summary>
        /// Checks current and previous lines for finding key-chars.
        /// </summary>
        /// <param name="rtb">A RichTextBox control</param>
        /// <param name="bolSearchCurrentLine">The search state</param>
        /// <returns>Returns first occurrences of key-chars before current char.</returns>
        private Dictionary<char, int> IsExistCheckingKeyChars(RichTextBox rtb, bool bolSearchCurrentLine = false)
        {
            GetChar(rtb);

            Dictionary<char, int> dicCheckingKeyCharsIndexes = new Dictionary<char, int>();
            for (int intCntr = 0; intCntr < chrCheckingKeyChars.Length; intCntr++)
            {
                dicCheckingKeyCharsIndexes.Add(chrCheckingKeyChars[intCntr], 0);
            }

            for (int intCntr = 0; intCntr < chrCheckingKeyChars.Length; intCntr++)
            {
                int intFirstIndexForChecking = 0;
                int intLastIndexForChecking = 0;
                for (int intLineCounter = GetLineNumber(rtb); intLineCounter >= 0; intLineCounter--)
                {
                    if (intLineCounter == GetLineNumber(rtb))
                    {
                        intLastIndexForChecking = rtb.GetCharIndexFromPosition(ptCurrentCharPosition);
                    }
                    else
                    {
                        intLastIndexForChecking = intFirstIndexForChecking - 1;
                    }
                    intFirstIndexForChecking = rtb.GetFirstCharIndexFromLine(intLineCounter);

                    try
                    {
                        dicCheckingKeyCharsIndexes[chrCheckingKeyChars[intCntr]] = rtb.Find(chrCheckingKeyChars[intCntr].ToString(), intFirstIndexForChecking,
                            rtb.GetCharIndexFromPosition(ptCurrentCharPosition), RichTextBoxFinds.NoHighlight | RichTextBoxFinds.None);
                        if (dicCheckingKeyCharsIndexes[chrCheckingKeyChars[intCntr]] != -1)
                        {
                            do
                            {
                                if (rtb.Find(chrCheckingKeyChars[intCntr].ToString(), intFirstIndexForChecking, rtb.GetCharIndexFromPosition(ptCurrentCharPosition),
                                    RichTextBoxFinds.NoHighlight | RichTextBoxFinds.None) != -1)
                                {
                                    dicCheckingKeyCharsIndexes[chrCheckingKeyChars[intCntr]] = rtb.Find(chrCheckingKeyChars[intCntr].ToString(), intFirstIndexForChecking,
                                        rtb.GetCharIndexFromPosition(ptCurrentCharPosition), RichTextBoxFinds.NoHighlight | RichTextBoxFinds.None);
                                }
                                intFirstIndexForChecking++;
                            } while (intFirstIndexForChecking != rtb.GetCharIndexFromPosition(ptCurrentCharPosition));
                            break;
                        }
                    }
                    catch
                    {
                        dicCheckingKeyCharsIndexes[chrCheckingKeyChars[intCntr]] = -1;
                        break;
                    }

                    if (bolSearchCurrentLine)
                    {
                        break;
                    }
                }
            }

            return dicCheckingKeyCharsIndexes;
        }

        /// <summary>
        /// Checks a line for calculating its indention level.
        /// </summary>
        /// <param name="intCharIndex">A char index</param>
        /// <param name="rtb">A RichTextBox control</param>
        /// <returns>Returns indention level of the line.</returns>
        private int CheckingIndentionLevel(int intCharIndex, RichTextBox rtb)
        {
            int intLineNumber = GetLineNumber(intCharIndex, rtb);
            int intIndentionLevelNumber = 0;

            intCharIndex = rtb.GetFirstCharIndexFromLine(intLineNumber);
            char chrChar = GetChar(intCharIndex, rtb);
            if (chrChar == '\n')
            {
                chrChar = GetChar(++intCharIndex, rtb);
            }

            if (chrChar != ' ')
            {
                return 0;
            }
            else
            {
                int intSpaceCntr = 0;
                while(chrChar == ' ')
                {
                    chrChar = GetChar(++intCharIndex, rtb);
                    if (chrChar == ' ')
                    {
                        intSpaceCntr++;
                    }

                    if (intSpaceCntr % 4 == 0 && intSpaceCntr != 0)
                    {
                        intIndentionLevelNumber++;
                        intSpaceCntr = 0;
                    }
                }

                if (intSpaceCntr % 4 != 0)
                {
                    intIndentionLevelNumber++;
                }
            }

            return intIndentionLevelNumber;
        }

        /// <summary>
        /// Implements Indention to the codes
        /// </summary>
        /// <param name="intCharIndex">A char index</param>
        /// <param name="intIndentionLevel">The number of indention level</param>
        /// <param name="rtb">A RichTextBox control</param>
        private void ImplementIndention(int intCharIndex, int intIndentionLevel, RichTextBox rtb)
        {
            intNextCharIndex = intCharIndex;

            intPrevCharIndex = intCharIndex;
            int intKeyCharsNumberInLine = 1;
            int intCurrentLineNumber = GetLineNumber(rtb);
            int intKeyCharLineNumber = GetLineNumber(intNextCharIndex, rtb);
            string[] strLinesTexts;
            Dictionary<char, int> dicResult;

            do
            {
                rtb.SelectionStart = intPrevCharIndex;
                dicResult = IsExistCheckingKeyChars(rtb);
                if (dicResult[chrCheckingKeyChars[0]] != -1)
                {
                    intKeyCharsNumberInLine++;
                    intPrevCharIndex = dicResult[chrCheckingKeyChars[0]];
                }
            } while (dicResult[chrCheckingKeyChars[0]] != -1);

            if (!bolCheckCalling)
            {
                if (intCurrentLineNumber == intKeyCharLineNumber)
                {
                    for (int intCntr = 1; intCntr <= intKeyCharsNumberInLine; intCntr++)
                    {
                        do
                        {
                            rtb.SelectionStart = intPrevCharIndex;
                            dicResult = IsExistCheckingKeyChars(rtb, true);
                            if (dicResult[chrCheckingKeyChars[0]] != -1)
                            {

                                intPrevCharIndex = dicResult[chrCheckingKeyChars[0]];
                            }
                        } while (dicResult[chrCheckingKeyChars[0]] != -1);

                        bolCheckCalling = true;
                        ImplementIndention(intPrevCharIndex, rtb);
                    }
                    return;
                }
            }

            bolCheckCalling = false;
            rtb.SelectionStart = intNextCharIndex;
            rtb.SelectionLength = 1;
            rtb.SelectedText = "\n" + rtb.SelectedText;
            intCurrentLineNumber = GetLineNumber(rtb);

            strLinesTexts = rtb.Lines;
            strLinesTexts[intCurrentLineNumber] = strLinesTexts[intCurrentLineNumber].Trim();

            for (int intIndentionCntr = 1; intIndentionCntr <= intIndentionLevel; intIndentionCntr++)
            {
                for (int intSpaceCntr = 1; intSpaceCntr <= 4; intSpaceCntr++)
                {
                    strLinesTexts[intCurrentLineNumber] = ' ' + strLinesTexts[intCurrentLineNumber];
                }
            }
            rtb.Lines = strLinesTexts;

            rtb.SelectionStart = intNextCharIndex + ((intIndentionLevel * 4) + 1);
            intNextCharIndex = rtb.SelectionStart;
            rtb.SelectionLength = 1;
            rtb.SelectedText = rtb.SelectedText + "\n";
            intCurrentLineNumber = GetLineNumber(rtb);

            strLinesTexts = rtb.Lines;
            strLinesTexts[intCurrentLineNumber] = strLinesTexts[intCurrentLineNumber].Trim();

            for (int intIndentionCntr = 1; intIndentionCntr <= intIndentionLevel + 1; intIndentionCntr++)
            {
                for (int intSpaceCntr = 1; intSpaceCntr <= 4; intSpaceCntr++)
                {
                    strLinesTexts[intCurrentLineNumber] = ' ' + strLinesTexts[intCurrentLineNumber];
                }
            }
            rtb.Lines = strLinesTexts;
            rtb.SelectionStart = intInitialCursorPosition + ((rtb.TextLength - intInitialCursorPosition) - intRemainingCharsOfInitialText);
            intNextCharIndex = rtb.SelectionStart;
            intPrevCharIndex = intNextCharIndex;
        }

        /// <summary>
        /// Implements Indention to the codes
        /// </summary>
        /// <param name="intCharIndex">A char index</param>
        /// <param name="rtb">A RichTextBox control</param>
        private void ImplementIndention(int intCharIndex, RichTextBox rtb)
        {
            int intIndentionLevel = CheckingIndentionLevel(intCharIndex, rtb);
            ImplementIndention(intCharIndex, intIndentionLevel, rtb);
        }
    }
}

我希望这个示例代码可以帮助您。
如果您对它们进行了改进,请更新并分享代码。

我能说什么。为了这个脚本,付出了很多努力和时间。我会记住并做到那些话......非常感谢您!这是一个非常棒的脚本,更加强大! :) 上帝保佑...... - Elegiac

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接