{----------------------------------------------------------------------------- The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.mozilla.org/MPL/MPL-1.1.html Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either expressed or implied. See the License for the specific language governing rights and limitations under the License. The Original Code is: JvHLEditor.PAS, released on 2002-07-04. The Initial Developers of the Original Code are: Andrei Prygounkov Copyright (c) 1999, 2002 Andrei Prygounkov All Rights Reserved. Contributor(s): Eswar Prakash R [eswar dott prakash att gmail.com] You may retrieve the latest version of this file at the Project JEDI's JVCL home page, located at http://jvcl.sourceforge.net component : TJvHLEditor description : JvEditor with built-in highlighting for: pascal, cbuilder, sql, python, jscript, vbscript, perl, ini, html, not quite c Known Issues: (rom) source cleaning incomplete (rom) GetAttr should be broken up further -----------------------------------------------------------------------------} // $Id: JvHLEditor.pas 11043 2006-11-26 07:21:48Z marquardt $ { history (JVCL Library versions) : 1.03: - first release; 1.11: - improvements in custom reserved words support; - comments works better in custom reserved words; 1.17: - python highlighting by Rafal Smotrzyk - rsmotrzyk att mikroplan dott com dott pl; 1.17.2: - jscript, vbscript highlighting by Rafal Smotrzyk - rsmotrzyk att mikroplan dott com dott pl; 1.17.6: - html highlighting; 1.12.2: - fixed bug with pressing End-key if CursorBeoyondEOF enabled (greetings to Andre N Belokon) 1.23: - fixed another bug in comment checking (range check error) (greetings to Willo vd Merwe) 1.23.1: - first version of perl highlighter; 1.41: - fixed another bug in comment checking; 1.51.3 (JVCL Library 1.51 with Update 3): - fixed bug: exception on comments in "c++, java, sql" - mode; 1.51.4 (JVCL Library 1.51 with Update 4): - ini-file highlighter; - fixed bug: custom reserved words not working; 1.61: - new: in html-highlighter unknown (not html) tag highlighted with "statement" color. This allows to use html-highlighter to display xml-files. 2.10.2: (changes by Andreas Hausladen) - C/C++ line continuation symbol '\' extends the highlight colors to the next line (LongToken=True) - "Not Quite C" highlighter (C similar, for programming LEGO MindStorm(R) robots) - fixed bug: SetBlockColor raise an exception (AV) if iEnd becomes greater than MAX_X - in TRAHLEditor.GetAttr all IsXxxKeyWord get a CONST string, so no compiler magic LStrAddRef is needed. - some speed optimations - new property DelphiColors: Boolean - renamed all "Identifer" to "Identifier". published property "Identifer" still exists but uses "FIdentifier" - added some new DelphiKeyWord - fixed bug: RescanLong() may exceed FLongDesc[] dimension 2.10.3: (changes by Andreas Hausladen - faster RescanLong - faster KeyWord search for drawing 3.0: - added TJvEditorHighlighter component } unit JvHLEditor; {$I jvcl.inc} interface uses Windows, {$IFDEF UNITVERSIONING} JclUnitVersioning, {$ENDIF UNITVERSIONING} SysUtils, Classes, Graphics, JvEditor, JvEditorCommon, JvHLParser; type TJvHLEditor = class; TOnReservedWord = procedure(Sender: TObject; Token: string; var Reserved: Boolean) of object; TJvEditorHighlighter = class(TComponent) protected procedure GetAttr(Editor: TJvHLEditor; Lines: TStrings; Line, ColBeg, ColEnd: Integer; LongToken: TLongTokenType; var LineAttrs: TLineAttrs); virtual; abstract; procedure ScanLongTokens(Editor: TJvHLEditor; Lines: TStrings; Line: Integer; var FLong: TLongTokenType); virtual; abstract; function GetRescanLongKeys(Editor: TJvHLEditor; Action: TModifiedAction; ACaretX, ACaretY: Integer; const Text: string): Boolean; virtual; abstract; end; TJvHLEditor = class(TJvEditor, IJvHLEditor) private Parser: TJvIParser; FHighlighter: TJvHighlighter; FColors: TJvColors; FLine: string; FLineNum: Integer; FLong: TLongTokenType; FLongTokens: Boolean; FLongDesc: array {[0..Max_Line]} of TLongTokenType; FSyntaxHighlighting: Boolean; FSyntaxHighlighter: TJvEditorHighlighter; FOnReservedWord: TOnReservedWord; // Coco/R ProductionsLine: Integer; function RescanLong(iLine: Integer): Boolean; procedure CheckInLong; function FindLongEnd: Integer; procedure SetHighlighter(const Value: TJvHighlighter); function GetDelphiColors: Boolean; procedure SetDelphiColors(Value: Boolean); function GetColors: TJvColors; procedure SetColors(const Value: TJvColors); function GetSyntaxHighlighting: Boolean; procedure SetSyntaxHighlighting(Value: Boolean); function GetHighlighter: TJvHighlighter; protected procedure Loaded; override; procedure Notification(AComponent: TComponent; Operation: TOperation); override; procedure GetAttr(Line, ColBeg, ColEnd: Integer); override; procedure TextModified(ACaretX, ACaretY: Integer; Action: TModifiedAction; const Text: string); override; function GetReservedWord(const Token: string; var Reserved: Boolean): Boolean; virtual; function UserReservedWords: Boolean; virtual; procedure SetSyntaxHighlighter(const Value: TJvEditorHighlighter); public constructor Create(AOwner: TComponent); override; destructor Destroy; override; procedure Assign(Source: TPersistent); override; published property Highlighter: TJvHighlighter read GetHighlighter write SetHighlighter default hlPascal; property Colors: TJvColors read GetColors write SetColors; property DelphiColors: Boolean read GetDelphiColors write SetDelphiColors stored False; property LongTokens: Boolean read FLongTokens write FLongTokens default True; property OnReservedWord: TOnReservedWord read FOnReservedWord write FOnReservedWord; property SyntaxHighlighting: Boolean read GetSyntaxHighlighting write SetSyntaxHighlighting stored False; property SyntaxHighlighter: TJvEditorHighlighter read FSyntaxHighlighter write SetSyntaxHighlighter; end; {$IFDEF UNITVERSIONING} const UnitVersioning: TUnitVersionInfo = ( RCSfile: '$URL: https://jvcl.svn.sourceforge.net/svnroot/jvcl/tags/JVCL3_32/run/JvHLEditor.pas $'; Revision: '$Revision: 11043 $'; Date: '$Date: 2006-11-26 08:21:48 +0100 (dim., 26 nov. 2006) $'; LogPath: 'JVCL\run' ); {$ENDIF UNITVERSIONING} implementation uses Math, JvConsts, JvJCLUtils; function LastNonSpaceChar(const S: string): Char; var I: Integer; begin Result := #0; I := Length(S); while (I > 0) and (S[I] = ' ') do Dec(I); if I > 0 then Result := S[I]; end; function GetTrimChar(const S: string; Index: Integer): Char; var LS, L: Integer; begin LS := Length(S); if LS <> 0 then begin L := 1; while (L <= LS) and (S[L] = ' ') do Inc(L); if L <= LS then Result := S[L - 1 + Index] else Result := S[Index]; end else Result := #0; end; function HasStringOpenEnd(Lines: TStrings; iLine: Integer): Boolean; { find C/C++ "line breaker" '\' } var I: Integer; IsOpen: Boolean; P, F: PChar; S: string; begin Result := False; if (iLine < 0) or (iLine >= Lines.Count) then Exit; I := iLine - 1; IsOpen := False; if (I >= 0) and (LastNonSpaceChar(Lines[I]) = '\') then // check prior lines IsOpen := HasStringOpenEnd(Lines, I); S := Lines[iLine]; F := PChar(S); P := F; repeat P := StrScan(P, Char('"')); if P <> nil then begin if (P = F) or (P[-1] <> '\') then IsOpen := not IsOpen else begin // count the backslashes I := 1; while (P-1-I > F) and (P[-1-I] = '\') do Inc(I); if I mod 2 = 0 then IsOpen := not IsOpen; end; Inc(P); end; until P = nil; Result := IsOpen; end; function StrScan(P: PChar; Ch: Char): PChar; begin Result := P; while True do begin while Result[0] in LeadBytes do Inc(Result); // mbcs if Result[0] = Ch then Exit else if Result[0] = #0 then begin Result := nil; Exit; end; Inc(Result); end; end; //=== { TJvHLEditor } ======================================================== constructor TJvHLEditor.Create(AOwner: TComponent); begin inherited Create(AOwner); Parser := TJvIParser.Create; Parser.ReturnComments := True; FHighlighter := hlPascal; FColors := TJvColors.Create; FLongTokens := True; FSyntaxHighlighting := True; ProductionsLine := High(Integer); end; destructor TJvHLEditor.Destroy; begin Parser.Free; FColors.Free; inherited Destroy; end; procedure TJvHLEditor.Notification(AComponent: TComponent; Operation: TOperation); begin if (Operation = opRemove) and (AComponent = FSyntaxHighlighter) then SyntaxHighlighter := nil; inherited Notification(AComponent, Operation); end; procedure TJvHLEditor.Loaded; begin inherited Loaded; RescanLong(0); end; procedure TJvHLEditor.SetHighlighter(const Value: TJvHighlighter); begin if FHighlighter <> Value then begin FHighlighter := Value; case FHighlighter of hlPascal: Parser.Style := psPascal; hlCBuilder, hlJava, hlJScript, hlNQC, hlCSharp: Parser.Style := psCpp; hlPython: Parser.Style := psPython; hlVB: Parser.Style := psVB; hlHtml: Parser.Style := psHtml; hlPerl: Parser.Style := psPerl; hlIni: Parser.Style := psPascal; hlCocoR: Parser.Style := psCocoR; hlPhp: Parser.Style := psPhp; hlSql: Parser.Style := psSql; end; RescanLong(0); Invalidate; end; end; procedure TJvHLEditor.GetAttr(Line, ColBeg, ColEnd: Integer); const Symbols = [',', ':', ';', '.', '[', ']', '(', ')', '=', '+', '-', '/', '<', '>', '%', '*', '~', '''', '\', '^', '@', '{', '}', '#', '|', '&']; const DelphiKeyWords = ' constructor destructor string record procedure with of' + ' repeat until try finally except for to downto case' + ' type interface implementation initialization finalization' + ' default private public protected published automated property' + ' program read write override object nil raise' + ' on set xor shr shl begin end args if then else' + ' endif goto while do var or and not mod div unit' + ' function uses external const class inherited' + ' register stdcall cdecl safecall pascal is as package program' + ' external overload platform deprecated implements export contains' + ' requires resourcestring message dispid assembler asm abstract absolute' + ' dispinterface file threadvar library' + // TurboPascal ' interrupt inline near far' + // Delphi 8 ' operator strict final unsafe sealed static '; BuilderKeyWords = ' __asm _asm asm auto __automated break bool case catch __cdecl' + ' _cdecl cdecl char class __classid __closure const const_cast' + ' continue __declspec default delete __dispid do double dynamic_cast' + ' else enum __except explicit _export __export extern false __fastcall' + ' _fastcall __finally float for friend goto if __import _import inline' + ' int __int8 __int16 __int32 __int64 long mutable namespace new operator' + ' __pascal _pascal pascal private protected __property public __published' + ' register reinterpret_cast return __rtti short signed sizeof static static_cast' + ' __stdcall _stdcall struct switch template this __thread throw true __try' + ' try typedef typename typeid union using unsigned virtual void volatile' + ' wchar_t while '; NQCKeyWords = {Not Quite C - a C similar language for programming LEGO MindStorm(R) robots } ' __event_src __type acquire break __sensor abs asm case catch const' + ' continue default do else false for if inline' + ' int monitor repeat return signed start stop sub switch task true' + ' until void while '; // REPLACE keyword added to the list SQLKeyWords = ' active as add asc after ascending all at alter auto' + ' and autoddl any avg based between basename blob' + ' base_name blobedit before buffer begin by cache compiletime' + ' cast computed char close character conditional character_length connect' + ' char_length constraint check containing check_point_len continue check_point_length count' + ' collate create collation cstring column current commit cursor' + ' committed database descending date describe db_key descriptor debug disconnect' + ' dec display decimal distinct declare do default domain' + ' delete double desc drop echo exception edit execute' + ' else exists end exit entry_point extern escape external' + ' event extract fetch foreign file found filter from' + ' float full for function gdscode grant generator group' + ' gen_id commit_group_wait global group_commit_wait_time goto' + ' having help if input_type immediate insert in int' + ' inactive integer index into indicator is init isolation' + ' inner isql input join key' + ' lc_messages like lc_type logfile left log_buffer_size length log_buf_size' + ' lev long level manual merge max message' + ' maximum min maximum_segment minimum max_segment module_name names not' + ' national null natural numeric nchar num_log_bufs no num_log_buffers' + ' noauto octet_length or of order on outer only output' + ' open output_type option overflow page post_event pagelength precision' + ' pages prepare page_size procedure parameter protected password primary' + ' plan privileges position public quit' + ' raw_partitions retain rdb db_key return read replace returning_values real returns' + ' record_version revoke references right release rollback reserv runtime replace' + ' reserving schema sql segment sqlcode select sqlerror set sqlwarning' + ' shadow stability shared starting shell starts show statement' + ' singular static size statistics smallint sub_type snapshot sum' + ' some suspend sort table translate terminator translation then trigger to trim' + ' transaction uncommitted upper union user unique using update' + ' value varying values version varchar view variable' + ' wait while when with whenever work where write' + ' term new old '; PythonKeyWords = ' and del for is raise' + ' assert elif from lambda return' + ' break else global not try' + ' class except if or while' + ' continue exec import pass' + ' def finally in print '; JavaKeyWords = ' abstract delegate if boolean do implements break double import' + ' byte else instanceof case extends int catch false interface' + ' char final long class finally multicast continue float' + ' default for native short transient new static true' + ' null super try package switch void private synchronized volatile' + ' protected this while public throw return throws '; JScriptKeyWords = //'@cc_on @if @set' ' break delete function return typeof case do if switch var' + ' catch else in this void continue false instanceof throw while' + ' debugger finally new true with default for null try' + ' abstract double goto native static boolean enum implements package super' + ' byte export import private synchronized char extends int protected throws' + ' class final interface public transient const float long short volatile' ; VBKeyWords = ' as and base binary byref byval call case class compare const date debug declare deftype dim do each else elseif ' + ' empty end endif enum eqv erase error event execute exit explicit false for friend function get' + ' global gosub goto if imp implements input is kill len let line load lock loop lset me mid mod name new next not nothing null on open option optional' + ' or paramarray preserve print private property public raiseevent randomize redim rem' + ' resume return seek select set static step' + ' string sub then time to true unlock until wend while with withevents xor '; VBStatements = ' access alias any beep ccur cdbl chdir chdrive choose' + ' chr cint clear clng clone close cls command compare' + ' cos csng cstr curdir currency cvar cvdate ' + ' defcur defdbl defint deflng defsng defstr deftype defvar delete deletesetting' + ' doevents double dynaset edit environ eof erl err exp fix format ' + ' hex int integer isdate isempty isnull isnumeric lbound lcase' + ' lib like loc local lof long mkdir oct output pset put' + ' random read refresh reset restore rmdir rnd rset savesetting ' + ' sendkeys shared single stop system text type typeof ubound unload ' + ' using variant vartype write'; HTMLTags = ' doctype a address applet area b base basefont bgsound big blink ' + ' blockquote body br caption center cite code col colgroup comment ' + ' dfn dir li div dl dt dd em embed font form frame frameset h align ' + ' h1 h2 h3 h4 h5 h6 head hr html i iframe img input isindex kbd link ' + ' listing map marquee menu meta multicol nextid nobr noframes noscript ' + ' object ol option p plaintext pre s samp script select small sound ' + ' spacer span strike strong style sub sup table tbody td textarea tfoot' + ' th thead title tr tt u ul var wbr xmp '; HTMLSpecChars = ' Aacute aacute acirc Acirc acute AElig aelig agrave Agrave alefsym ' + ' alpha Alpha AMP amp and ang Aring aring asymp atilde Atilde Auml ' + ' auml bdquo beta Beta brvbar bull cap Ccedil ccedil cedil cent chi ' + ' Chi circ clubs cong copy COPY crarr cup curren dagger Dagger dArr ' + ' darr deg Delta delta diams divide eacute Eacute ecirc Ecirc Egrave ' + ' egrave empty emsp ensp Epsilon epsilon equiv eta Eta ETH eth Euml ' + ' euml euro exist fnof forall frac12 frac14 frac34 frasl Gamma gamma ' + ' ge gt GT harr hArr hearts hellip iacute Iacute Icirc icirc iexcl Igrave ' + ' igrave image infin int Iota iota iquest isin Iuml iuml kappa Kappa Lambda ' + ' lambda lang laquo larr lArr lceil ldquo le lfloor lowast loz lrm lsaquo ' + ' lsquo lt LT macr mdash micro middot minus mu Mu nabla nbsp ndash ne ' + ' ni not notin nsub Ntilde ntilde Nu nu oacute Oacute ocirc Ocirc oelig ' + ' OElig ograve Ograve oline Omega omega omicron Omicron oplus or ordf ' + ' ordm Oslash oslash Otilde otilde otimes ouml Ouml para part permil ' + ' perp phi Phi Pi pi piv plusmn pound Prime prime prod prop psi Psi quot ' + ' QUOT radic rang raquo rArr rarr rceil rdquo real REG reg rfloor Rho ' + ' rho rlm rsaquo rsquo sbquo scaron Scaron sdot sect shy Sigma sigma ' + ' sigmaf sim spades sub sube sum sup sup1 sup2 sup3 supe szlig Tau ' + ' tau there4 Theta theta thetasym thinsp THORN thorn tilde times trade ' + ' Uacute uacute uArr uarr ucirc Ucirc ugrave Ugrave uml upsih upsilon ' + ' Upsilon uuml Uuml weierp xi Xi Yacute yacute yen yuml Yuml zeta Zeta ' + ' zwj zwnj '; PerlKeyWords = ' sub if else unless foreach next local ' + ' return defined until while do elsif eq '; PerlStatements = ' stat die open print push close defined chdir last read chop ' + ' keys sort bind unlink select length '; CocoKeyWords = DelphiKeyWords + ' compiler productions delphi end_delphi ignore case characters ' + ' tokens create destroy errors comments from nested chr any ' + ' description '; CSharpKeyWords = ' abstract as base bool break byte case catch char checked class ' + ' const continue decimal default delegate do double else enum event ' + ' explicit extern false finally fixed float for foreach goto if ' + ' implicit in int interface internal is lock long namespace new null ' + ' object operator out override params private protected public readonly ' + ' ref return sbyte sealed short sizeof stackalloc static string struct ' + ' switch this throw true try typeof uint ulong unchecked unsafe ushort ' + ' using virtual void volatile while '; function PosI(const S1, S2: string): Boolean; var F, P: PChar; Len: Integer; begin Len := Length(S1); Result := True; P := PChar(S2); while P[0] <> #0 do begin while P[0] = ' ' do Inc(P); F := P; while not (P[0] <= #32) do Inc(P); if (P - F) = Len then if StrLIComp(PChar(S1), F, Len) = 0 then Exit; end; Result := False; end; function PosNI(const S1, S2: string): Boolean; var F, P: PChar; Len: Integer; begin Len := Length(S1); Result := True; P := PChar(S2); while P[0] <> #0 do begin while P[0] = ' ' do Inc(P); F := P; while not (P[0] <= #32) do Inc(P); if (P - F) = Len then if StrLComp(PChar(S1), F, Len) = 0 then Exit; end; Result := False; end; function IsDelphiKeyWord(const St: string): Boolean; begin Result := PosI(St, DelphiKeyWords); end; function IsBuilderKeyWord(const St: string): Boolean; begin Result := PosNI(St, BuilderKeyWords); end; function IsNQCKeyWord(const St: string): Boolean; begin Result := PosNI(St, NQCKeyWords); end; function IsJavaKeyWord(const St: string): Boolean; begin Result := PosNI(St, JavaKeyWords); end; function IsJScriptKeyWord(const St: string): Boolean; begin Result := PosNI(St, JScriptKeyWords); end; function IsVBKeyWord(const St: string): Boolean; begin Result := PosI(St, VBKeyWords); end; function IsVBStatement(const St: string): Boolean; begin Result := PosI(St, VBStatements); end; function IsSQLKeyWord(const St: string): Boolean; begin Result := PosI(St, SQLKeyWords); end; function IsPythonKeyWord(const St: string): Boolean; begin Result := PosNI(St, PythonKeyWords); end; function IsHtmlTag(const St: string): Boolean; begin Result := PosI(St, HTMLTags); end; function IsHtmlSpecChar(const St: string): Boolean; begin Result := PosI(St, HTMLSpecChars); end; function IsPerlKeyWord(const St: string): Boolean; begin Result := PosNI(St, PerlKeyWords); end; function IsPerlStatement(const St: string): Boolean; begin Result := PosNI(St, PerlStatements); end; function IsCocoKeyWord(const St: string): Boolean; begin Result := PosI(St, CocoKeyWords); end; function IsPhpKeyWord(const St: string): Boolean; begin Result := PosNI(St, PerlKeyWords); end; function IsCSharpKeyWord(const St: string): Boolean; begin Result := PosNI(St, CSharpKeyWords); end; function IsComment(const St: string): Boolean; var LS: Integer; begin LS := Length(St); case Highlighter of hlPascal: Result := ((LS > 0) and (St[1] = '{')) or ((LS > 1) and (((St[1] = '(') and (St[2] = '*')) or ((St[1] = '/') and (St[2] = '/')))); hlCBuilder, hlJava, hlJScript, hlPhp, hlNQC, hlCSharp: Result := (LS > 1) and (St[1] = '/') and ((St[2] = '*') or (St[2] = '/')); // Support for SQL comment line beginning with -- hlSql: Result := (LS > 1) and (((St[1] = '-') and (St[2] = '-')) or (St[1] = '/') and ((St[2] = '*'))); // support for /* */ // HTML multi line comment support hlHtml: Result := (LS > 3) and (St[1] = '<') and (St[2] = '!') and (St[3] = '-') and (St[4] = '-'); hlVB: Result := (LS > 0) and (St[1] = ''''); hlPython, hlPerl: Result := (LS > 0) and (St[1] = '#'); hlIni: Result := (LS > 0) and ((St[1] = '#') or (St[1] = ';')); hlCocoR: Result := (LS > 1) and (((St[1] = '/') and (St[2] = '/')) or ((St[1] = '(') and (St[2] = '*')) or ((St[1] = '/') and (St[2] = '*')) ); else Result := False; end; end; function IsPreproc(const St: string): Boolean; var LS: Integer; begin LS := Length(St); case Highlighter of hlPascal: Result := ((LS > 0) and ((St[1] = '{') and (St[2] = '$'))) or ((LS > 1) and (((St[1] = '(') and (St[2] = '*') and (St[3] = '$')))); {hlCBuilder, hlSql, hlJava, hlJscript, hlPhp, hlNQC: hlVB: hlPython, hlPerl: hlIni: hlCocoR:} else Result := False; end; end; function IsStringConstant(const St: string): Boolean; var LS: Integer; begin LS := Length(St); case FHighlighter of hlPascal, hlCBuilder, hlSql, hlPython, hlJava, hlJScript, hlPerl, hlCocoR, hlPhp, hlNQC, hlCSharp: Result := (LS > 0) and ((St[1] = '''') or (St[1] = '"')); hlVB: Result := (LS > 0) and (St[1] = '"'); hlHtml: Result := False; else Result := False; { unknown Highlighter ? } end; end; procedure SetBlockColor(iBeg, iEnd: Integer; Color: TJvSymbolColor); var I: Integer; begin if iEnd > Max_X then iEnd := Max_X; for I := iBeg to iEnd do with LineAttrs[I] do begin FC := Color.ForeColor; BC := Color.BackColor; Style := Color.Style; Border := clNone; end; end; procedure SetColor(Color: TJvSymbolColor); begin SetBlockColor(Parser.PosBeg[0] + 1, Parser.PosEnd[0], Color); end; function NextSymbol: string; var I: Integer; begin I := 0; while (Parser.pcPos[I] <> #0) and (Parser.pcPos[I] in [' ', Tab, Cr, Lf]) do Inc(I); Result := Parser.pcPos[I]; end; procedure TestHtmlSpecChars(const Token: string); var I, J, iBeg, iEnd: Integer; S1: string; F1: Integer; begin I := 1; F1 := Parser.PosBeg[0]; while I <= Length(Token) do begin if Token[I] = '&' then begin iBeg := I; iEnd := iBeg; Inc(I); while I <= Length(Token) do begin if Token[I] = ';' then begin iEnd := I; Break; end; Inc(I); end; if iEnd > iBeg + 1 then begin S1 := Copy(Token, iBeg + 1, iEnd - iBeg - 1); if IsHtmlSpecChar(S1) then for J := iBeg to iEnd do with LineAttrs[F1 + J] do begin FC := Colors.Preproc.ForeColor; BC := Colors.Preproc.BackColor; Style := Colors.Preproc.Style; Border := clNone; end; end; end; Inc(I); end; end; procedure SetIniColors(const S: string); var EquPos: Integer; LS: Integer; begin LS := Length(S); if (LS > 0) and (S[1] = '[') and (S[LS] = ']') then SetBlockColor(0, LS, Colors.Declaration) else begin EquPos := Pos('=', S); if EquPos > 0 then begin SetBlockColor(0, EquPos, Colors.Identifier); SetBlockColor(EquPos, EquPos, Colors.Symbol); SetBlockColor(EquPos + 1, LS, Colors.Strings); end; end; end; // for Coco/R procedure HighlightGrammarName(const S: string); var P: Integer; begin P := Pos('-->Grammar<--', S); if P > 0 then SetBlockColor(P, P + Length('-->Grammar<--') - 1, Colors.Preproc); end; // (rom) const, var, local function sequence not cleaned up yet var F: Boolean; C: TJvSymbolColor; Reserved: Boolean; PrevToken: string; PrevToken2: string; NextToken: string; Ch: Char; InTag: Boolean; N: Integer; var S: string; LS: Integer; Token: string; I: Integer; begin if not FSyntaxHighlighting then Exit; S := Lines[Line]; if (FHighlighter = hlNone) and not UserReservedWords then C := Colors.PlainText else begin FLine := S; FLineNum := Line; CheckInLong; if (FHighlighter = hlSyntaxHighlighter) and (FSyntaxHighlighter <> nil) then begin // user defined syntax highlighting FSyntaxHighlighter.GetAttr(Self, Lines, Line, ColBeg, ColEnd, FLong, LineAttrs); Exit; end; Parser.pcProgram := PChar(S); Parser.pcPos := Parser.pcProgram; LS := Length(S); Ch := GetTrimChar(S, 1); if (Highlighter in [hlCBuilder, hlNQC]) and (LS > 0) and (((Ch = '#') and (FLong = 0)) or (FLong = lgPreproc)) then C := Colors.Preproc else if ((FHighlighter in [hlPython, hlPerl]) and (LS > 0) and (Ch = '#') and (FLong = 0)) or ((Highlighter = hlIni) and (LS > 0) and ((Ch = '#') or (Ch = ';'))) then C := Colors.Comment else C := Colors.PlainText; // (rom) reenabled second part of if to handle two line DOCTYPE if (FLong <> 0) and (FHighlighter <> hlHtml) then begin Parser.pcPos := Parser.pcProgram + FindLongEnd + 1; case Highlighter of hlCBuilder, hlPython, hlPerl, hlNQC, hlCSharp, hlHtml: case FLong of lgString: C := Colors.Strings; lgComment1, lgComment2: C := Colors.Comment; lgPreproc: C := Colors.Preproc; end; hlPascal: case FLong of lgComment1, lgComment2: C := Colors.Comment; lgPreproc1, lgPreproc2: C := Colors.Preproc; end; else C := Colors.Comment; end; end; end; LineAttrs[1].FC := C.ForeColor; LineAttrs[1].Style := C.Style; LineAttrs[1].BC := C.BackColor; LineAttrs[1].Border := clNone; N := Min(Max_X, Length(S)); for I := 2 to N do Move(LineAttrs[1], LineAttrs[I], SizeOf(LineAttrs[1])); if Length(S) < Max_X then begin LineAttrs[N + 1].FC := Font.Color; LineAttrs[N + 1].Style := Font.Style; LineAttrs[N + 1].BC := Color; LineAttrs[N + 1].Border := clNone; for I := N + 1 + 1 to Max_X do Move(LineAttrs[N + 1], LineAttrs[I], SizeOf(LineAttrs[1])); end; if (FHighlighter = hlNone) and not UserReservedWords then Exit; if (Length(S) > 0) then begin Ch := GetTrimChar(S, 1); if ((Ch = '#') and (FHighlighter in [hlCBuilder, hlPython, hlPerl, hlNQC])) or (((Ch = '#') or (Ch = ';')) and (FHighlighter = hlIni)) then Exit; end; if FHighlighter = hlIni then SetIniColors(S) else try InTag := FLong = lgTag; PrevToken := ''; PrevToken2 := ''; Token := Parser.Token; while Token <> '' do begin F := True; if GetReservedWord(Token, Reserved) then begin if Reserved then SetColor(Colors.Reserved) else F := False; end else case FHighlighter of hlPascal: if IsDelphiKeyWord(Token) then SetColor(Colors.Reserved) else F := False; hlCBuilder: if IsBuilderKeyWord(Token) then SetColor(Colors.Reserved) else F := False; hlNQC: if IsNQCKeyWord(Token) then SetColor(Colors.Reserved) else F := False; hlSql: if IsSQLKeyWord(Token) then SetColor(Colors.Reserved) else F := False; hlPython: if IsPythonKeyWord(Token) then SetColor(Colors.Reserved) else if Token = 'None' then SetColor(Colors.Number) else if (PrevToken = 'def') or (PrevToken = 'class') then SetColor(Colors.Declaration) else if (NextSymbol = '(') and IsIdentifier(Token) then SetColor(Colors.FunctionCall) else F := False; hlJava: if IsJavaKeyWord(Token) then SetColor(Colors.Reserved) else if PrevToken = 'function' then SetColor(Colors.Declaration) else F := False; hlJScript: if IsJScriptKeyWord(Token) then SetColor(Colors.Reserved) else if PrevToken = 'function' then SetColor(Colors.Declaration) else F := False; hlVB: if IsVBKeyWord(Token) then SetColor(Colors.Reserved) else if IsVBStatement(Token) then SetColor(Colors.Statement) else if SameText(PrevToken, 'function') or SameText(PrevToken, 'sub') or SameText(PrevToken, 'class') then SetColor(Colors.Declaration) else F := False; hlHtml: if not InTag then begin { Check for the comment starting with ' then begin InTag := False; SetColor(Colors.Reserved); F := False; end else if Token = '>' then begin InTag := False; SetColor(Colors.Reserved) end else if (Token = '/') and (PrevToken = '<') then SetColor(Colors.Reserved) else if (NextSymbol = '=') and IsIdentifier(Token) then SetColor(Colors.Identifier) else if PrevToken = '=' then SetColor(Colors.Strings) else if IsHtmlTag(Token) then SetColor(Colors.Reserved) else if (PrevToken = '<') or ((PrevToken = '/') and (PrevToken2 = '<')) then SetColor(Colors.Statement) else F := False; end; hlPerl: if IsPerlKeyWord(Token) then SetColor(Colors.Reserved) else if IsPerlStatement(Token) then SetColor(Colors.Statement) else if (Token[1] in ['$', '@', '%', '&']) then SetColor(Colors.FunctionCall) else F := False; hlCocoR: if IsCocoKeyWord(Token) then SetColor(Colors.Reserved) else if (Parser.PosBeg[0] = 0) and (Line > ProductionsLine) and IsIdentifier(Token) then begin NextToken := Parser.Token; Parser.RollBack(1); SetColor(Colors.Declaration) end else F := False; hlPhp: if IsPhpKeyWord(Token) then SetColor(Colors.Reserved) else F := False; hlCSharp: if IsCSharpKeyWord(Token) then SetColor(Colors.Reserved) else F := False; else F := False; end; if F then {Ok} else if IsPreproc(Token) then SetColor(Colors.Preproc) else if IsComment(Token) then SetColor(Colors.Comment) else if IsStringConstant(Token) then SetColor(Colors.Strings) else if (Length(Token) = 1) and (Token[1] in Symbols) then SetColor(Colors.Symbol) else if IsIntConstant(Token) or IsRealConstant(Token) then SetColor(Colors.Number) else if (FHighlighter in [hlCBuilder, hlJava, hlJScript, hlPython, hlPhp, hlNQC, hlCSharp]) and (PrevToken = '0') and ((Token[1] = 'x') or (Token[1] = 'X')) then SetColor(Colors.Number) else if FHighlighter = hlHtml then SetColor(Colors.PlainText) else SetColor(Colors.Identifier); if FHighlighter = hlHtml then { found special chars starting with '&' and ending with ';' } TestHtmlSpecChars(Token); PrevToken2 := PrevToken; PrevToken := Token; Token := Parser.Token; end; if Highlighter = hlCocoR then HighlightGrammarName(S); except end; end; procedure TJvHLEditor.CheckInLong; begin if not FLongTokens then begin FLong := lgNone; Exit; end; if FLineNum < Length(FLongDesc) then begin FLong := FLongDesc[FLineNum]; if FLong = lgUndefined then begin RescanLong(FLineNum); // scan the line FLong := FLongDesc[FLineNum]; end; end else RescanLong(-1); end; function TJvHLEditor.RescanLong(iLine: Integer): Boolean; const MaxScanLinesAtOnce = 5000; var P, F: PChar; MaxLine, MaxScanLine: Integer; S: string; I, i1, L1: Integer; begin FLong := lgNone; Result := False; // no Invalidate if (not FSyntaxHighlighting) or (not FLongTokens or (FHighlighter in [hlNone, hlIni])) or (Lines.Count = 0) then Exit; if Lines.Count >= Length(FLongDesc) then SetLength(FLongDesc, (Lines.Count div (64*1024) + 1) * (64*1024)); ProductionsLine := High(Integer); MaxLine := Lines.Count - 1; if MaxLine > High(FLongDesc) then MaxLine := High(FLongDesc); if iLine > MaxLine then Exit; MaxScanLine := MaxLine; FLong := lgNone; if iLine < 0 then begin FillChar(FLongDesc[0], SizeOf(FLongDesc[0]) * (1 + MaxLine), lgUndefined); FLongDesc[0] := lgNone; iLine := 0; end else begin FLong := FLongDesc[iLine]; if FLong = lgUndefined then begin if (iLine > 0) and (FLongDesc[iLine - 1] = lgUndefined) then begin iLine := 0; // scan all FLong := lgNone; end else begin Dec(iLine); FLong := FLongDesc[iLine]; MaxScanLine := Min(iLine + MaxScanLinesAtOnce, MaxLine); end; end else MaxScanLine := Min(iLine + MaxScanLinesAtOnce, MaxLine); end; while iLine < MaxScanLine do begin if (FHighlighter = hlSyntaxHighlighter) and (FSyntaxHighlighter <> nil) then FSyntaxHighlighter.ScanLongTokens(Self, Lines, iLine, FLong) else begin S := Lines[iLine]; P := Pointer(S); F := P; L1 := Length(S); if (L1 = 0) then begin case Highlighter of hlPascal: if FLong in [lgString] then FLong := lgNone; hlCBuilder, hlPython, hlPerl, hlNQC: if FLong in [lgPreproc] then FLong := lgNone; else if FLong in [lgPreproc1, lgPreproc2, lgString] then FLong := lgNone; end; end; I := 1; while I <= L1 do begin case FHighlighter of hlPascal: case FLong of lgNone: // not in comment case S[I] of '/': begin if S[I + 1] = '/' then Break; end; '{': begin P := StrScan(F + I, Char('}')); if P = nil then begin if S[I + 1] = '$' then FLong := lgPreproc1 else FLong := lgComment1; Break; end else I := P - F + 1; end; '(': if {S[I + 1]} F[I] = '*' then begin if {S[I + 2]} F[I + 1] = '$' then FLong := lgPreproc2 else FLong := lgComment2; P := StrScan(F + I + 2, Char(')')); if P = nil then Break else begin if P[-1] = '*' then FLong := lgNone; I := P - F + 1; end; end; '''': begin P := StrScan(F + I + 1, Char('''')); if P <> nil then begin i1 := P - F; if P[1] <> '''' then I := i1 else { ?? } end else I := L1 + 1; end; end; lgPreproc1, lgComment1: begin // { P := StrScan(F + I - 1, Char('}')); if P <> nil then begin FLong := lgNone; I := P - F + 1; end else I := L1 + 1; end; lgPreproc2, lgComment2: begin // (* P := StrScan(F + I, Char(')')); if P = nil then Break else begin if P[-1] = '*' then FLong := lgNone; I := P - F + 1; end; end; end; hlCBuilder, hlSql, hlJava, hlJScript, hlPhp, hlNQC, hlCSharp: case FLong of lgNone: // not in comment case S[I] of '/': if {S[I + 1]} F[I] = '*' then begin FLong := lgComment2; P := StrScan(F + I + 2, Char('/')); if P = nil then Break else begin if P[-1] = '*' then FLong := lgNone; I := P - F + 1; end; end; '"': begin P := StrScan(F + I + 1, Char('"')); if P <> nil then begin i1 := P - F; if P[1] <> '"' then I := i1 else { ?? } end else if FHighlighter in [hlCBuilder, hlJava, hlJScript, hlNQC] then begin if (LastNonSpaceChar(S) = '\') and (HasStringOpenEnd(Lines, iLine)) then FLong := lgString; I := L1 + 1; end else I := L1 + 1; end; '#': begin if (GetTrimChar(S, 1) = '#') and (LastNonSpaceChar(S) = '\') then begin FLong := lgPreproc; Break; end; end; end; lgComment2: begin // /* P := StrScan(F + I, Char('/')); if P = nil then Break else begin if P[-1] = '*' then FLong := lgNone; I := P - F + 1; end; end; lgString: begin P := StrScan(F + I + 1, Char('"')); if P <> nil then begin i1 := P - F; if P[1] <> '"' then I := i1 else { ?? } end else begin if FHighlighter in [hlCBuilder, hlJava, hlJScript, hlNQC] then begin if (LastNonSpaceChar(S) <> '\') or (not HasStringOpenEnd(Lines, iLine)) then FLong := lgNone; end; I := L1 + 1; end; end; lgPreproc: begin if LastNonSpaceChar(S) <> '\' then FLong := lgNone; end; end; hlPython, hlPerl: case FLong of lgNone: // not in comment case S[I] of '#': I := L1; '"': begin P := StrScan(F + I, Char('"')); if P = nil then begin FLong := lgString; Break; end else I := P - F + 1; end; end; lgString: // python and perl long string begin P := StrScan(F + I - 1, Char('"')); if P <> nil then begin FLong := lgNone; I := P - F + 1; end else I := L1 + 1; end; end; hlHtml: case FLong of lgNone: // not in comment case S[I] of '<': begin P := StrScan(F + I, Char('>')); if P = nil then begin // Multiline comments in HTML if S[2] = '!' then FLong := lgComment1 else FLong := lgTag; Break; end else I := P - F + 1; end; end; // Multiline comments in HTML lgComment1: begin P := StrScan(F + I - 1, Char('>')); if P = nil then Break else if (P[-2] = '-') and (P[-1] = '-') then FLong := lgNone; I := P - F + 1; end; lgTag: // html tag begin P := StrScan(F + I - 1, Char('>')); if P <> nil then begin FLong := lgNone; I := P - F + 1; end else I := L1 + 1; end; end; hlCocoR: case FLong of lgNone: // not in comment case S[I] of '(': if {S[I + 1]} F[I] = '*' then begin FLong := lgComment2; P := StrScan(F + I + 2, Char(')')); if P = nil then Break else begin if P[-1] = '*' then FLong := lgNone; I := P - F + 1; end; end; '"': begin P := StrScan(F + I + 1, Char('"')); if P <> nil then begin i1 := P - F; if P[1] <> '"' then I := i1 else { ?? } end else I := L1 + 1; end; '''': begin P := StrScan(F + I + 1, Char('''')); if P <> nil then begin i1 := P - F; if P[1] <> '''' then I := i1 else { ?? } end else I := L1 + 1; end; '/': if {S[I + 1]} F[I] = '*' then begin FLong := lgComment2; P := StrScan(F + I + 2, Char('/')); if P = nil then Break else begin if P[-1] = '*' then FLong := lgNone; I := P - F + 1; end; end; end; lgComment2: begin // (* P := StrScan(F + I, Char(')')); if P = nil then Break else begin if P[-1] = '*' then FLong := lgNone; I := P - F + 1; end; end; end; end; Inc(I); end; if (FHighlighter = hlCocoR) and (StrLIComp(PChar(S), 'productions', Length('productions')) = 0) then begin ProductionsLine := iLine; end; end; Inc(iLine); if FLongDesc[iLine] <> FLong then begin FLongDesc[iLine] := FLong; Result := True; // Invalidate end; end; // undefine following lines if MaxScanLine < MaxLine then FillChar(FLongDesc[MaxScanLine + 1], SizeOf(FLongDesc[0]) * (MaxLine - MaxScanLine), lgUndefined); end; function TJvHLEditor.FindLongEnd: Integer; var P, F: PChar; I: Integer; begin P := PChar(FLine); Result := Length(FLine); case FHighlighter of hlPascal: case FLong of lgPreproc1, lgComment1: begin P := StrScan(P, Char('}')); if P <> nil then Result := P - PChar(FLine); end; lgPreproc2, lgComment2: begin F := P; while True do begin F := StrScan(F, Char('*')); if F = nil then Exit; if F[1] = ')' then Break; Inc(F); end; P := F + 1; Result := P - PChar(FLine); end; end; hlCBuilder, hlSql, hlJava, hlJScript, hlPhp, hlNQC, hlCSharp: begin case FLong of lgComment2: begin F := P; while True do begin F := StrScan(F, Char('*')); if F = nil then Exit; if F[1] = '/' then Break; Inc(F); end; P := F + 1; Result := P - PChar(FLine); end; lgString: begin F := P; repeat P := StrScan(P, Char('"')); if P <> nil then begin if (P = F) or (P[-1] <> '\') then begin Result := P - F; Break; end else begin // count the backslashes I := 1; while (P - 1 - I > F) and (P[-1 - I] = '\') do Inc(I); if I and $01 = 0 then {faster than: if I mod 2 = 0 then} begin Result := P - F; Break; end; end; Inc(P); end; until P = nil; end; end; end; hlPython, hlPerl: case FLong of lgString: begin P := StrScan(P, Char('"')); if P <> nil then Result := P - PChar(FLine); end; end; hlHtml: case FLong of // HTML multiline comments lgComment1: begin P := StrScan(P, Char('>')); if P <> nil then // check if the previous characters are // -- if (P[-1] = '-') and (P[-2] = '-') then Result := P - PChar(FLine); end; lgTag: begin P := StrScan(P, Char('>')); if P <> nil then Result := P - PChar(FLine); end; end; end; end; procedure TJvHLEditor.TextModified(ACaretX, ACaretY: Integer; Action: TModifiedAction; const Text: string); var S: string; L: Integer; { LP, I: Integer; P: PChar; OldProductionsLine: Integer; } begin if not FLongTokens then Exit; case FHighlighter of hlPascal: S := #13'{}*()/ '; hlCBuilder, hlJava, hlJScript, hlSql, hlPhp, hlNQC, hlCSharp: S := #13'*/\ '; hlVB: S := #13''''; hlPython, hlPerl: S := #13'#"'; hlHtml: S := #13'<>'; hlCocoR: S := #13'*()/ '; hlSyntaxHighlighter: if FSyntaxHighlighter <> nil then begin if FSyntaxHighlighter.GetRescanLongKeys(Self, Action, ACaretX, ACaretY, Text) then begin if RescanLong(ACaretY) then Invalidate; end; Exit; end else S := #13; else S := #13; { unknown Highlighter ? } end; if Action = maAll then ACaretY := -1; // rescan all lines if (Action in [maAll, maReplace]) or HasAnyChar(S, Text) then begin if RescanLong(ACaretY) then Invalidate; end else begin if (Highlighter = hlPascal) and (Cardinal(ACaretY) < Cardinal(Length(FLongDesc))) then begin // comment <-> preproc S := Lines[ACaretY]; L := Length(S); // [Backspace, "insert"] if ((ACaretX > 1) and (ACaretX <= L + 1) and (S[ACaretX - 1] = '{')) or ((ACaretX > 2) and (ACaretX <= L + 2) and (S[ACaretX - 2] = '(') and (S[ACaretX - 1] = '*')) or // [Delete] ((ACaretX > 0) and (ACaretX <= L) and (S[ACaretX] = '{')) or ((ACaretX > 1) and (ACaretX <= L + 1) and (S[ACaretX - 1] = '(') and (S[ACaretX] = '*')) then begin if RescanLong(ACaretY) then Invalidate; end; end; end; { if (FHighlighter = hlCocoR) and (HasAnyChar('productions'#13, Text)) then begin LP := Length('productions'); OldProductionsLine := ProductionsLine; ProductionsLine := High(Integer); for I := 0 to Lines.Count - 1 do begin P := PChar(Lines[I]); if (StrLIComp(P, 'productions', LP) = 0) and ((Length(P) = LP) or (P[LP] = ' ')) then begin ProductionsLine := I; Break; end; end; if ProductionsLine <> OldProductionsLine then Invalidate; end; } end; function TJvHLEditor.GetReservedWord(const Token: string; var Reserved: Boolean): Boolean; begin Result := Assigned(FOnReservedWord); if Result then begin Reserved := False; FOnReservedWord(Self, Token, Reserved); end end; function TJvHLEditor.UserReservedWords: Boolean; begin Result := Assigned(FOnReservedWord); end; procedure TJvHLEditor.Assign(Source: TPersistent); begin inherited Assign(Source); if Source is TJvHLEditor then begin FHighlighter := TJvHLEditor(Source).Highlighter; Colors.Assign(TJvHLEditor(Source).Colors); SelForeColor := TJvHLEditor(Source).SelForeColor; SelBackColor := TJvHLEditor(Source).SelBackColor; Color := TJvHLEditor(Source).Color; FSyntaxHighlighting := TJvHLEditor(Source).SyntaxHighlighting; RightMarginColor := TJvHLEditor(Source).RightMarginColor; Invalidate; end; end; function TJvHLEditor.GetDelphiColors: Boolean; function CompareColor(Symbol: TJvSymbolColor; const DelphiColor: TDelphiColor): Boolean; begin Result := (Symbol.ForeColor = DelphiColor.ForeColor) and (Symbol.BackColor = DelphiColor.BackColor) and (Symbol.Style = DelphiColor.Style); end; begin Result := False; if not CompareColor(Colors.Comment, DelphiColor_Comment) then Exit; if not CompareColor(Colors.Preproc, DelphiColor_Preproc) then Exit; if not CompareColor(Colors.Number, DelphiColor_Number) then Exit; if not CompareColor(Colors.Strings, DelphiColor_Strings) then Exit; if not CompareColor(Colors.Symbol, DelphiColor_Symbol) then Exit; if not CompareColor(Colors.Reserved, DelphiColor_Reserved) then Exit; if not CompareColor(Colors.Identifier, DelphiColor_Identifier) then Exit; if not CompareColor(Colors.PlainText, DelphiColor_PlainText) then Exit; Result := True; end; procedure TJvHLEditor.SetDelphiColors(Value: Boolean); procedure SetColor(Symbol: TJvSymbolColor; const DelphiColor: TDelphiColor); begin with DelphiColor do Symbol.SetColor(ForeColor, BackColor, Style); end; begin if Value then begin SetColor(Colors.Comment, DelphiColor_Comment); SetColor(Colors.Preproc, DelphiColor_Preproc); SetColor(Colors.Number, DelphiColor_Number); SetColor(Colors.Strings, DelphiColor_Strings); SetColor(Colors.Symbol, DelphiColor_Symbol); SetColor(Colors.Reserved, DelphiColor_Reserved); SetColor(Colors.Identifier, DelphiColor_Identifier); SetColor(Colors.PlainText, DelphiColor_PlainText); end; end; procedure TJvHLEditor.SetSyntaxHighlighter(const Value: TJvEditorHighlighter); begin if Value <> FSyntaxHighlighter then begin if Value <> nil then FHighlighter := hlSyntaxHighlighter else if FHighlighter = hlSyntaxHighlighter then FHighlighter := hlNone; FSyntaxHighlighter := Value; RescanLong(0); Invalidate; end; end; function TJvHLEditor.GetColors: TJvColors; begin Result := FColors; end; procedure TJvHLEditor.SetColors(const Value: TJvColors); begin FColors.Assign(Value); end; function TJvHLEditor.GetSyntaxHighlighting: Boolean; begin Result := FSyntaxHighlighting; end; procedure TJvHLEditor.SetSyntaxHighlighting(Value: Boolean); begin FSyntaxHighlighting := Value; Invalidate; end; function TJvHLEditor.GetHighlighter: TJvHighlighter; begin Result := FHighlighter; end; {$IFDEF UNITVERSIONING} initialization RegisterUnitVersion(HInstance, UnitVersioning); finalization UnregisterUnitVersion(HInstance); {$ENDIF UNITVERSIONING} end.