Notice: Undefined index: print in /home/ftpsite/joshis.iprofil.cz/articles.php on line 61
Syntax highlighting klíčových slov pomocí RichTextBoxu - joshis.iprofil.cz
 
[ Webhosting profitux.cz ]
Nové osobní stránky Petra Dvořáka

Moje články

hertpl: Undefined index: page_title(template line: 251) in templates/articles.html on line 145

Verze pro tisk (ladí se...) >>

Syntax highlighting klíčových slov pomocí RichTextBoxu (C#)

Úvod

V tomto článku si předvedeme, jak je možné upravit .NET komponentu RichTextBox za účelem provádění syntax highlightingu, neboli zvýrazňování syntaxe (například na zvýrazňování kódu C/C++, C#, PHP, ...).

Pozn.: Implementace je spíše než ukázkovým řešením jen zjednodušení, určitě nebude vždy fungovat správně... je to tedy jen jakýsi "proof of concept".

Cílem je udělat to celé tak, že:

  • řešení je co nejvíce obecné, aby některé algoritmy (třeba už tokenizace, viz níže) mohly být v případě potřeby pozměněny
  • řešení je co nejvíce čitelné (protože o to jde ve webových tutoriálech především, optimalizaci za daným účelem si pak schopnější programátor zajisté provede sám)
  • zvýraznění syntaxe musí probíhat v "rozumném čase" (třeba při otevření souboru je potřeba obarvit celkem velký kus kódu)
  • text při zvýrazňování nemá blikat (to je častý nedostatek na webu nalezitelných řešení)
  • bude to umět operace Undo/Redo (taktéž často není diskutováno) - není totiž možné použít vestavěné operace Undo/Redo, neboť například operace Undo by pouze znovu odbarvila text, apod. (za tímto účelem si napíšeme nejprimitivnější implementaci těchto funkcí)

Soubory ke stažení

Náhled editoru používajícího syntax highlighting

Algoritmus

Obarvování textu bude založeno na tokenizaci kódu pomocí regulárního výrazu (.NET třída Regexp, metoda split()). K tomuto kompromisu - kompromis spočívá v "líném" použití třídy Regexp - jsem se uchýlil proto, aby kód, který zde budu prezentovat, zůstal rozumně dlouhý a co nejvíce srozumitelný. Na druhou stranu tak celý kód běží o něco pomaleji, než by mohl, kdyby byla tokenizace šitá na míru. Navíc toto řešení neumí obarvit například řetězce nebo komentáře (tokeny které mohou obsahovat mezeru, čárku, ... nebo jiný znak indikující hranici dvou tokenů). Jedná se tedy spíše o zvýrazňování klíčových slov (nicméně to se dá upravit právě napsáním vlastní tokenizace a určením "typu tokenu" spíše než konkrétního klíčového slova, jak to dělám zde)... Pro samotné obarvení textu se bude používat metod RichTextBoxu spojených s úpravami vybraného textu.

Implementace

Jendotlivé části implementace jsem kvůli přehlednosti rozdělil do následujících částí:

Kostra programu

Na začátku (poté, co přidáme do projektu novou třídu) upravíme kód do této podoby:

» Rozbalit/sbalit kód «

Máme zde tedy uvedené všechny potřebné jmenné prostory a deklaraci třídy extendedRichText, odvozené od RichTextBoxu. Povšimněte si také komenářů - ty nic neříkají o kódu samotném, používám je zde jako výukovou pomůcku pro to, abych vám mohl říct kam co psát.

Reprezentace tokenu

Začneme tedy psát třídu token na místo komentáře "Slot A":

» Rozbalit/sbalit kód «

Token je tedy v našem (jednoduchém, výukovém) pojetí slovo, které má k sobě asociovanou barvu a typ písma...

Datové položky, konstruktor třídy

Budeme pokračovat definicí těla třídy extendedRichText - všechno odteď dolů tedy píšeme do Slotu B (!!!).

Nejprve samozřejmně specifikujeme datové položky, o kterých už víme, že je budeme potřebovat. Po nich následuje definice třídy pro zpracování operací Undo/Redo, dále pak konstruktor třídy - extendedRichTex­t():

» Rozbalit/sbalit kód «

Historie úprav (Undo/Redo)

Již z definice třídy UndoClass je vidět, že operace Undo a Redo budou prováděny velice jednoduše - prostě si jen uložíme do seznamu aktuální text a pozici kurzoru v textu. Aby toto bylo únosné, neukládáme si historii po stisku každé klávesy, nýbrž po stisku "význačných kláves" - handler KeyPressEvent(­...) vypadá tedy takto:

» Rozbalit/sbalit kód «

Následují metody pro samotné provádění operací Undo a Redo. Metoda StoreNewUndo() slouží k uložení aktuálního bodu historie úprav (je privátní, není asi potřeba ji explicitně volat z venku), RedoEvent() je metoda pro operaci Redo, UndoEvent() je metoda pro operaci Undo (obě metody jsou veřejné).

» Rozbalit/sbalit kód «

Vyvolání metod UndoEvent() a RedoEvent() ponecháme na položce menu a k ní asociované klávesové zkratce, řešeni subclassingem vynecháme.

Metody pro vypnutí překreslování RichTextBoxu

Dále si rozšíříme třídu o metody BeginUpdate() a EndUpdate(), které zabrání (resp. povolí) překreslování RichTextBoxu (tím zabráníme problikávání).

Za tímto účelem nejprve jednorázově importujeme WinAPI funkci SendMessage(...) a definujeme podstatné konstanty... Je pěkné to uzavřít do "regionu", ať nám to nestraší...

» Rozbalit/sbalit kód «

Nyní už můžeme napsat přímo metody BeginUpdate() a EndUpdate():

» Rozbalit/sbalit kód «

Povšimněte si zakomentovaného řádku v metodě EndUpdate(). Obnovení (překreslení) RichTextBoxu ve chvíli, kdy opětovně povolíme překreslování totiž z principu do metody EndUpdate() nepatří, nicméně volání metody Refresh() po zavolání EndUpdate() je tak časté, že já jsem ve svém řešení tento řádek odkomentoval, abych to nepsal pořád dokola (ale zde si to předvedeme "pěkně", proto ho nechám zakomentovaný).

Dále je na začátku BeginUpdate() potřeba odebrat (a následně na konci EndUpdate() přidat) handler pro událost TextChanged - na to nezapomenout, jinak se nám budou lavinovitě valit události!

Tokenizace, obarvování textu

Teď se dostáváme k samotnému jádru naší třídy. Napíšeme si funkci pro tokenizaci textu (využijeme třídu Regexp), funkci na obarvování textu a handleru události TextChanged.

» Rozbalit/sbalit kód «

Kopírování a vkládání textu

Abychom mohli rychleji provádět vložení textu, budeme rozlišovat dva případy. Jeden z nich je, že schránka obsahuje prostý text. V tom případě si nepomůžeme a musíme celý text ve schránce obarvit (při vkládání velkého kusu kódu je to pomalejší). V případě, že schránka obsahuje RichText, je pravděpodobné, že se jedná o zkopírováný již jednou zvýrazněný text - v takovém případě text prostě vložíme. Samozřejmě to je hodně zjednodušený přístup - když si někdo zkopíruje RichText z jiného editoru a prostě ho vloží, obarvení nebude správné. Pro jednoduchost nám to ale bude stačit.

» Rozbalit/sbalit kód «

Metody pro kopírování (Copy) a vyjmutí (Cut) můžeme ponechat tak jak jsou...

Jak nakonec třídu používat

Příklad jednoduchého použití naší třídy:

public mainForm()
{
    InitializeComponent();
 
    token T1 = new token("if", Color.Blue, new Font("Courier New", 10, FontStyle.Bold));
    token T2 = new token("using", Color.Purple, new Font("Courier New", 10, FontStyle.Bold));
    token T3 = new token("namespace", Color.Purple, new Font("Courier New", 10, FontStyle.Bold));
    token T4 = new token("class", Color.Blue, new Font("Courier New", 10, FontStyle.Bold));
    token T5 = new token("public", Color.Blue, new Font("Courier New", 10, FontStyle.Bold));
    token T6 = new token("private", Color.Blue, new Font("Courier New", 10, FontStyle.Bold));
    token T7 = new token("new", Color.Blue, new Font("Courier New", 10, FontStyle.Bold));
    token T8 = new token("void", Color.Blue, new Font("Courier New", 10, FontStyle.Bold));
    token T9 = new token("(", Color.Orange, new Font("Courier New", 10, FontStyle.Bold));
    token T10 = new token(")", Color.Orange, new Font("Courier New", 10, FontStyle.Bold));
    token T11 = new token("{", Color.Orange, new Font("Courier New", 10, FontStyle.Bold));
    token T12 = new token("}", Color.Orange, new Font("Courier New", 10, FontStyle.Bold));
    token T13 = new token("==", Color.Red, new Font("Courier New", 10, FontStyle.Bold));
    token[] tokenArray = { T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13 };
 
    ExtRichText = new extendedRichText(tokenArray , 50);
    this.Controls.Add(ExtRichText);
}

Implementace - celý kód

Celý "necenzurovaný" a nerozstříhaný kód třídy vypadá následovně:

» Rozbalit/sbalit kód «

Nový komentář k článku "Syntax highlighting klíčových slov pomocí RichTextBoxu"

Podpis (smí obsahovat url ve tvaru "http://domeny")
Zde napište slovo "člověk" (malá písmena, smí být bez diakritiky)
Text příspěvku (Texy markup)

Komentáře čtenářů

hertpl: Undefined index: comments(template line: 478) in templates/articles.html on line 219