Simple Color Syntax Code Editor for PHP written in C# and .NET: Part I

There are hundreds of editors to choose from out on the market(SlickEdit, CodeWright, Visual Studio, SharpDevelop), so the goal of this article is not to replace them, but to show you how to manipulate the RichEditBox to create color syntax as well as show you one way to print the RichEditBox to the printer.  For some reason, I got caught up doing some PHP coding, so I decided to focus coloring this editor according to PHP, a popular and efficient language for scripting inside of HTML. 
 
Figure 1 Print Preview of RichTextBox ColorSyntax Editor 
 
Figure 2 The Color Syntax Editor Application 
 
The Application is a simple Windows Form program that allows you to open and save the text files edited in the rich edit control, as well as print them out. The program takes advantage of a syntax text file for PHP which lists the functions and keywords contained in the PHP language.  Below is a sample of the syntax file. The file consists of 3 headers:  KEYWORDS, FUNCTIONS, and COMMENTS. The Application assigns colors to the keywords based on their specified category:
 
[KEYWORDS]
echo
int
integer
real
php
...
[FUNCTIONS]
sort
strlen
debugger_on
debugger_off
error_log
phpinfo
[COMMENTS]
//
# 
 
The file is read in using the ColorSyntaxEditor class shown as part of the design below.As seen in figure 3, the design consists of a Form class containing the GUI and FlickerFreeRichEditTextBox, a SyntaxReader class for reading language syntax from a file, and a WordAndPosition class used to hold information about words and positions of those words in the RichTextBox:
 
Figure 3 UML Design of Color Syntax Editor reverse engineered using WithClass UML Tool for C#
 
One thing you may be curious about is how do we assign different colors to individual words inside the RichTextBox?.The way this is done is through a trick that selects the word we are interested in coloring and then assigns a color to the selection (you can also assign a font to the selection if you wish): 
 
Listing 1 Coloring in a Selection in the RichTextBox
  1. Color c = Lookup(wp.Word);  
  2. richTextBox1.Select(wp.Position, wp.Length);  
  3. richTextBox1.SelectionColor = c;  
Editing the text has the following use case it uses to change the color:
  1. Event Handler traps that the RichTextBox text has changed
  2. Parse the current line of text  that the cursor is sitting on
  3. Parse each word in the line using a Regular Expression object and save words in an array.
  4. Select each parsed word and color according to the ColorSyntaxEditor.
Below is the method that implements this use case: 
  
Listing 2 Coloring in words and symbols on the current line 
  1. private void MakeColorSyntaxForCurrentLine()  
  2. {  
  3.     // Store current cursor position  
  4.     int CurrentSelectionStart = richTextBox1.SelectionStart;  
  5.     int CurrentSelectionLength = richTextBox1.SelectionLength;  
  6.     // find start of line  
  7.     int pos = CurrentSelectionStart;  
  8.     while ((pos > 0) && (richTextBox1.Text[pos - 1] != '\n'))  
  9.         pos--;  
  10.     // find end of line  
  11.     int pos2 = CurrentSelectionStart;  
  12.     while ((pos2 < richTextBox1.Text.Length) && (richTextBox1.Text[pos2] != '\n')) pos2++;  
  13.     string s = richTextBox1.Text.Substring(pos, pos2 - pos);  
  14.     // Parse the line into individual symbols and words  
  15.     int count = ParseLine(s);  
  16.     // Loop through each word, select, and color according to  
  17.     // Lookup function which calls the SyntaxReader   
  18.     for (int i = 0; i < count; i++)  
  19.     {  
  20.         WordAndPosition wp = TheBuffer[i];  
  21.         Color c = Lookup(wp.Word);  
  22.         richTextBox1.Select(wp.Position + pos, wp.Length);  
  23.         richTextBox1.SelectionColor = c;  
  24.     }  
  25.     // Restore Cursor  
  26.     if (CurrentSelectionStart >= 0)  
  27.         richTextBox1.Select(CurrentSelectionStart, CurrentSelectionLength);  
  28. }  
Comments of course need to be handled a bit differently so a comment case looks like the following:
  1. TextChanged Event Handler traps RichTextBox event
  2. Extract the current line that the cursor sits on
  3. if next symbol is a comment, color the entire line to the comment color 
The code for coloring the comment is shown below:
 
Listing 3 Coloring in comments in the RichTextBox 
  1. // check if its a C++ style comment  
  2. if (wp.Word == "/" && previousWord == "/")  
  3. {  
  4.     // color until end of line  
  5.     int posCommentStart = wp.Position - 1;  
  6.     posCommentEnd = pos2;// pos2 was determined in the previous  
  7.                          // code as being the last char position  
  8.                          // in the line   
  9.     richTextBox1.Select(posCommentStart + pos, posCommentEnd -  
  10.     (posCommentStart + pos));  
  11.     richTextBox1.SelectionColor = this.kCommentColor;  
  12. }  
How do we parse the words in a line?  The words of code need to be parsed using a regular expression that pulls out entire words such as(php, echo, var)  as an element to color and symbols (such as .;?+) as an element to color.  The regular expression that does this is shown in the code below:
 
Listing 4 A regular expression for parsing words and symbols 
  1. Regex r = new Regex(@"\w+|[^A-Za-z0-9_ \f\t\v]",  
  2. RegexOptions.IgnoreCase|RegexOptions.Compiled);  
This line of code creates a regular expression object that accepts either words or symbols.The alphanumeric words are filtered in using the \w expression. The symbols are filtered in using the [^A-Za-z0-9 \f\t\v] expression which basically says only accept a single character that is not an alphanumeric or whitespace characters.
 
The code for parsing a line into words and symbols and their corresponding positions is shown below:
 
Listing 5 Matching the regular expression against the words in the line of code
  1. private int ParseLine(string s)  
  2. {  
  3.     // Clear out the Array of code words   
  4.     TheBuffer.Initialize();  
  5.     int count = 0;  
  6.     // Create the regular expression object to match against the string   
  7.     Regex r = new Regex(@"\w+|[^A-Za-z0-9_ \f\t\v]", RegexOptions.IgnoreCase | RegexOptions.Compiled);  
  8.     Match m;  
  9.     // Loop through the string and continue to record  
  10.     // words and symbols and their corresponding positions and lengths   
  11.     for (m = r.Match(s); m.Success; m = m.NextMatch())  
  12.     {  
  13.         TheBuffer[count].Word = m.Value;  
  14.         TheBuffer[count].Position = m.Index;  
  15.         TheBuffer[count].Length = m.Length;  
  16.         // Console.WriteLine("Next Word = " + m.Value);  
  17.         count++;  
  18.     }  
  19.     // return the number of symbols and words  
  20.     return count;  
  21. }  
Reducing the Flicker
 
A few individuals have been kind enough to e-mail me a solution on how to reduce the flicker in this application (Mark Mihevc & JACK DUNN)  and I just wanted to take the opportunity in this article to thank them.  In order to reduce the flicker of the RichTextBox, we simply subclass the RichTextBox class with our own called FlickerFreeRichEditTextBox. Then we override the WndProc method in this class to give us control over when the WM_PAINT message is passed into the class.  The entire subclass of the rich text box control is shown below:
 
Listing 6 Subclassing the RichTextBox to reduce flicker in the control
  1. using System;  
  2. using System.Windows.Forms;  
  3. namespace ColorSyntaxEditor  
  4. {  
  5.     /// <summary>  
  6.     /// Summary description for FlickerFreeRichEditTextBox - Subclasses the RichTextBox to allow control over flicker  
  7.     /// </summary>   
  8.     public class FlickerFreeRichEditTextBox : RichTextBox  
  9.     {  
  10.         const short WM_PAINT = 0x00f;  
  11.         public FlickerFreeRichEditTextBox()  
  12.         {  
  13.         }  
  14.         public static bool _Paint = true;  
  15.         protected override void WndProc(ref System.Windows.Forms.Message m)  
  16.         {  
  17.             // Code courtesy of Mark Mihevc  
  18.             // sometimes we want to eat the paint message so we don't have to see all the  
  19.             // flicker from when we select the text to change the color.  
  20.             if (m.Msg == WM_PAINT)  
  21.             {  
  22.                 if (_Paint)  
  23.                     base.WndProc(ref m); // if we decided to paint this control, just call the RichTextBox WndProc  
  24.                 else  
  25.                     m.Result = IntPtr.Zero; // not painting, must set this to IntPtr.Zero if not painting therwise serious problems.  
  26.             }  
  27.             else  
  28.                 base.WndProc(ref m); // message other than WM_PAINT, jsut do what you normally do.  
  29.         }  
  30.     }  
  31. }  
To use the control above, simply change the static _Paint variable to false when we select and color the text and turn it back to true when we are done. Walla!  The flicker disappears.  Below are the calls in our code that toggles the _Paint flag in the RichTextBox's TextChanged Event Handler inside our Form.
  
Listing 7 Toggling the _Paint flag of the subclassed RichTextBox to reduce flicker
  1. private void richTextBox1_TextChanged(object sender, System.EventArgs e)  
  2. {  
  3.     if (populating)  
  4.         return;  
  5.     ColorSyntaxEditor.FlickerFreeRichEditTextBox._Paint = false// turn off flag to ignore WM_PAINT messages  
  6.     MakeColorSyntaxForCurrentLine();  
  7.     ColorSyntaxEditor.FlickerFreeRichEditTextBox._Paint = true// restore flag so we can paint the control  
  8. }  
Part II Printing the RichTextBox
 
We can some of the same techniques previously mentioned in this article to help us in printing out the rich text box.  Remember from previous articles in C# Corner on printing that all printing is performed from the PrintPage EventHandler produced by double clicking on the printDocument1 component. (See Printing in C#).  From the PrintPage event handler, we can get a handle to the device context of the printer.  We then pass this Graphics object to our GDI+  method that prints the RichEditControl.
  1. private void printDocument1_PrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs e)  
  2. {  
  3.     // Get device context for the printer   
  4.     Graphics g = e.Graphics;  
  5.     // Pass the device context to the function that  
  6.     // draws the RichTextBox to the printer   
  7.     lastChar = DrawEditControl(g, lastChar);  
  8.     if (lastChar != -1)  
  9.         e.HasMorePages = true;  
  10.     else  
  11.     {  
  12.         e.HasMorePages = false;  
  13.         lastChar = 0;  
  14.     }  
  15. }  
Note also that this method checks to see if we have multiple pages to print.  If we do, the HasMorePages is set to true and the PrintPage event will be triggered again. Now lets take a look at the DrawEditControl method which renders the contents of the RichTextBox control:
  1. private int lastChar = 0;  
  2. private int DrawEditControl(Graphics g, int lastChar)  
  3. // draw the control by selecting each character and deterimining its  
  4. // color   
  5. int xPos = 10;  
  6. int yPos = 40;  
  7. int kMargin = 50;   
  8. // start from the last position of the previous page  
  9. for (int c = lastChar; c<richTextBox1.Text.Length; c++)  
  10. {  
  11.     // Select a single character and retrieve the color and font  
  12.     richTextBox1.Select(c,1);  
  13.     char nextChar = richTextBox1.Text[c];  
  14.     Color theColor = richTextBox1.SelectionColor;  
  15.     Font theFont = richTextBox1.SelectionFont;  
  16.     // Determine the character height from the font  
  17.     int height = theFont.Height;   
  18.     // if the next character is a return character, increment the Y  
  19.     // Position  
  20.     if (nextChar == '\n')  
  21.     {  
  22.         // add to height on return characters  
  23.         yPos += (height + 3);   
  24.         xPos = 10;   
  25.         // Get the height of the default print page  
  26.         int paperHeight = printDocument1.PrinterSettings. DefaultPageSettings.PaperSize.Height;   
  27.         // Test to see if we went past the bottom margin of the page  
  28.         if (yPos > paperHeight - kMargin)  
  29.             return c;  
  30.     }  
  31.     // if the next character is a space or tab, increment the horizontal  
  32.     // position by half the height  
  33.     else if ((nextChar == ' ') || (nextChar == '\t'))  
  34.     {  
  35.         xPos += theFont.Height/2;  
  36.     }  
  37.     else  
  38.     {  
  39.         Regex r = new Regex(@"\w", RegexOptions.IgnoreCase | RegexOptions.Compiled);  
  40.         Match m;  
  41.         string nextWord = "";  
  42.         bool reduceAtEnd = false;  
  43.         m = r.Match(nextChar.ToString());  
  44.         // Determine if next character is alpha numeric  
  45.         if (m.Success)  
  46.             reduceAtEnd = true;  
  47.         else  
  48.             nextWord = nextChar.ToString();   
  49.         // use a regular expression matching alphanumerics  
  50.         // until a whole word is formed  
  51.         // by printing the whole word, rather than individual  
  52.         // characters, this way the characters will be spaced  
  53.         // better in the printout  
  54.         while (m.Success)  
  55.         {  
  56.             nextWord += nextChar;  
  57.             c++;  
  58.             nextChar = richTextBox1.Text[c];  
  59.             m = r.Match(nextChar.ToString());  
  60.         }   
  61.         if (reduceAtEnd)  
  62.         {  
  63.             c--;  
  64.         }   
  65.         // Draw the string at the current x position with the current font  
  66.         // and current selection color   
  67.         g.DrawString(nextWord, theFont, new SolidBrush(theColor),xPos, yPos);   
  68.         // Measure the length of the string to see where to advance the next  
  69.         // horizontal position   
  70.         SizeF thesize = g.MeasureString(nextWord, theFont);  
  71.         // Increment the x position by the size of the word  
  72.         xPos += (int) thesize.Width - 4;  
  73.     }   
  74. }   
  75. // All characters in the RichTextBox have been visited, return -1  
  76. return -1;  
The code above goes character by character in the RichEditBox,  selects the character, and uses the selection to determine the color and font. The characters are rendered to the printer using the Graphics method DrawString.Whole words are grouped using a regular expression before being sent to draw string to make a more aesthetic and accurately portrayed print representation. The cursor is advanced either horizontally or vertically according to the current character.  Return characters will advance the current drawing position vertically to the beginning of the next line.  All other characters advance the current drawing position horizontally.
 
Conclusions and Observations
 
The color syntax editor for PHP is a simple tool to help you understand how to utilize the RichTextBox control. You can edit the syntax file read by the SyntaxReader class to support C#,  C++,  Java, VB or any language you want.  It would be nice to have a way of setting a characters color in the rich edit control according to position rather than selection. I noticed also that the rich edit control can be streamed out into a file that maintains its color and font information.  This probably would be a nice future feature for this app.