| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303 |
- {
- This file is part of the Free Component Library (FCL)
- Copyright (c) 2025 by Michael Van Canneyt
- INI syntax highlighter
- See the file COPYING.FPC, included in this distribution,
- for details about the copyright.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- **********************************************************************}
- {$MODE objfpc}
- {$H+}
- unit syntax.ini;
- interface
- uses
- {$IFDEF FPC_DOTTEDUNITS}
- System.Types, System.SysUtils, syntax.highlighter;
- {$ELSE}
- Types, SysUtils, syntax.highlighter;
- {$ENDIF}
- type
- { TIniSyntaxHighlighter }
- TIniSyntaxHighlighter = class(TSyntaxHighlighter)
- private
- FSource: string;
- FPos: integer;
- protected
- procedure ProcessSection(var endPos: integer);
- procedure ProcessKey(var endPos: integer);
- procedure ProcessComment(var endPos: integer);
- procedure ProcessValue(var endPos: integer);
- function IsWordChar(ch: char): boolean;
- function IsKeyChar(ch: char): boolean;
- class procedure CheckCategories;
- class procedure RegisterDefaultCategories; override;
- class function GetLanguages : TStringDynarray; override;
- public
- constructor Create; override;
- class var
- CategoryINI : Integer;
- function Execute(const Source: string): TSyntaxTokenArray; override;
- end;
- function DoIniHighlighting(const Source: string): TSyntaxTokenArray;
- implementation
- { TIniSyntaxHighlighter }
- procedure TIniSyntaxHighlighter.ProcessSection(var endPos: integer);
- var
- startPos: integer;
- begin
- startPos := FPos;
- Inc(FPos); // Skip opening [
- while (FPos <= Length(FSource)) and (FSource[FPos] <> ']') and (FSource[FPos] <> #10) and (FSource[FPos] <> #13) do
- Inc(FPos);
- if (FPos <= Length(FSource)) and (FSource[FPos] = ']') then
- Inc(FPos); // Include closing ]
- endPos := FPos - 1;
- AddToken(Copy(FSource, startPos, endPos - startPos + 1), shSection);
- end;
- procedure TIniSyntaxHighlighter.ProcessKey(var endPos: integer);
- var
- startPos: integer;
- begin
- startPos := FPos;
- while (FPos <= Length(FSource)) and IsKeyChar(FSource[FPos]) do
- Inc(FPos);
- endPos := FPos - 1;
- AddToken(Copy(FSource, startPos, endPos - startPos + 1), shKey);
- end;
- procedure TIniSyntaxHighlighter.ProcessComment(var endPos: integer);
- var
- startPos: integer;
- begin
- startPos := FPos;
- // Process until end of line
- while (FPos <= Length(FSource)) and (FSource[FPos] <> #10) and (FSource[FPos] <> #13) do
- Inc(FPos);
- endPos := FPos - 1;
- AddToken(Copy(FSource, startPos, endPos - startPos + 1), shComment);
- end;
- procedure TIniSyntaxHighlighter.ProcessValue(var endPos: integer);
- var
- startPos: integer;
- inQuotes: boolean;
- quoteChar: char;
- begin
- startPos := FPos;
- inQuotes := False;
- quoteChar := #0;
- // Skip leading whitespace
- while (FPos <= Length(FSource)) and (FSource[FPos] in [' ', #9]) do
- Inc(FPos);
- // Check if value starts with quotes
- if (FPos <= Length(FSource)) and (FSource[FPos] in ['"', '''']) then
- begin
- inQuotes := True;
- quoteChar := FSource[FPos];
- Inc(FPos);
- end;
- if inQuotes then
- begin
- // Process quoted value
- while (FPos <= Length(FSource)) and (FSource[FPos] <> quoteChar) do
- begin
- if (FSource[FPos] = '\') and (FPos < Length(FSource)) then
- Inc(FPos); // Skip escaped character
- Inc(FPos);
- end;
- if (FPos <= Length(FSource)) and (FSource[FPos] = quoteChar) then
- Inc(FPos); // Include closing quote
- end
- else
- begin
- // Process unquoted value until end of line or comment
- while (FPos <= Length(FSource)) and (FSource[FPos] <> #10) and (FSource[FPos] <> #13) and (FSource[FPos] <> ';') and (FSource[FPos] <> '#') do
- Inc(FPos);
- end;
- endPos := FPos - 1;
- if inQuotes then
- AddToken(Copy(FSource, startPos, endPos - startPos + 1), shStrings)
- else
- AddToken(Copy(FSource, startPos, endPos - startPos + 1), shDefault);
- end;
- function TIniSyntaxHighlighter.IsWordChar(ch: char): boolean;
- begin
- Result := ch in ['a'..'z', 'A'..'Z', '0'..'9', '_', '-', '.'];
- end;
- function TIniSyntaxHighlighter.IsKeyChar(ch: char): boolean;
- begin
- Result := ch in ['a'..'z', 'A'..'Z', '0'..'9', '_', '-', '.', ' '];
- end;
- class procedure TIniSyntaxHighlighter.CheckCategories;
- begin
- if CategoryINI = 0 then
- RegisterDefaultCategories;
- end;
- class procedure TIniSyntaxHighlighter.RegisterDefaultCategories;
- begin
- CategoryINI := RegisterCategory('INI');
- end;
- class function TIniSyntaxHighlighter.GetLanguages: TStringDynarray;
- begin
- Result := ['ini', 'cfg', 'conf'];
- end;
- constructor TIniSyntaxHighlighter.Create;
- begin
- inherited Create;
- CheckCategories;
- DefaultCategory := CategoryINI;
- end;
- function TIniSyntaxHighlighter.Execute(const Source: string): TSyntaxTokenArray;
- var
- lLen, endPos, startPos: integer;
- ch: char;
- atLineStart: boolean;
- begin
- Result := Nil;
- CheckCategories;
- lLen := Length(Source);
- if lLen = 0 then
- Exit;
- FSource := Source;
- FTokens.Reset;
- FPos := 1;
- EndPos := 0;
- atLineStart := True;
- while FPos <= lLen do
- begin
- ch := FSource[FPos];
- case ch of
- '[':
- begin
- if atLineStart then
- ProcessSection(endPos)
- else
- begin
- AddToken('[', shSymbol);
- endPos := FPos;
- Inc(FPos);
- end;
- atLineStart := False;
- end;
- ';', '#':
- begin
- ProcessComment(endPos);
- atLineStart := False;
- end;
- '=':
- begin
- AddToken('=', shOperator);
- endPos := FPos;
- Inc(FPos);
- // Process value after =
- if FPos <= Length(FSource) then
- ProcessValue(endPos);
- atLineStart := False;
- end;
- '"', '''':
- begin
- FPos := FPos - 1; // Back up one to include quote in ProcessValue
- ProcessValue(endPos);
- Inc(FPos); // Move past the value
- atLineStart := False;
- end;
- #10, #13:
- begin
- startPos := FPos;
- if (FSource[FPos] = #13) and (FPos < Length(FSource)) and (FSource[FPos + 1] = #10) then
- Inc(FPos, 2) // CRLF
- else
- Inc(FPos); // LF or CR only
- endPos := FPos - 1;
- AddToken(Copy(FSource, startPos, endPos - startPos + 1), shDefault);
- atLineStart := True;
- end;
- ' ', #9:
- begin
- startPos := FPos;
- while (FPos <= Length(FSource)) and (FSource[FPos] in [' ', #9]) do
- Inc(FPos);
- endPos := FPos - 1;
- AddToken(Copy(FSource, startPos, endPos - startPos + 1), shDefault);
- end;
- 'a'..'z', 'A'..'Z', '0'..'9', '_', '-', '.':
- begin
- if atLineStart then
- begin
- // This could be a key
- ProcessKey(endPos);
- end
- else
- begin
- // Regular text
- startPos := FPos;
- while (FPos <= Length(FSource)) and IsWordChar(FSource[FPos]) do
- Inc(FPos);
- endPos := FPos - 1;
- AddToken(Copy(FSource, startPos, endPos - startPos + 1), shDefault);
- end;
- atLineStart := False;
- end;
- else
- AddToken(ch, shDefault);
- endPos := FPos;
- Inc(FPos);
- atLineStart := False;
- end;
- if FPos = endPos then Inc(FPos);
- end;
- Result := FTokens.GetTokens;
- end;
- function DoIniHighlighting(const Source: string): TSyntaxTokenArray;
- var
- highlighter: TIniSyntaxHighlighter;
- begin
- highlighter := TIniSyntaxHighlighter.Create;
- try
- Result := highlighter.Execute(Source);
- finally
- highlighter.Free;
- end;
- end;
- initialization
- TIniSyntaxHighlighter.Register;
- end.
|