Vamos a programar #67 - Syntax Highlighter M (beta)

Hola de nuevo a todos, el día de hoy vamos a continuar con más sobre expresiones regulares, pero mas específicamente, cómo resaltar la sintaxis usando código en C#.

En el post anterior vimos una prueba de concepto (Proof Of Concept {POC}) de un programa que sirve para realzar la sintaxis, pero debido a varias razones que son difíciles de explicar (o porque soy flojo), no todo el código estaba implementado. Por eso hoy veremos cómo es que funciona la versión beta de este programa y explicaré un poco que hace cada función.

Antes que nada, veamos el código que hace funcionar las cosas:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading;

namespace SyntaxHighlighter
{
	public partial class FrmMain : Form
	{
		private enum WordTypes : int
		{
			WType1 = 0,
			WType2,
			WType3,
			NType,
			OpType,
			SType,
			CType,
			OtherType
		}
		#region "Cargar Estilos por default"
		private Color FontColor1 = (Color)Properties.Settings.Default["ColorRW1"];
		private Color FontColor2 = (Color)Properties.Settings.Default["ColorRW2"];
		private Color FontColor3 = (Color)Properties.Settings.Default["ColorRW3"];
		private Color ColorComments = (Color)Properties.Settings.Default["ColorCom"];
		private Color ColorNum = (Color)Properties.Settings.Default["ColorNum"];
		private Color ColorStrings = (Color)Properties.Settings.Default["ColorStrings"];
		private Color ColorOperator = (Color)Properties.Settings.Default["ColorOp"];
		private Color GeneralColor = (Color)Properties.Settings.Default["GeneralFontColor"];
		private Font GeneralFont = (Font)Properties.Settings.Default["GeneralFont"];
		#endregion

	# region "Palabras reservadas"
		private string ReservedWords1 = @"\b(as|base|bool|break|byte|case|catch|char|checked|class|continue|decimal|default|delegate|do|double|else|enum|explicit|false|finally|fixed|float|for|foreach|goto|if|implicit|in|int|interface|is|lock|long|namespace|null|object|operator|out|params|ref|return|sbyte|short|sizeof|stackalloc|string|struct|switch|this|throw|true|try|typeof|uint|ulong|unchecked|ushort|using|var|void|while)\b";
		private string ReservedWords2 = @"\b(public|private|internal|protected|abstract|async|const|event|extern|new|override|partial|readonly|sealed|static|unsafe|virtual|volatile)\b";
		private string Comments = @"((\/\/.+)|(\/\*[\s\S]*?\*\/)|\b(\\\\)\b)";
		private string Numbers = @"((0[xX])(\d|[a-f|A-F])+)|(\b(\d+)\b)";
		private string Strings = "(\".*?\")";
		private string Operators = @"\[|\]|\(|\)|\.|\?|\b(and)\b|(\:\:)|\+|\-|\*|\/|\%|\&|\^|\!|\~|\=|\<|\>|\?\:|\++\|\-\-|\&\&|\<\<|\>\>|\=\=|\!\=|\<\=|\>\=|\+\=|\-\=|\*\=|\/\=|\%\=|\&\=|\=|\^\=|\<\<\=|\>\>\=|\-\>|\?\?|\=\>|";
	#endregion

	#region Delegados
		private delegate void UpadteRTBDelegate(int Start, int End, Color Colores);
		private delegate string GetTextDelegate();
	#endregion

		private void UpdateRTB(int Start, int End, Color Colores)
		{
			if (RTBMain.InvokeRequired)
				RTBMain.Invoke(new UpadteRTBDelegate(UpdateRTB), Start, End, Colores);
			else
			{
				RTBMain.SelectionStart = Start;
				RTBMain.SelectionLength = End;
				RTBMain.SelectionColor = Colores;
			}

		}

		private string GetText()
		{
			if (RTBMain.InvokeRequired)
				return (string)RTBMain.Invoke(new GetTextDelegate(GetText));
			else
				return RTBMain.Text;
		}

		private void DoTheWork()
		{
			Thread DoWork = new Thread(new ThreadStart(() => ColorizeAll()));
			DoWork.Start();
		}

		public FrmMain()
		{
			InitializeComponent();
		}

		private void ColorizeAll()
		{
			Colorize(ReservedWords1, WordTypes.WType1);
			Colorize(ReservedWords2, WordTypes.WType2);
			Colorize(Numbers, WordTypes.NType);
			Colorize(Operators, WordTypes.OpType);
			Colorize(Strings, WordTypes.SType);
			Colorize(Comments, WordTypes.CType);
		}

		private void SaveColor(WordTypes Tipo, Color Colores)
		{
			switch (Tipo)
			{
				case WordTypes.WType1:
					FontColor1 = Colores;
					Properties.Settings.Default["ColorRW1"] = Colores;
					Properties.Settings.Default.Save();
					break;
				case WordTypes.WType2:
					FontColor2 = Colores;
					Properties.Settings.Default["ColorRW2"] = Colores;
					Properties.Settings.Default.Save();
					break;
				case WordTypes.WType3:
					FontColor3 = Colores;
					Properties.Settings.Default["ColorRW3"] = Colores;
					Properties.Settings.Default.Save();
					break;
				case WordTypes.NType:
					ColorNum = Colores;
					Properties.Settings.Default["ColorNum"] = Colores;
					Properties.Settings.Default.Save();
					break;
				case WordTypes.OpType:
					ColorOperator = Colores;
					Properties.Settings.Default["ColorOp"] = Colores;
					Properties.Settings.Default.Save();
					break;
				case WordTypes.SType:
					ColorStrings = Colores;
					Properties.Settings.Default["ColorStrings"] = Colores;
					Properties.Settings.Default.Save();
					break;
				case WordTypes.CType:
					ColorComments = Colores;
					Properties.Settings.Default["ColorCom"] = Colores;
					Properties.Settings.Default.Save();
					break;
				case WordTypes.OtherType:
					GeneralColor = Colores;
					RTBMain.ForeColor = GeneralColor;
					Properties.Settings.Default["GeneralFontColor"] = Colores;
					Properties.Settings.Default.Save();
					break;
				default:
					break;
			}
		}

		private void Colorize(string Patron, WordTypes Tipo)
		{
			Color CurrentFontColor = GeneralColor;
			switch (Tipo)
			{
				case WordTypes.WType1:
					CurrentFontColor = FontColor1;
					break;
				case WordTypes.WType2:
					CurrentFontColor = FontColor2;
					break;
				case WordTypes.WType3:
					CurrentFontColor = FontColor3;
					break;
				case WordTypes.CType:
					CurrentFontColor = ColorComments;
					break;
				case WordTypes.NType:
					CurrentFontColor = ColorNum;
					break;
				case WordTypes.SType:
					CurrentFontColor = ColorStrings;
					break;
				case WordTypes.OpType:
					CurrentFontColor = ColorOperator;
					break;
				case WordTypes.OtherType:
					CurrentFontColor = GeneralColor;
					break;
			}
			//Si quitas el comentario siguiente y comentas la linea que sigue, puedes ver lo importante de la funcion GetText()
			//MatchCollection wordColl = Regex.Matches(RTBMain.Text, Patron);
			MatchCollection wordColl = Regex.Matches(GetText(), Patron);
			foreach (Match m in wordColl)
			{
				UpdateRTB(m.Index, m.Length, CurrentFontColor);
			}
		}

		private void TSBtnOpen_Click(object sender, EventArgs e)
		{
			OpenFileDialog OpDiag = new OpenFileDialog();
			OpDiag.Filter = "Código fuente C#|*.cs|Arvhivos de texto|*.txt|Todos los archivos|*.*";
			OpDiag.Multiselect = false;
			DialogResult DiagRes = OpDiag.ShowDialog();
			if (DiagRes == DialogResult.OK)
			{
				RTBMain.Clear();
				StreamReader SR = new StreamReader(OpDiag.FileName, Encoding.UTF8);
				RTBMain.AppendText(SR.ReadToEnd());
				DoTheWork();

			}
		}

		private void TSBtnSave_Click(object sender, EventArgs e)
		{
			SaveFileDialog SaveDiag = new SaveFileDialog();
			SaveDiag.Filter = "Texto enriquecido|*.rtf";
			if (SaveDiag.ShowDialog() == DialogResult.OK)
				RTBMain.SaveFile(SaveDiag.FileName);
		}

		private void TSBtnFont_Click(object sender, EventArgs e)
		{
			FontDialog FontDiag = new FontDialog();
			FontDiag.ShowApply = false;
			if (FontDiag.ShowDialog() == DialogResult.OK)
			{
				RTBMain.Font = FontDiag.Font;
				Properties.Settings.Default["GeneralFont"] = FontDiag.Font;
				Properties.Settings.Default.Save();
				DoTheWork();
			}
		}

		private void TSBtnColor_Click(object sender, EventArgs e)
		{
			ColorDialog ColorDiag = new ColorDialog();
			if (ColorDiag.ShowDialog() == DialogResult.OK)
			{
				SaveColor((WordTypes)TSLBWordTypes.SelectedIndex, ColorDiag.Color);
			}

		}

		private void FrmMain_Load(object sender, EventArgs e)
		{
			RTBMain.Font = GeneralFont;
			RTBMain.ForeColor = GeneralColor;
			TSLBWordTypes.SelectedIndex = 7;
		}

		private void TSBtnReDo_Click(object sender, EventArgs e)
		{
			DoTheWork();
		}

		private void TSLBWordTypes_TextChanged(object sender, EventArgs e)
		{

		}
	}
}

La explicación de cada parte en orden de aparición.

Primero tenemos una enumeración llamada "WordTypes" que hereda del tipo "int", esto solo es para poder usar su valor de manera mas sencilla. La enumeración esta compuesta  por los elementos:

  • "WType1" - y su valor es 0
  • "WType2" - Su valor es 1
  • "Wtype3" - Su valor es 2
  • "NType" - Su valor es 3
  • "OpType" - Su valor es 4
  • "SType" - Su valor es 5
  • "CType" - Su valor es 6
  • "OtherType" - Su valor es 7
Solo para tenerlo en cuenta, "WType" es por Word Type, "NType" es por Numbers Type, "OpType" es por Operator Type, "SType" es por String Type, "CType" es por Comment Type y "OtherType" su nombre es muy explicatorio. Si bien recuerdo, en la gran mayoría de los lenguajes se cubren todos los tipos de palabras reservadas, si alguno requiriera de algún tipo más, se harán las adecuaciones para que se puedan escoger mas tipos, pero por ahora cubrimos gran parte y el principal por ahora que es C#.

Después creamos la función "UpdateRTB()", está función servirá para poder darle los estilos al texto en el control "RTBMain" y hay dos razones para usar un procedimiento para esa tarea. El primero es que esta acción se va a realizar mas de una vez y la otra; es para poder usarla desde un "Thread" (alguna vez dije que en lugar de "Thread" usaría hilo pero naahh) y por eso es que justo antes de crear el procedimiento, también creamos un delegado que contiene los mismos parámetros que la función. La función recibe tres parámetro, el primero del tipo "int", el segundo del tipo "int" y el tercero del tipo "Color", dentro de la función lo primero que revisa es si el control esta siendo invocado, si es así, manda a llamar al delegado para que este establezca los parámetros. Si hacemos un poco de memoria, en el post anterior, vimos que para resaltar una palabra usábamos tres valores (de ahí los tres parámetros), el primero era donde iba a comenzar la selección, el segundo que tanto se iba a seleccionar y el tercero era el color para el resaltado. Con las adecuaciones que hicimos, ahora podemos hacer el resaltado desde otro "Thread".

La siguiente función es "GetText()", está función tiene cómo objetivo obtener el texto del control "RTBMain" Al igual que la función anterior, primero prueba si el control está siendo invocado, si es asi la funcion llama al delegado "GetTextDelegate" y regresa un valor del tipo "string" con el texto que el delegado obtuvo, en caso contrario devuelve un valor del tipo "string" con el texto de "RTBMain"
Aquí vemos la importancia de los delgados. Ya los veremos mas adelante ;)
La siguiente función es "DoTheWork()", está función crea un "Thread" diferente y ejecuta la función "ColorizeAll()"

El siguiente procedimiento es "ColorizeAll()", esta función su trabajo es llamar a la función "Colorize", llamándola tantas veces cómo tipos de palabras reservadas tenemos, debido a cómo está hecho el programa, aquí es muy importante conservar el orden (incluso ahora dos de los elementos están al revés por lo que podrás notar que alguna cosas tienen color que no deben, eso si, nada grave).

La siguiente función es "SaveColor". Esta función recibe dos parámetros, el primero es del tipo "WorTypes" y el segundo es del tipo "Color", esta función se usa para guardar los colores que el usuario elija para cada tipo de palabra reservada, usando la instrucción "switch", comprueba de que caso se trata y dependiendo de que valor sea "Tipo"; que es del tipo "WordTypes";  y guarda el valor en la configuración del programa.

Finalmente el procedimiento "Colorize". Recibe dos parámetro, el primero del tipo "string" que es el patrón de expresión regular que se va a buscar y el segundo del tipo "WordTypes". Al igual que el procedimiento para guardar las configuraciones, en este procedimiento "probamos" cual de los posibles valores es el que se va a evaluar y a partir de ahí cambiar al color que se designó para el resaltado.

Al implementar todas la funciones, el programa ya sirve, pero aun es beta, puesto que tienen errores básicos. Todos esto los arreglaremos, pero primero trataremos de implementar las funciones que serian "de cajón", es decir resaltar la sintaxis de otros lenguajes y no solo de C#. Eso lo haremos en el siguiente post, continuaremos con mas de este programa.

Y bien, por ahora es todo, cómo de costumbre, puedes bajar el código de mi dropbox para probarlo y modificarlo.

Los leo luego.

No hay comentarios.