Syntax Highlighting – highlights syntactic text constructs using various colors, fonts, and styles. It is usually used to make it easier to read the source text of computer programs, to improve visual perception.

Wikipedia

At the request of my readers, which fortunately coincided with my intentions, I decided to remove the example.detail field, which stored text in RTF format. Instead, add a text field example.snippet, which will store the code snippet with comments. But since I already managed to put examples into the database, I had to make a button and a small script to transfer the text.

RTF -> TXT converter

procedure frmExampleView_Button1_OnClick(Sender: TObject; var Cancel: boolean); var tmpDataSet:TDataSet; begin SQLQuery('select * from example',tmpDataSet); while not tmpDataSet.EOF do begin frmExampleView.redExample.TextRTF := tmpDataSet.FieldByName('detail').asString; SQLExecute('UPDATE example SET snippet = '''+ escape_special_characters( frmExampleView.redExample.Text )+''' WHERE id = '+tmpDataSet.FieldByName('id').asString); tmpDataSet.Next; end; tmpDataSet.Free; frmExampleView.redExample.Clear; ShowMessage('Transfer complete!'); end;
Code language: Delphi (delphi)

The code uses the fact that the example view form already has a RichEdit component, which is used for conversion. If you need to create a conversion function, you will have to create a separate instance of the TdbRichEdit class.

The frmExampleView.Button1 button is temporary, I will remove it in the next version of the program.

Edit form

To edit the example, we will use the usual TdbMemo multi-line editing field, in which we will activate the display of a vertical scroll bar (ScrollBars = ssVertical property).

View form

The view form remains the same, using the RichEdit component, which has toolbars and rulers disabled (Properties Toolbar1.Show = False, Toolbar2.Show = False, Toolbar3.Show = False, Ruler = False).

I decided to implement code highlighting through the HTML format. It seemed to me that this is a simpler, more transparent and universal solution than being tied to the capabilities of TdbRichEdit or the RTF format.

Unfortunately, in MVDB, RichEdit does not implement a method for adding text in HTML format, this can only be done through loading a file and the LoadHTML() method, so another small script has been added to the collection of extensions:

procedure RichEdit_SetHTMLText(AEdit:TdbRichEdit; AText:string); // load HTML into editor var tmpFileName:string; tmpMemo: TdbMemo; begin tmpFileName := GetTempFileName; // get temporary file name tmpMemo := TdbMemo.Create(nil); // create a component for writing a text file in UTF-8 format tmpMemo.Text := AText; tmpMemo.SaveToFileUTF8(tmpFileName ); // save file AEdit.Clear; AEdit.LoadHTML(tmpFileName); // upload file as HTML DeleteFile(tmpFileName); // delete temporary file end;
Code language: Delphi (delphi)

The UserApp_LoadExample() data loading method has also changed: it uses the RichEdit_SetHTMLText() described above, as well as the function of converting text to HTML format with CodeHL_TextToHTML() syntax highlighting.

procedure UserApp_LoadExample; // loading an example from the database var tmpData:string; tmpTableName:string; tmpTableID:string; tmpText:string; begin frmExampleView.edtCaption.Text := ''; frmExampleView.redExample.Clear; if ActiveGrid <> nil then begin tmpTableName := Grid_GetTableName( ActiveGrid ); if tmpTableName <> '' then begin tmpTableID := IntToStr(ActiveGrid.dbItemID); try // get existing ID IDExample := SQLExecute('SELECT id_example FROM '+tmpTableName+' WHERE id = '+tmpTableID ); except // and if the ID does not exist, it will be -1 IDExample := -1; end; // load formatted text tmpText := SQLExecute('SELECT snippet FROM example WHERE id = '+IntToStr(IDExample)); RichEdit_SetHTMLText( frmExampleView.redExample, CodeHL_TextToHTML( tmpText )); // load title frmExampleView.edtCaption.Text := SQLExecute('SELECT caption FROM example WHERE id = '+IntToStr(IDExample)); end; end; end;
Code language: Delphi (delphi)

TXT -> HTML converter

In fact, the converter is a parser that looks through the source text character by character. When the end of a word (one of the delimiter characters) is detected, a check is made for the beginning of the linear mode, and then the word is searched in arrays of keywords and special text formatting actions are performed – adding HTML sequences.

Line Mode

Linear mode is used when detecting comments as well as string literals. Linear mode disables parsing of text into words until a special character (or multiple characters) is found to end linear mode.

Three kinds of comments are allowed in ObjectPascal and one way of defining string literals.

ObjectStart characterEnd chatacter
Sstring literal ‘ (single quote) ‘ (single quote)
Comment //end of line ($0A)
Comment { }
Comment (* *)

Keywords

There are two groups of keywords in ObjectPascal: language keywords and standard classes. Each group has its own color and selection style. In addition to keywords, numeric values are usually highlighted. I implemented all the described functionality in the CodeHL_CheckWord() procedure.

procedure CodeHL_CheckWord(var AWord:string ); // checking the word, adding tags var s:string; i: integer; j: integer begin s := LowerCase(Trim(AWord)); if (s = '') or (s = ',') then exit; // if the word is a number if ValidInt(s) or ValidFloat(s) then begin AWord := '<font color="#FF00FF"><b>'+AWord+'</b></font>'; exit; end; // if word is in list 1 for i:=0 to Length(CHL_KeyWord1) - 1 do begin j := CompareText(s,CHL_KeyWord1[i]); if j = 0 then begin AWord := '<font color="#005F00"><b>'+AWord+'</b></font>'; exit; end else if j = -1 then begin break end; end; // if word is in list 2 for i:=0 to Length(CHL_KeyWord2) - 1 do begin j := CompareText(s,CHL_KeyWord2[i]); if j = 0 then begin AWord := '<font color="#000000"><b>'+AWord+'</b></font>'; exit; end else if j = -1 then begin break end; end; end;
Code language: Delphi (delphi)

The main code highlighting function CodeHL_TextToHTML(), in addition to the actions described above, replaces the characters used for markup with special character sequences. And all text is wrapped in tags that preserve spaces and line breaks when displaying data.

function CodeHL_TextToHTML(AText: string):string; // adding highlighted HTML tags to text var i: integer; j: integer tmpWord:string; tmpchar:string; s1, s2: string; tmpIndex: integer; begin tmpIndex := -1; Result := ''; tmpWord := ''; for i := 1 to Length(AText) do begin tmpChar := AText[i]; // replacement to insert into HTML case tmpChar of '<': tmpChar:='&lt;'; '>': tmpChar:='&gt;'; '&': tmpChar:='&amp;'; chr(10): tmpChar:=''; end; // linear mode - this disables character-by-character analysis until a trigger sequence is detected if tmpIndex <> -1 then begin s1 := CHL_LM_End[tmpIndex]; // terminating sequence found? if copy(AText,i,Length(s1) ) = s1 then begin tmpWord := tmpWord + S1; // if the terminating sequence is more than one character, then we make an adjustment if Length(S1) > 1 then delete( AText, i+1, Length(s1) - 1 ); // if CHL_LM_Italic[tmpIndex] = '1' then begin s1 := '<i>'; s2 := '</i>'; end else begin s1 := ''; s2 := ''; end; Result := Result + '<font color="'+CHL_LM_Color[tmpIndex]+'">'+S1+ tmpWord +S2+ '</font>'; tmpIndex := -1; tmpWord := ''; end else tmpWord := tmpWord + tmpChar; continue; end; // end of word if AText[i] in [' ','{','}','/','\',')','(',';',':','"','''' ,'.',chr(13)] then begin // check for start of linear mode for j:=0 to Length( CHL_LM_Begin )-1 do begin if copy( AText, i, Length(CHL_LM_Begin[j]) ) = CHL_LM_Begin[j] then begin CodeHL_CheckWord(tmpWord); Result := Result + tmpWord; tmpIndex := j; continue; end; end; // check for words CodeHL_CheckWord(tmpWord); Result := Result + tmpWord; Result := Result + AText[i]; // delimiter character that was at the end of the word tmpWord := ''; end else tmpWord := tmpWord + tmpChar; end; Result := Result + tmpWord; Result := '<pre>'+Result+'</pre>'; end;
Code language: Delphi (delphi)

While the function looks rough in places, overall it does its job and has acceptable performance: the delay in displaying data is about half a second, including the creation of a temporary file for loading HTML.

Data

All the data necessary for work is stored in arrays, which are filled by the CodeHL_Init() procedure. This can later be used to extend functionality by adding different languages ​​for highlighting. Within the framework of the program described in this article, you will definitely need the SQL language, since some of the tasks relate specifically to the use of SQL queries and commands, and it would be nice to highlight the syntax of this language.

var CHL_KeyWord1 : array of string; // language keywords CHL_KeyWord2 : array of string; // standard types // line mode parameters CHL_LM_Begin: array of string; // start character CHL_LM_End: array of string; // end character CHL_LM_Color: array of string; // color as code CHL_LM_Italic: array of string; // sign of the inclination of the text procedure CodeHL_Init; // initialization // words must be sorted alphabetically begin // set data for linear mode CHL_LM_Begin := SplitString('//,{,(*,''',','); CHL_LM_End := SplitString(chr(13)+',},*),''',','); CHL_LM_Color := SplitString('#00009F,#00009F,#00009F,#FF0000',','); CHL_LM_Italic := SplitString('0,0,0,1',','); // keyword data CHL_KeyWord1 := SplitString('absolute,abstract,and,array,as,asm,assembler,automated,begin,case,'+ 'cdecl,class,const,constructor,destructor,dispid,dispinterface,div,do,downto,dynamic,'+ 'else,end,except,export,exports,external,far,file,finalization,finally,for,forward,'+ 'function,goto,if,implementation,in,inherited,initialization,inline,interface,is,'+ 'label,library,message,mod,near,nil,not,object,of,or,out,override,packed,pascal,'+ 'private,procedure,program,property,protected,public,published,raise,record,register,'+ 'repeat,resourcestring,safecall,set,shl,shr,stdcall,then,threadvar,to,try,type,unit,'+ 'until,uses,var,virtual,while,with,xor',','); CHL_KeyWord2 := SplitString('ansichar,ansistring,array,boolean,byte,bytebool,cardinal,'+ 'char,currency,double,false,fixedint,fixeduint,int64,integer,longbool,longint,'+ 'longword,nativeint,nativeuint,pansichar,pansistring,pboolean,pbyte,pcardinal,'+ 'pchar,pcurrency,pdouble,pfixedint,pfixeduint,pint64,pinteger,plongbool,plongint,'+ 'plongword,pnativeint,pnativeuint,pointer,ppointer,prawbytestring,pshortint,'+ 'pshortstring,psingle,psmallint,pstring,puint64,punicodestring,pvariant,'+ 'pwidechar,pwidestring,pword,pwordbool,rawbytestring,real,record,shortint,'+ 'shortstring,single,smallint,string,tdoublerec,text,true,tsinglerec,type,'+ 'uint64,unicodestring,variant,widechar,widestring,word,wordbool',',' ); end;
Code language: PHP (php)

The list of standard types includes all types that are available in Delphi, but there are far fewer in My Visual Database. If we reduce this list, leaving only the necessary values, then the performance of the backlight module will be even higher.

What’s next?

Attentive reader has noticed that on the picture with the editing form there is a “Format code” button, the action of which deserves a separate description. This button allows you to align indents, wrap lines, and perform other actions that will make the source code look better. But I will talk about this in the next article.

Leave a Reply

Your email address will not be published. Required fields are marked *