syntax.ini.pp 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. {
  2. This file is part of the Free Component Library (FCL)
  3. Copyright (c) 2025 by Michael Van Canneyt
  4. INI syntax highlighter
  5. See the file COPYING.FPC, included in this distribution,
  6. for details about the copyright.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  10. **********************************************************************}
  11. {$MODE objfpc}
  12. {$H+}
  13. unit syntax.ini;
  14. interface
  15. uses
  16. {$IFDEF FPC_DOTTEDUNITS}
  17. System.Types, System.SysUtils, syntax.highlighter;
  18. {$ELSE}
  19. Types, SysUtils, syntax.highlighter;
  20. {$ENDIF}
  21. type
  22. { TIniSyntaxHighlighter }
  23. TIniSyntaxHighlighter = class(TSyntaxHighlighter)
  24. private
  25. FSource: string;
  26. FPos: integer;
  27. protected
  28. procedure ProcessSection(var endPos: integer);
  29. procedure ProcessKey(var endPos: integer);
  30. procedure ProcessComment(var endPos: integer);
  31. procedure ProcessValue(var endPos: integer);
  32. function IsWordChar(ch: char): boolean;
  33. function IsKeyChar(ch: char): boolean;
  34. class procedure CheckCategories;
  35. class procedure RegisterDefaultCategories; override;
  36. class function GetLanguages : TStringDynarray; override;
  37. public
  38. constructor Create; override;
  39. class var
  40. CategoryINI : Integer;
  41. function Execute(const Source: string): TSyntaxTokenArray; override;
  42. end;
  43. function DoIniHighlighting(const Source: string): TSyntaxTokenArray;
  44. implementation
  45. { TIniSyntaxHighlighter }
  46. procedure TIniSyntaxHighlighter.ProcessSection(var endPos: integer);
  47. var
  48. startPos: integer;
  49. begin
  50. startPos := FPos;
  51. Inc(FPos); // Skip opening [
  52. while (FPos <= Length(FSource)) and (FSource[FPos] <> ']') and (FSource[FPos] <> #10) and (FSource[FPos] <> #13) do
  53. Inc(FPos);
  54. if (FPos <= Length(FSource)) and (FSource[FPos] = ']') then
  55. Inc(FPos); // Include closing ]
  56. endPos := FPos - 1;
  57. AddToken(Copy(FSource, startPos, endPos - startPos + 1), shSection);
  58. end;
  59. procedure TIniSyntaxHighlighter.ProcessKey(var endPos: integer);
  60. var
  61. startPos: integer;
  62. begin
  63. startPos := FPos;
  64. while (FPos <= Length(FSource)) and IsKeyChar(FSource[FPos]) do
  65. Inc(FPos);
  66. endPos := FPos - 1;
  67. AddToken(Copy(FSource, startPos, endPos - startPos + 1), shKey);
  68. end;
  69. procedure TIniSyntaxHighlighter.ProcessComment(var endPos: integer);
  70. var
  71. startPos: integer;
  72. begin
  73. startPos := FPos;
  74. // Process until end of line
  75. while (FPos <= Length(FSource)) and (FSource[FPos] <> #10) and (FSource[FPos] <> #13) do
  76. Inc(FPos);
  77. endPos := FPos - 1;
  78. AddToken(Copy(FSource, startPos, endPos - startPos + 1), shComment);
  79. end;
  80. procedure TIniSyntaxHighlighter.ProcessValue(var endPos: integer);
  81. var
  82. startPos: integer;
  83. inQuotes: boolean;
  84. quoteChar: char;
  85. begin
  86. startPos := FPos;
  87. inQuotes := False;
  88. quoteChar := #0;
  89. // Skip leading whitespace
  90. while (FPos <= Length(FSource)) and (FSource[FPos] in [' ', #9]) do
  91. Inc(FPos);
  92. // Check if value starts with quotes
  93. if (FPos <= Length(FSource)) and (FSource[FPos] in ['"', '''']) then
  94. begin
  95. inQuotes := True;
  96. quoteChar := FSource[FPos];
  97. Inc(FPos);
  98. end;
  99. if inQuotes then
  100. begin
  101. // Process quoted value
  102. while (FPos <= Length(FSource)) and (FSource[FPos] <> quoteChar) do
  103. begin
  104. if (FSource[FPos] = '\') and (FPos < Length(FSource)) then
  105. Inc(FPos); // Skip escaped character
  106. Inc(FPos);
  107. end;
  108. if (FPos <= Length(FSource)) and (FSource[FPos] = quoteChar) then
  109. Inc(FPos); // Include closing quote
  110. end
  111. else
  112. begin
  113. // Process unquoted value until end of line or comment
  114. while (FPos <= Length(FSource)) and (FSource[FPos] <> #10) and (FSource[FPos] <> #13) and (FSource[FPos] <> ';') and (FSource[FPos] <> '#') do
  115. Inc(FPos);
  116. end;
  117. endPos := FPos - 1;
  118. if inQuotes then
  119. AddToken(Copy(FSource, startPos, endPos - startPos + 1), shStrings)
  120. else
  121. AddToken(Copy(FSource, startPos, endPos - startPos + 1), shDefault);
  122. end;
  123. function TIniSyntaxHighlighter.IsWordChar(ch: char): boolean;
  124. begin
  125. Result := ch in ['a'..'z', 'A'..'Z', '0'..'9', '_', '-', '.'];
  126. end;
  127. function TIniSyntaxHighlighter.IsKeyChar(ch: char): boolean;
  128. begin
  129. Result := ch in ['a'..'z', 'A'..'Z', '0'..'9', '_', '-', '.', ' '];
  130. end;
  131. class procedure TIniSyntaxHighlighter.CheckCategories;
  132. begin
  133. if CategoryINI = 0 then
  134. RegisterDefaultCategories;
  135. end;
  136. class procedure TIniSyntaxHighlighter.RegisterDefaultCategories;
  137. begin
  138. CategoryINI := RegisterCategory('INI');
  139. end;
  140. class function TIniSyntaxHighlighter.GetLanguages: TStringDynarray;
  141. begin
  142. Result := ['ini', 'cfg', 'conf'];
  143. end;
  144. constructor TIniSyntaxHighlighter.Create;
  145. begin
  146. inherited Create;
  147. CheckCategories;
  148. DefaultCategory := CategoryINI;
  149. end;
  150. function TIniSyntaxHighlighter.Execute(const Source: string): TSyntaxTokenArray;
  151. var
  152. lLen, endPos, startPos: integer;
  153. ch: char;
  154. atLineStart: boolean;
  155. begin
  156. Result := Nil;
  157. CheckCategories;
  158. lLen := Length(Source);
  159. if lLen = 0 then
  160. Exit;
  161. FSource := Source;
  162. FTokens.Reset;
  163. FPos := 1;
  164. EndPos := 0;
  165. atLineStart := True;
  166. while FPos <= lLen do
  167. begin
  168. ch := FSource[FPos];
  169. case ch of
  170. '[':
  171. begin
  172. if atLineStart then
  173. ProcessSection(endPos)
  174. else
  175. begin
  176. AddToken('[', shSymbol);
  177. endPos := FPos;
  178. Inc(FPos);
  179. end;
  180. atLineStart := False;
  181. end;
  182. ';', '#':
  183. begin
  184. ProcessComment(endPos);
  185. atLineStart := False;
  186. end;
  187. '=':
  188. begin
  189. AddToken('=', shOperator);
  190. endPos := FPos;
  191. Inc(FPos);
  192. // Process value after =
  193. if FPos <= Length(FSource) then
  194. ProcessValue(endPos);
  195. atLineStart := False;
  196. end;
  197. '"', '''':
  198. begin
  199. FPos := FPos - 1; // Back up one to include quote in ProcessValue
  200. ProcessValue(endPos);
  201. Inc(FPos); // Move past the value
  202. atLineStart := False;
  203. end;
  204. #10, #13:
  205. begin
  206. startPos := FPos;
  207. if (FSource[FPos] = #13) and (FPos < Length(FSource)) and (FSource[FPos + 1] = #10) then
  208. Inc(FPos, 2) // CRLF
  209. else
  210. Inc(FPos); // LF or CR only
  211. endPos := FPos - 1;
  212. AddToken(Copy(FSource, startPos, endPos - startPos + 1), shDefault);
  213. atLineStart := True;
  214. end;
  215. ' ', #9:
  216. begin
  217. startPos := FPos;
  218. while (FPos <= Length(FSource)) and (FSource[FPos] in [' ', #9]) do
  219. Inc(FPos);
  220. endPos := FPos - 1;
  221. AddToken(Copy(FSource, startPos, endPos - startPos + 1), shDefault);
  222. end;
  223. 'a'..'z', 'A'..'Z', '0'..'9', '_', '-', '.':
  224. begin
  225. if atLineStart then
  226. begin
  227. // This could be a key
  228. ProcessKey(endPos);
  229. end
  230. else
  231. begin
  232. // Regular text
  233. startPos := FPos;
  234. while (FPos <= Length(FSource)) and IsWordChar(FSource[FPos]) do
  235. Inc(FPos);
  236. endPos := FPos - 1;
  237. AddToken(Copy(FSource, startPos, endPos - startPos + 1), shDefault);
  238. end;
  239. atLineStart := False;
  240. end;
  241. else
  242. AddToken(ch, shDefault);
  243. endPos := FPos;
  244. Inc(FPos);
  245. atLineStart := False;
  246. end;
  247. if FPos = endPos then Inc(FPos);
  248. end;
  249. Result := FTokens.GetTokens;
  250. end;
  251. function DoIniHighlighting(const Source: string): TSyntaxTokenArray;
  252. var
  253. highlighter: TIniSyntaxHighlighter;
  254. begin
  255. highlighter := TIniSyntaxHighlighter.Create;
  256. try
  257. Result := highlighter.Execute(Source);
  258. finally
  259. highlighter.Free;
  260. end;
  261. end;
  262. initialization
  263. TIniSyntaxHighlighter.Register;
  264. end.