PageRenderTime 58ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/components/synedit/synbeautifier.pas

http://github.com/graemeg/lazarus
Pascal | 1810 lines | 1409 code | 217 blank | 184 comment | 148 complexity | 224650010fd300b1ae7c059b8b652bb3 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.0, MPL-2.0-no-copyleft-exception
  1. {-------------------------------------------------------------------------------
  2. The contents of this file are subject to the Mozilla Public License
  3. Version 1.1 (the "License"); you may not use this file except in compliance
  4. with the License. You may obtain a copy of the License at
  5. http://www.mozilla.org/MPL/
  6. Software distributed under the License is distributed on an "AS IS" basis,
  7. WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
  8. the specific language governing rights and limitations under the License.
  9. The Original Code is: SynHighlighterGeneral.pas, released 2000-04-07.
  10. The Original Code is based on the mwGeneralSyn.pas file from the
  11. mwEdit component suite by Martin Waldenburg and other developers, the Initial
  12. Author of this file is Martin Waldenburg.
  13. Portions written by Martin Waldenburg are copyright 1999 Martin Waldenburg.
  14. All Rights Reserved.
  15. Contributors to the SynEdit and mwEdit projects are listed in the
  16. Contributors.txt file.
  17. Alternatively, the contents of this file may be used under the terms of the
  18. GNU General Public License Version 2 or later (the "GPL"), in which case
  19. the provisions of the GPL are applicable instead of those above.
  20. If you wish to allow use of your version of this file only under the terms
  21. of the GPL and not to allow others to use your version of this file
  22. under the MPL, indicate your decision by deleting the provisions above and
  23. replace them with the notice and other provisions required by the GPL.
  24. If you do not delete the provisions above, a recipient may use your version
  25. of this file under either the MPL or the GPL.
  26. $Id: SynHighlighterGeneral.pas,v 1.3 2000/11/08 22:09:59 mghie Exp $
  27. You may retrieve the latest version of this file at the SynEdit home page,
  28. located at http://SynEdit.SourceForge.net
  29. }
  30. unit SynBeautifier;
  31. {$I synedit.inc}
  32. interface
  33. uses
  34. Classes, SysUtils, LCLProc, SynEditMiscClasses, SynEditMiscProcs, LazSynEditText, SynEditPointClasses,
  35. SynEditKeyCmds, SynHighlighterPas, SynEditHighlighterFoldBase, SynRegExpr;
  36. type
  37. TSynCustomBeautifier = class;
  38. // Callback for indent
  39. TSynBeautifierSetIndentProc =
  40. procedure(
  41. (* LinePos:
  42. 1-based, the line that should be changed *)
  43. LinePos: Integer;
  44. (* Indent:
  45. New indent in spaces (Logical = Physical *)
  46. Indent: Integer;
  47. (* RelativeToLinePos:
  48. Indent specifies +/- offset from indent on RTLine (0: for absolute indent) *)
  49. RelativeToLinePos: Integer = 0;
  50. (* IndentChars:
  51. String used to build indent; maybe empty, single char, or string (usually 1 tab or 1 space)
  52. The String will be repeated and cut as needed, then filled with spaces at the end
  53. * NOTE: If this is specified the TSynBeautifierIndentType is ignored
  54. *)
  55. IndentChars: String = '';
  56. (* IndentCharsFromLinePos:
  57. Use tab/space mix from this Line for indent (if specified > 0)
  58. "IndentChars" will only be used, if the found tab/space mix is to short
  59. * NOTE: If this is specified the TSynBeautifierIndentType is ignored
  60. *)
  61. IndentCharsFromLinePos: Integer = 0
  62. ) of object;
  63. // Event triggered if Lines may needs Indend
  64. TSynBeautifierGetIndentEvent =
  65. function(
  66. Sender: TObject; // the beautifier
  67. Editor: TObject; // the synedit
  68. LogCaret, OldLogCaret: TPoint; // Caret after and before the edit action
  69. FirstLinePos, LastLinePos: Integer; // Changed lines. this can include lines outside the range of OldLogCaret to LogCaret
  70. Reason: TSynEditorCommand; // what caused the event
  71. SetIndentProc: TSynBeautifierSetIndentProc
  72. ): boolean of object;
  73. { TSynCustomBeautifier }
  74. TSynCustomBeautifier = class(TComponent)
  75. private
  76. FAutoIndent: Boolean;
  77. FOnGetDesiredIndent: TSynBeautifierGetIndentEvent;
  78. FCurrentEditor: TSynEditBase; // For callback / applyIndent
  79. FCurrentLines: TSynEditStrings;
  80. protected
  81. procedure DoBeforeCommand(const ACaret: TSynEditCaret;
  82. var Command: TSynEditorCommand); virtual; abstract;
  83. procedure DoAfterCommand(const ACaret: TSynEditCaret;
  84. var Command: TSynEditorCommand;
  85. StartLinePos, EndLinePos: Integer); virtual; abstract;
  86. property CurrentEditor: TSynEditBase read FCurrentEditor;
  87. property CurrentLines: TSynEditStrings read FCurrentLines;
  88. public
  89. procedure Assign(Src: TPersistent); override;
  90. function GetCopy: TSynCustomBeautifier;
  91. procedure BeforeCommand(const Editor: TSynEditBase; const Lines: TSynEditStrings;
  92. const ACaret: TSynEditCaret; var Command: TSynEditorCommand;
  93. InitialCmd: TSynEditorCommand);
  94. procedure AfterCommand(const Editor: TSynEditBase; const Lines: TSynEditStrings;
  95. const ACaret: TSynEditCaret; var Command: TSynEditorCommand;
  96. InitialCmd: TSynEditorCommand; StartLinePos, EndLinePos: Integer);
  97. // GetDesiredIndentForLine: Returns the 1-based Physical x pos
  98. function GetDesiredIndentForLine
  99. (Editor: TSynEditBase; const Lines: TSynEditStrings;
  100. const ACaret: TSynEditCaret): Integer; virtual; abstract;
  101. function GetDesiredIndentForLine
  102. (Editor: TSynEditBase; const Lines: TSynEditStrings;
  103. const ACaret: TSynEditCaret; out ReplaceIndent: Boolean;
  104. out DesiredIndent: String): Integer; virtual; abstract;
  105. property OnGetDesiredIndent: TSynBeautifierGetIndentEvent
  106. read FOnGetDesiredIndent write FOnGetDesiredIndent;
  107. property AutoIndent: Boolean read FAutoIndent write FAutoIndent;
  108. end;
  109. TSynCustomBeautifierClass = class of TSynCustomBeautifier;
  110. TSynBeautifierIndentType = (
  111. sbitSpace, sbitCopySpaceTab,
  112. sbitPositionCaret,
  113. sbitConvertToTabSpace, // convert to tabs, fill with spcaces if needed
  114. sbitConvertToTabOnly // convert to tabs, even if shorter
  115. );
  116. { TSynBeautifier }
  117. TSynBeautifier = class(TSynCustomBeautifier)
  118. private
  119. FIndentType: TSynBeautifierIndentType;
  120. protected
  121. FLogicalIndentLen: Integer;
  122. function GetLine(AnIndex: Integer): String; virtual;
  123. procedure GetIndentInfo(const LinePos: Integer;
  124. out ACharMix: String; out AnIndent: Integer;
  125. AStopScanAtLine: Integer = 0);
  126. // requiring FCurrentEditor, FCurrentLines
  127. procedure DoBeforeCommand(const ACaret: TSynEditCaret;
  128. var Command: TSynEditorCommand); override;
  129. procedure DoAfterCommand(const ACaret: TSynEditCaret;
  130. var Command: TSynEditorCommand;
  131. StartLinePos, EndLinePos: Integer); override;
  132. function GetIndent(const LinePos: Integer; out BasedOnLine: Integer;
  133. AStopScanAtLine: Integer = 0): Integer;
  134. function AdjustCharMix(DesiredIndent: Integer; CharMix, AppendMix: String): String;
  135. function CreateTabSpaceMix(var DesiredIndent: Integer; OnlyTabs: Boolean): String;
  136. function GetCharMix(const LinePos: Integer; var Indent: Integer;
  137. var IndentCharsFromLinePos: Integer;
  138. ModifyIndent: Boolean = False): String;
  139. procedure ApplyIndent(LinePos: Integer; Indent: Integer;
  140. RelativeToLinePos: Integer = 0; IndentChars: String = '';
  141. IndentCharsFromLinePos: Integer = 0);
  142. function UnIndentLine(const ACaret: TSynEditCaret; out CaretNewX: Integer): Boolean;
  143. public
  144. procedure Assign(Src: TPersistent); override;
  145. // Retruns a 0-based position (even 0-based physical)
  146. function GetIndentForLine(Editor: TSynEditBase; const Line: string;
  147. Physical: boolean): Integer;
  148. function GetDesiredIndentForLine
  149. (Editor: TSynEditBase; const Lines: TSynEditStrings;
  150. const ACaret: TSynEditCaret): Integer; override;
  151. function GetDesiredIndentForLine
  152. (Editor: TSynEditBase; const Lines: TSynEditStrings;
  153. const ACaret: TSynEditCaret; out ReplaceIndent: Boolean;
  154. out DesiredIndent: String): Integer; override;
  155. published
  156. property IndentType: TSynBeautifierIndentType read FIndentType write FIndentType;
  157. end;
  158. TSynCommentContineMode = (
  159. sccNoPrefix, // May still do indent, if matched
  160. sccPrefixAlways, // If the pattern did not match all will be done, except the indent AFTER the prefix (can not be detected)
  161. sccPrefixMatch
  162. //sccPrefixMatchFirst
  163. //sccPrefixMatchPrev
  164. //sccPrefixMatchPrevTwo // last 2 lines match
  165. //sccPrefixMatchPrevTwoExact // last 2 lines match and same result for prefix
  166. );
  167. TSynCommentMatchLine = (
  168. sclMatchFirst, // Match the first line of the comment to get substitutes for Prefix ($1)
  169. sclMatchPrev // Match the previous line of the comment to get substitutes for Prefix ($1)
  170. //sclMatchNestedFirst // For nested comments, first line of this nest.
  171. );
  172. TSynCommentMatchMode = (
  173. // Which parts of the first line to match sclMatchFirst or sclMatchPrev (if prev = first)
  174. scmMatchAfterOpening, // will not include (*,{,//. The ^ will match the first char after
  175. scmMatchOpening, // will include (*,{,//. The ^ will match the ({/
  176. scmMatchWholeLine, // Match the entire line
  177. scmMatchAtAsterisk // AnsiComment only, will match the * of (*, but not the (
  178. );
  179. TSynCommentIndentFlag = (
  180. // * For Matching lines (FCommentMode)
  181. // By default indent is the same as for none comment lines (none overrides sciAlignOpen)
  182. sciNone, // Does not Indent comment lines (Prefix may contain a fixed indent)
  183. sciAlignOpen, // Indent to real opening pos on first line, if comment does not start at BOL "Foo(); (*"
  184. // Will force every new line back to Align.
  185. // To align only once, use IndentFirstLineExtra=MaxInt
  186. // sciAdd...: if (only if) previous line had started with the opening token "(*" or "{".
  187. // or if sciAlignOpen is set
  188. // This is not done for "//", as Comment Extension will add a new "//"
  189. // But Applies to sciNone
  190. sciAddTokenLen, // add 1 or 2 spaces to indent (for the length of the token)
  191. // in case of scmMatchAtAsterisk, 1 space is added. ("(" only)
  192. sciAddPastTokenIndent, // Adds any indent found past the opening token "(*", "{" or "//".
  193. // For "//" this is added after the nem "//", but before the prefix.
  194. sciMatchOnlyTokenLen, // Apply the Above only if first line matches. (Only if sciAddTokenLen is specified)
  195. sciMatchOnlyPastTokenIndent,
  196. sciAlignOnlyTokenLen, // Apply the Above only if sciAlignOpen was used (include via max)
  197. sciAlignOnlyPastTokenIndent,
  198. // flag to ignore spaces, that are matched.
  199. // flag to be smart, if not matched
  200. sciApplyIndentForNoMatch // Apply above rules For NONE Matching lines (FCommentMode),
  201. // includes FIndentFirstLineExtra
  202. );
  203. TSynCommentIndentFlags = set of TSynCommentIndentFlag;
  204. TSynCommentExtendMode = (
  205. sceNever, // Never Extend
  206. sceAlways, // Always
  207. sceSplitLine, // If the line was split (caret was not at EOL, when enter was pressed
  208. sceMatching, // If the line matched (even if sccPrefixAlways or sccNoPrefix
  209. sceMatchingSplitLine
  210. );
  211. TSynCommentType = (sctAnsi, sctBor, sctSlash);
  212. // end mode
  213. { TSynBeautifierPascal }
  214. TSynBeautifierPascal = class(TSynBeautifier)
  215. private
  216. FIndentMode: Array [TSynCommentType] of TSynCommentIndentFlags;
  217. FIndentFirstLineExtra: Array [TSynCommentType] of String;
  218. FIndentFirstLineMax: Array [TSynCommentType] of Integer;
  219. FCommentMode: Array [TSynCommentType] of TSynCommentContineMode;
  220. FMatchMode: Array [TSynCommentType] of TSynCommentMatchMode;
  221. FMatchLine: Array [TSynCommentType] of TSynCommentMatchLine;
  222. FMatch: Array [TSynCommentType] of String;
  223. FPrefix: Array [TSynCommentType] of String;
  224. FCommentIndent: Array [TSynCommentType] of TSynBeautifierIndentType;
  225. FEolMatch: Array [TSynCommentType] of String;
  226. FEolMode: Array [TSynCommentType] of TSynCommentContineMode;
  227. FEolPostfix: Array [TSynCommentType] of String;
  228. FEolSkipLongerLine: Array [TSynCommentType] of Boolean;
  229. FExtendSlashCommentMode: TSynCommentExtendMode;
  230. private
  231. FPasHighlighter: TSynPasSyn;
  232. FCaretAtEOL: Boolean;
  233. FStringBreakAppend: String;
  234. FStringBreakEnabled: Boolean;
  235. FStringBreakPrefix: String;
  236. FWorkLine: Integer; // 1 based
  237. FWorkFoldType: TSynCommentType;
  238. FGetLineAfterComment: Boolean;
  239. FRegExprEngine: TRegExpr;
  240. FCacheFoldEndLvlIdx, FCacheFoldEndLvl: Integer;
  241. FCacheCommentLvlIdx, FCacheCommentLvl: Integer;
  242. FCacheFoldTypeForIdx: Integer;
  243. FCacheFoldType: TPascalCodeFoldBlockType;
  244. FCacheFirstLineForIdx, FCacheFirstLine: Integer;
  245. FCacheSlashColumnForIdx, FCacheSlashColumn: Integer;
  246. FCacheCommentStartColForIdx, FCacheCommentStartCol: Integer;
  247. FCacheLastHlTokenForIdx, FCacheLastHlTokenCol: Integer;
  248. FCacheLastHlTokenKind: TtkTokenKind;
  249. FCacheWorkFoldEndLvl: Integer;
  250. FCacheWorkCommentLvl: Integer;
  251. FCacheWorkFoldType: TPascalCodeFoldBlockType;
  252. FCacheWorkFirstLine: Integer;
  253. FCacheWorkSlashCol: Integer;
  254. FCacheWorkCommentStartCol: Integer;
  255. protected
  256. function IsPasHighlighter: Boolean;
  257. procedure InitCache;
  258. procedure InitPasHighlighter;
  259. // Generic Helpers
  260. function GetFoldEndLevelForIdx(AIndex: Integer): Integer;
  261. function GetFoldCommentLevelForIdx(AIndex: Integer): Integer;
  262. function GetFoldTypeAtEndOfLineForIdx(AIndex: Integer): TPascalCodeFoldBlockType;
  263. function GetFirstCommentLineForIdx(AIndex: Integer): Integer; // Result is 1 based
  264. function GetSlashStartColumnForIdx(AIndex: Integer): Integer;
  265. function GetLastHlTokenForIdx(AIndex: Integer; out APos: Integer): TtkTokenKind;
  266. function GetCommentStartColForIdx(AIndex: Integer): Integer; // Returns Column (logic) for GetFirstCommentLineForIdx(AIndex)
  267. // Values based on FWorkLine
  268. function GetFoldEndLevel: Integer;
  269. function GetFoldCommentLevel: Integer;
  270. function GetFoldTypeAtEndOfLine: TPascalCodeFoldBlockType;
  271. function GetFirstCommentLine: Integer; // Result is 1 based
  272. function GetSlashStartColumn: Integer; // Acts on FWorkLine-1
  273. function GetCommentStartCol: Integer; // Acts on GetFirstCommentLineForIdx(FWorkLine) / Logical Resulc
  274. function GetMatchStartColForIdx(AIndex: Integer): Integer; // Res=1-based
  275. function GetMatchStartPos(AIndex: Integer = -1; AForceFirst: Boolean = False): TPoint; // Res=1-based / AIndex-1 is only used for sclMatchPrev
  276. protected
  277. function GetLine(AnIndex: Integer): String; override;
  278. procedure DoBeforeCommand(const ACaret: TSynEditCaret;
  279. var Command: TSynEditorCommand); override;
  280. procedure DoAfterCommand(const ACaret: TSynEditCaret;
  281. var Command: TSynEditorCommand;
  282. StartLinePos, EndLinePos: Integer); override;
  283. procedure DoNewLineInString(AStringStartY, AStringStartX: Integer;
  284. const ACaret: TSynEditCaret;
  285. var Command: TSynEditorCommand;
  286. StartLinePos, EndLinePos: Integer);
  287. public
  288. constructor Create(AOwner: TComponent); override;
  289. destructor Destroy; override;
  290. procedure Assign(Src: TPersistent); override;
  291. // Retruns a 0-based position (even 0-based physical)
  292. published
  293. // *** coments with (* *)
  294. (* AnsiIndentFirstLineMax:
  295. * For comments that do NOT start at the BOL in their opening line " Foo; ( * comment":
  296. if AnsiIndentFirstLineMax is is set:
  297. - If the comment starts before or at the "Max-Column, then sciAlignOpen is always used.
  298. - If the comment starts after max column, then the specified mode is used.
  299. If the specified mode is sciAlignOpen it will be cut at Max
  300. * For comments that start at BOL in their opening line " ( * comment":
  301. This is ignored
  302. AnsiIndentFirstLineExtra:
  303. For comments that do NOT start at the BOL, an extra indent is added
  304. on the 2nd line (except if sciAlignOpen is used (in Flags, or via A.I.FirstLineMax))
  305. *)
  306. property AnsiIndentMode: TSynCommentIndentFlags read FIndentMode[sctAnsi]
  307. write FIndentMode[sctAnsi];
  308. property AnsiIndentFirstLineMax: Integer read FIndentFirstLineMax[sctAnsi]
  309. write FIndentFirstLineMax[sctAnsi];
  310. property AnsiIndentFirstLineExtra: String read FIndentFirstLineExtra[sctAnsi]
  311. write FIndentFirstLineExtra[sctAnsi];
  312. (* match should start with ^, and leading spaces should not be in () "^\s?(\*\*?\*?)"
  313. prefix can refer to $1 and should have spaces to indent after the match "$1 "
  314. *)
  315. property AnsiCommentMode: TSynCommentContineMode read FCommentMode[sctAnsi]
  316. write FCommentMode[sctAnsi];
  317. property AnsiMatch: String read FMatch[sctAnsi]
  318. write FMatch[sctAnsi];
  319. property AnsiPrefix : String read FPrefix[sctAnsi]
  320. write FPrefix[sctAnsi];
  321. property AnsiMatchMode: TSynCommentMatchMode read FMatchMode[sctAnsi]
  322. write FMatchMode[sctAnsi];
  323. property AnsiMatchLine: TSynCommentMatchLine read FMatchLine[sctAnsi]
  324. write FMatchLine[sctAnsi];
  325. property AnsiCommentIndent: TSynBeautifierIndentType read FCommentIndent[sctAnsi]
  326. write FCommentIndent[sctAnsi];
  327. // Add postfix at EOL
  328. property AnsiEolMode: TSynCommentContineMode read FEolMode[sctAnsi]
  329. write FEolMode[sctAnsi];
  330. property AnsiEolPostfix: String read FEolPostfix[sctAnsi]
  331. write FEolPostfix[sctAnsi];
  332. property AnsiEolMatch: String read FEolMatch[sctAnsi]
  333. write FEolMatch[sctAnsi];
  334. property AnsiEolSkipLongerLine: Boolean read FEolSkipLongerLine[sctAnsi]
  335. write FEolSkipLongerLine[sctAnsi];
  336. // *** coments with { }
  337. property BorIndentMode: TSynCommentIndentFlags read FIndentMode[sctBor]
  338. write FIndentMode[sctBor];
  339. property BorIndentFirstLineMax: Integer read FIndentFirstLineMax[sctBor]
  340. write FIndentFirstLineMax[sctBor];
  341. property BorIndentFirstLineExtra: String read FIndentFirstLineExtra[sctBor]
  342. write FIndentFirstLineExtra[sctBor];
  343. property BorCommentMode: TSynCommentContineMode read FCommentMode[sctBor]
  344. write FCommentMode[sctBor];
  345. property BorMatch: String read FMatch[sctBor]
  346. write FMatch[sctBor];
  347. property BorPrefix : String read FPrefix[sctBor]
  348. write FPrefix[sctBor];
  349. property BorMatchMode: TSynCommentMatchMode read FMatchMode[sctBor]
  350. write FMatchMode[sctBor];
  351. property BorMatchLine: TSynCommentMatchLine read FMatchLine[sctBor]
  352. write FMatchLine[sctBor];
  353. property BorCommentIndent: TSynBeautifierIndentType read FCommentIndent[sctBor]
  354. write FCommentIndent[sctBor];
  355. property BorEolMode: TSynCommentContineMode read FEolMode[sctBor]
  356. write FEolMode[sctBor];
  357. property BorEolPostfix : String read FEolPostfix[sctBor]
  358. write FEolPostfix[sctBor];
  359. property BorEolMatch: String read FEolMatch[sctBor]
  360. write FEolMatch[sctBor];
  361. property BorEolSkipLongerLine: Boolean read FEolSkipLongerLine[sctBor]
  362. write FEolSkipLongerLine[sctBor];
  363. // *** coments with //
  364. // Continue only, if Extended
  365. property ExtendSlashCommentMode: TSynCommentExtendMode read FExtendSlashCommentMode
  366. write FExtendSlashCommentMode;
  367. property SlashIndentMode: TSynCommentIndentFlags read FIndentMode[sctSlash]
  368. write FIndentMode[sctSlash];
  369. property SlashIndentFirstLineMax: Integer read FIndentFirstLineMax[sctSlash]
  370. write FIndentFirstLineMax[sctSlash];
  371. property SlashIndentFirstLineExtra: String read FIndentFirstLineExtra[sctSlash]
  372. write FIndentFirstLineExtra[sctSlash];
  373. property SlashCommentMode: TSynCommentContineMode read FCommentMode[sctSlash]
  374. write FCommentMode[sctSlash];
  375. property SlashMatch: String read FMatch[sctSlash]
  376. write FMatch[sctSlash];
  377. property SlashPrefix : String read FPrefix[sctSlash]
  378. write FPrefix[sctSlash];
  379. property SlashMatchMode: TSynCommentMatchMode read FMatchMode[sctSlash]
  380. write FMatchMode[sctSlash];
  381. property SlashMatchLine: TSynCommentMatchLine read FMatchLine[sctSlash]
  382. write FMatchLine[sctSlash];
  383. property SlashCommentIndent: TSynBeautifierIndentType read FCommentIndent[sctSlash]
  384. write FCommentIndent[sctSlash];
  385. property SlashEolMode: TSynCommentContineMode read FEolMode[sctSlash]
  386. write FEolMode[sctSlash];
  387. property SlashEolPostfix : String read FEolPostfix[sctSlash]
  388. write FEolPostfix[sctSlash];
  389. property SlashEolMatch: String read FEolMatch[sctSlash]
  390. write FEolMatch[sctSlash];
  391. property SlashEolSkipLongerLine: Boolean read FEolSkipLongerLine[sctSlash]
  392. write FEolSkipLongerLine[sctSlash];
  393. // String
  394. property StringBreakEnabled: Boolean read FStringBreakEnabled write FStringBreakEnabled;
  395. property StringBreakAppend: String read FStringBreakAppend write FStringBreakAppend;
  396. property StringBreakPrefix: String read FStringBreakPrefix write FStringBreakPrefix;
  397. end;
  398. function dbgs(ACommentType: TSynCommentType): String; overload;
  399. function dbgs(AContinueMode: TSynCommentContineMode): String; overload;
  400. function dbgs(AMatchLine: TSynCommentMatchLine): String; overload;
  401. function dbgs(AMatchMode: TSynCommentMatchMode): String; overload;
  402. function dbgs(AIndentFlag: TSynCommentIndentFlag): String; overload;
  403. function dbgs(AIndentFlags: TSynCommentIndentFlags): String; overload;
  404. function dbgs(AExtendMode: TSynCommentExtendMode): String; overload;
  405. implementation
  406. uses SynEdit;
  407. function dbgs(ACommentType: TSynCommentType): String;
  408. begin
  409. Result := ''; WriteStr(Result, ACommentType);
  410. end;
  411. function dbgs(AContinueMode: TSynCommentContineMode): String;
  412. begin
  413. Result := ''; WriteStr(Result, AContinueMode);
  414. end;
  415. function dbgs(AMatchLine: TSynCommentMatchLine): String;
  416. begin
  417. Result := ''; WriteStr(Result, AMatchLine);
  418. end;
  419. function dbgs(AMatchMode: TSynCommentMatchMode): String;
  420. begin
  421. Result := ''; WriteStr(Result, AMatchMode);
  422. end;
  423. function dbgs(AIndentFlag: TSynCommentIndentFlag): String;
  424. begin
  425. Result := ''; WriteStr(Result, AIndentFlag);
  426. end;
  427. function dbgs(AIndentFlags: TSynCommentIndentFlags): String;
  428. var
  429. i: TSynCommentIndentFlag;
  430. begin
  431. Result := '';
  432. for i := low(TSynCommentIndentFlag) to high(TSynCommentIndentFlag) do
  433. if i in AIndentFlags then
  434. if Result = ''
  435. then Result := dbgs(i)
  436. else Result := Result + ',' + dbgs(i);
  437. if Result <> '' then
  438. Result := '[' + Result + ']';
  439. end;
  440. function dbgs(AExtendMode: TSynCommentExtendMode): String;
  441. begin
  442. Result := ''; WriteStr(Result, AExtendMode);
  443. end;
  444. { TSynBeautifierPascal }
  445. function TSynBeautifierPascal.IsPasHighlighter: Boolean;
  446. begin
  447. Result := (TSynEdit(FCurrentEditor).Highlighter <> nil) and
  448. (TSynEdit(FCurrentEditor).Highlighter is TSynPasSyn);
  449. end;
  450. procedure TSynBeautifierPascal.InitCache;
  451. begin
  452. FCacheFoldEndLvlIdx := -1;
  453. FCacheCommentLvlIdx := -1;
  454. FCacheFoldTypeForIdx := -1;
  455. FCacheFirstLineForIdx := -1;
  456. FCacheSlashColumnForIdx := -1;
  457. FCacheCommentStartColForIdx := -1;
  458. FCacheWorkFoldEndLvl := -2;
  459. FCacheWorkCommentLvl := -2;
  460. FCacheWorkFoldType := cfbtIfThen; // dummy for not yet cached
  461. FCacheWorkFirstLine := -2;
  462. FCacheWorkSlashCol := -2;
  463. FCacheWorkCommentStartCol := -2;
  464. end;
  465. procedure TSynBeautifierPascal.InitPasHighlighter;
  466. begin
  467. FPasHighlighter := TSynPasSyn(TSynEdit(FCurrentEditor).Highlighter);
  468. FPasHighlighter.CurrentLines := FCurrentLines;
  469. FPasHighlighter.ScanRanges;
  470. end;
  471. function TSynBeautifierPascal.GetFoldEndLevelForIdx(AIndex: Integer): Integer;
  472. begin
  473. Result := FCacheFoldEndLvl;
  474. if AIndex = FCacheFoldEndLvlIdx then
  475. exit;
  476. FCacheFoldEndLvlIdx := AIndex;
  477. FCacheFoldEndLvl := FPasHighlighter.FoldBlockEndLevel(AIndex, FOLDGROUP_PASCAL, [sfbIncludeDisabled]);
  478. Result := FCacheFoldEndLvl;
  479. end;
  480. function TSynBeautifierPascal.GetFoldCommentLevelForIdx(AIndex: Integer): Integer;
  481. var
  482. tmp: Pointer;
  483. Block: TPascalCodeFoldBlockType;
  484. begin
  485. Result := FCacheCommentLvl;
  486. if AIndex = FCacheCommentLvlIdx then
  487. exit;
  488. FCacheCommentLvlIdx := AIndex;
  489. FCacheCommentLvl := GetFoldEndLevelForIdx(AIndex) - 1;
  490. while (FCacheCommentLvl > 0) do begin
  491. FPasHighlighter.FoldBlockNestedTypes(AIndex , FCacheCommentLvl - 1,
  492. tmp, FOLDGROUP_PASCAL, [sfbIncludeDisabled]);
  493. Block:=TPascalCodeFoldBlockType(PtrUInt(tmp));
  494. if not (Block in [cfbtAnsiComment, cfbtBorCommand, cfbtSlashComment]) then
  495. break;
  496. dec(FCacheCommentLvl);
  497. end;
  498. inc(FCacheCommentLvl);
  499. Result := FCacheCommentLvl;
  500. end;
  501. function TSynBeautifierPascal.GetFoldTypeAtEndOfLineForIdx(AIndex: Integer): TPascalCodeFoldBlockType;
  502. var
  503. EndLevel: Integer;
  504. tmp: Pointer;
  505. begin
  506. // TODO cfbtNestedComment
  507. Result := FCacheFoldType;
  508. if AIndex = FCacheFoldTypeForIdx then
  509. exit;
  510. FCacheFoldTypeForIdx := AIndex;
  511. EndLevel := GetFoldEndLevelForIdx(AIndex);
  512. if (EndLevel > 0) then
  513. begin
  514. if FPasHighlighter.FoldBlockNestedTypes(AIndex, EndLevel - 1,
  515. tmp, FOLDGROUP_PASCAL, [sfbIncludeDisabled])
  516. then
  517. FCacheFoldType:=TPascalCodeFoldBlockType(PtrUInt(tmp))
  518. else
  519. FCacheFoldType := cfbtNone;
  520. end;
  521. while (FCacheFoldType = cfbtNestedComment) and (EndLevel > 1) do begin
  522. dec(EndLevel);
  523. if FPasHighlighter.FoldBlockNestedTypes(AIndex, EndLevel - 1,
  524. tmp, FOLDGROUP_PASCAL, [sfbIncludeDisabled])
  525. then
  526. FCacheFoldType:=TPascalCodeFoldBlockType(PtrUInt(tmp))
  527. else
  528. FCacheFoldType := cfbtNone;
  529. end;
  530. Result := FCacheFoldType;
  531. end;
  532. function TSynBeautifierPascal.GetFirstCommentLineForIdx(AIndex: Integer): Integer;
  533. var
  534. FoldType: TPascalCodeFoldBlockType;
  535. ANewIndex, EndLvl, CommentLvl: Integer;
  536. begin
  537. Result := FCacheFirstLine;
  538. if AIndex = FCacheFirstLineForIdx then
  539. exit;
  540. ANewIndex := AIndex;
  541. FoldType := GetFoldTypeAtEndOfLineForIdx(ANewIndex);
  542. EndLvl := GetFoldEndLevelForIdx(ANewIndex);
  543. //if (EndLvl = 0) or not(FoldType in [cfbtAnsiComment, cfbtBorCommand, cfbtSlashComment]) and
  544. // (AIndex > 0) and (GetSlashStartColumnForIdx(AIndex - 1) > 0)
  545. //then begin
  546. // dec(ANewIndex);
  547. // FoldType := GetFoldTypeAtEndOfLineForIdx(ANewIndex);
  548. // EndLvl := GetFoldEndLevelForIdx(ANewIndex);
  549. //end;
  550. if (EndLvl = 0) or not(FoldType in [cfbtAnsiComment, cfbtBorCommand, cfbtSlashComment])
  551. then begin
  552. Result := ToPos(AIndex) - 1; // 1 based - the line above ANewIndex
  553. // maybe the line above has a trailing comment
  554. //if (AIndex <> ANewIndex) and (ANewIndex > 0) and (GetSlashStartColumnForIdx(ANewIndex-1) > 0) then
  555. // Result := ToPos(ANewIndex) - 1;
  556. exit;
  557. end;
  558. FCacheFirstLineForIdx := AIndex;
  559. FCacheFirstLine := ToPos(ANewIndex) - 1;
  560. CommentLvl := GetFoldCommentLevelForIdx(ANewIndex);
  561. while (FCacheFirstLine > 0) do begin
  562. if CommentLvl > FPasHighlighter.FoldBlockMinLevel(FCacheFirstLine-1, FOLDGROUP_PASCAL, [sfbIncludeDisabled]) then
  563. break;
  564. dec(FCacheFirstLine);
  565. end;
  566. if FoldType = cfbtSlashComment then begin
  567. // maybe the line above has a trailing comment
  568. if GetSlashStartColumnForIdx(ToIdx(FCacheFirstLine)) > 0 then
  569. dec(FCacheFirstLine);
  570. end;
  571. Result := FCacheFirstLine;
  572. //debugln(['FIRST LINE ', FCacheFirstLine]);
  573. end;
  574. function TSynBeautifierPascal.GetSlashStartColumnForIdx(AIndex: Integer): Integer;
  575. var
  576. Tk: TtkTokenKind;
  577. begin
  578. Result := FCacheSlashColumn;
  579. if AIndex = FCacheSlashColumnForIdx then
  580. exit;
  581. FCacheSlashColumnForIdx := AIndex;
  582. Tk := GetLastHlTokenForIdx(AIndex, FCacheSlashColumn);
  583. if Tk <> SynHighlighterPas.tkComment then
  584. FCacheSlashColumn := -1;
  585. Result := FCacheSlashColumn;
  586. end;
  587. function TSynBeautifierPascal.GetLastHlTokenForIdx(AIndex: Integer; out
  588. APos: Integer): TtkTokenKind;
  589. begin
  590. Result := FCacheLastHlTokenKind;
  591. APos := FCacheLastHlTokenCol;
  592. if AIndex = FCacheLastHlTokenForIdx then
  593. exit;
  594. FCacheSlashColumnForIdx := AIndex;
  595. FCacheLastHlTokenKind := SynHighlighterPas.tkUnknown;
  596. FCacheLastHlTokenCol := -1;
  597. if (FCurrentLines[AIndex] <> '') then begin
  598. FPasHighlighter.StartAtLineIndex(AIndex);
  599. while not FPasHighlighter.GetEol do begin
  600. FCacheLastHlTokenKind := TtkTokenKind(FPasHighlighter.GetTokenKind);
  601. FCacheLastHlTokenCol := FPasHighlighter.GetTokenPos + 1;
  602. FPasHighlighter.Next;
  603. end;
  604. end;
  605. Result := FCacheLastHlTokenKind;
  606. APos := FCacheLastHlTokenCol;
  607. end;
  608. function TSynBeautifierPascal.GetCommentStartColForIdx(AIndex: Integer): Integer;
  609. var
  610. nl: TLazSynFoldNodeInfoList;
  611. i: Integer;
  612. FoldCommentLvl: Integer;
  613. begin
  614. Result := FCacheCommentStartCol;
  615. if AIndex = FCacheCommentStartColForIdx then
  616. exit;
  617. FCacheCommentStartColForIdx := AIndex;
  618. if (GetFoldEndLevelForIdx(AIndex) = 0) or
  619. not(GetFoldTypeAtEndOfLineForIdx(AIndex) in [cfbtAnsiComment, cfbtBorCommand, cfbtSlashComment])
  620. then begin
  621. // must be SlashComment
  622. FCacheCommentStartCol := GetSlashStartColumnForIdx(AIndex - 1);
  623. //FCacheCommentStartCol := GetSlashStartColumnForIdx(AIndex);
  624. //if FCacheCommentStartCol > 0 then begin
  625. // i := GetSlashStartColumnForIdx(AIndex - 1);
  626. // if i > 0 then
  627. // FCacheCommentStartCol := i;
  628. //end;
  629. Result := FCacheCommentStartCol;
  630. //debugln(['FIRST COL prev-// ', FCacheCommentStartCol]);
  631. exit;
  632. end;
  633. FCacheCommentStartCol := -1;
  634. FoldCommentLvl := GetFoldCommentLevelForIdx(AIndex);
  635. nl := FPasHighlighter.FoldNodeInfo[GetFirstCommentLineForIdx(AIndex) - 1];
  636. nl.AddReference;
  637. nl.ClearFilter;
  638. nl.ActionFilter := [sfaOpen];
  639. nl.GroupFilter := FOLDGROUP_PASCAL;
  640. i := nl.Count - 1;
  641. while i >= 0 do begin
  642. if (not (sfaInvalid in nl[i].FoldAction)) and
  643. (nl[i].NestLvlEnd = FoldCommentLvl)
  644. then begin
  645. FCacheCommentStartCol := nl[i].LogXStart + 1; // Highlighter pos are 0 based
  646. break;
  647. end;
  648. dec(i);
  649. end;
  650. nl.ReleaseReference;
  651. //debugln(['FIRST COL ', FCacheCommentStartCol]);
  652. Result := FCacheCommentStartCol;
  653. end;
  654. function TSynBeautifierPascal.GetFoldEndLevel: Integer;
  655. begin
  656. Result := FCacheWorkFoldEndLvl;
  657. if FCacheWorkFoldEndLvl > -2 then
  658. exit;
  659. FCacheWorkFoldEndLvl := GetFoldEndLevelForIdx(FWorkLine-1);
  660. Result := FCacheWorkFoldEndLvl;
  661. end;
  662. function TSynBeautifierPascal.GetFoldCommentLevel: Integer;
  663. begin
  664. Result := FCacheWorkCommentLvl;
  665. if FCacheWorkCommentLvl > -2 then
  666. exit;
  667. FCacheWorkCommentLvl := GetFoldCommentLevelForIdx(FWorkLine-1);
  668. Result := FCacheWorkCommentLvl;
  669. end;
  670. function TSynBeautifierPascal.GetFoldTypeAtEndOfLine: TPascalCodeFoldBlockType;
  671. begin
  672. Result := FCacheWorkFoldType;
  673. if FCacheWorkFoldType <> cfbtIfThen then
  674. exit;
  675. FCacheWorkFoldType := GetFoldTypeAtEndOfLineForIdx(FWorkLine-1);
  676. Result := FCacheWorkFoldType;
  677. end;
  678. function TSynBeautifierPascal.GetFirstCommentLine: Integer;
  679. begin
  680. Result := FCacheWorkFirstLine;
  681. if FCacheWorkFirstLine > -2 then
  682. exit;
  683. FCacheWorkFirstLine := GetFirstCommentLineForIdx(FWorkLine-1);
  684. Result := FCacheWorkFirstLine;
  685. end;
  686. function TSynBeautifierPascal.GetSlashStartColumn: Integer;
  687. begin
  688. Result := FCacheWorkSlashCol;
  689. if FCacheWorkSlashCol > -2 then
  690. exit;
  691. FCacheWorkSlashCol := GetSlashStartColumnForIdx(FWorkLine-2);
  692. Result := FCacheWorkSlashCol;
  693. end;
  694. function TSynBeautifierPascal.GetCommentStartCol: Integer;
  695. begin
  696. Result := FCacheWorkCommentStartCol;
  697. if FCacheWorkCommentStartCol > -2 then
  698. exit;
  699. FCacheWorkCommentStartCol := GetCommentStartColForIdx(FWorkLine-1);
  700. Result := FCacheWorkCommentStartCol;
  701. end;
  702. function TSynBeautifierPascal.GetMatchStartColForIdx(AIndex: Integer): Integer;
  703. begin
  704. if ToPos(AIndex) = GetFirstCommentLine then begin
  705. // Match on FirstLine
  706. case FMatchMode[FWorkFoldType] of
  707. scmMatchAfterOpening: begin
  708. if FWorkFoldType = sctBor
  709. then Result := GetCommentStartCol + 1
  710. else Result := GetCommentStartCol + 2;
  711. end;
  712. scmMatchOpening: Result := GetCommentStartCol;
  713. scmMatchWholeLine: Result := 1;
  714. scmMatchAtAsterisk: Result := GetCommentStartCol + 1;
  715. end;
  716. end
  717. else begin
  718. // Match on prev, not first
  719. case FMatchMode[FWorkFoldType] of
  720. scmMatchAfterOpening, scmMatchOpening, scmMatchAtAsterisk:
  721. Result := 1 + GetIndentForLine(nil, FCurrentLines[AIndex], False);
  722. scmMatchWholeLine:
  723. Result := 1;
  724. end;
  725. end;
  726. end;
  727. function TSynBeautifierPascal.GetMatchStartPos(AIndex: Integer; AForceFirst: Boolean): TPoint;
  728. begin
  729. if AIndex < 0 then
  730. AIndex := ToIdx(FWorkLine);
  731. if AForceFirst then
  732. Result.Y := GetFirstCommentLine
  733. else
  734. case FMatchLine[FWorkFoldType] of
  735. sclMatchFirst: Result.Y := GetFirstCommentLine;
  736. sclMatchPrev: Result.Y := ToPos(AIndex)-1; // FWorkLine - 1
  737. end;
  738. Result.X := GetMatchStartColForIdx(ToIdx(Result.Y));
  739. end;
  740. function TSynBeautifierPascal.GetLine(AnIndex: Integer): String;
  741. var
  742. ReplacedPrefix: String;
  743. Indent, PreFixPos: Integer;
  744. r: Boolean;
  745. p: TPoint;
  746. begin
  747. if not FGetLineAfterComment then begin
  748. Result := inherited GetLine(AnIndex);
  749. exit;
  750. end;
  751. // Need to cut of existing Prefix to find indent after prefix
  752. if AnIndex < GetFirstCommentLine-1 then begin
  753. Result := '';
  754. //debugln(['GETLINE FROM (< First) ', AnIndex,' ''', FCurrentLines[AnIndex], ''' to empty']);
  755. end;
  756. // 1) Run the match for this line (match prev, or first)
  757. // and see if we can find the replaced prefix in this line
  758. if AnIndex > GetFirstCommentLine-1 then begin
  759. p := GetMatchStartPos(AnIndex);
  760. FRegExprEngine.InputString:= copy(FCurrentLines[ToIdx(p.y)], p.x, MaxInt);
  761. FRegExprEngine.Expression := FMatch[FWorkFoldType];
  762. if FRegExprEngine.ExecPos(1) then begin
  763. ReplacedPrefix := FRegExprEngine.Substitute(FPrefix[FWorkFoldType]);
  764. while (ReplacedPrefix <> '') and (ReplacedPrefix[1] in [#9, ' ']) do
  765. delete(ReplacedPrefix, 1, 1);
  766. while (ReplacedPrefix <> '') and (ReplacedPrefix[length(ReplacedPrefix)] in [#9, ' ']) do
  767. delete(ReplacedPrefix, length(ReplacedPrefix), 1);
  768. if ReplacedPrefix <> '' then begin
  769. Result := FCurrentLines[AnIndex];
  770. PreFixPos := pos(ReplacedPrefix, Result);
  771. if (PreFixPos > 0) and (PreFixPos <= GetMatchStartColForIdx(AnIndex)) then begin
  772. delete(Result, 1, PreFixPos - 1 + Length(ReplacedPrefix));
  773. //debugln(['GETLINE FROM (1) ', AnIndex,' ''', FCurrentLines[AnIndex], ''' to ''', Result, '''']);
  774. exit;
  775. end;
  776. end;
  777. end;
  778. end;
  779. // 2) See, if the current-1 line can be matched
  780. r := False;
  781. Result := FCurrentLines[AnIndex];
  782. Indent := GetMatchStartColForIdx(AnIndex);
  783. FRegExprEngine.InputString:= copy(FCurrentLines[AnIndex], Indent, MaxInt);
  784. FRegExprEngine.Expression := FMatch[FWorkFoldType];
  785. r := FRegExprEngine.ExecPos(1);
  786. if (not r) and (Indent > 1) and
  787. ((p.y <> GetFirstCommentLine) or (FMatchMode[FWorkFoldType] = scmMatchWholeLine))
  788. then begin
  789. // try whole line
  790. // TODO: only if not first, or if setting
  791. FRegExprEngine.InputString := FCurrentLines[AnIndex];
  792. r := FRegExprEngine.ExecPos(1);
  793. if r then
  794. Indent := 1;
  795. end;
  796. if (r) then begin
  797. Indent := Indent + FRegExprEngine.MatchPos[0] - 1 + FRegExprEngine.MatchLen[0];
  798. Result := FCurrentLines[AnIndex];
  799. if (Indent <= Length(Result)) then
  800. while (Indent > 1) and (Result[Indent] in [#9, ' ']) do
  801. dec(Indent);
  802. inc(Indent);
  803. if Indent > 0 then begin
  804. Result := copy(Result, Indent, MaxInt);
  805. //debugln(['GETLINE FROM (2) ', AnIndex,' ''', FCurrentLines[AnIndex], ''' to ''', Result, '''']);
  806. exit;
  807. end;
  808. end;
  809. // 3) maybe try currest replace, if different from 1?
  810. // Nothing found
  811. Result := '';
  812. //debugln(['GETLINE FROM (X) ', AnIndex,' ''', FCurrentLines[AnIndex], ''' to empty']);
  813. end;
  814. procedure TSynBeautifierPascal.DoBeforeCommand(const ACaret: TSynEditCaret;
  815. var Command: TSynEditorCommand);
  816. begin
  817. FCaretAtEOL := ACaret.BytePos > Length(FCurrentLines[ToIdx(ACaret.LinePos)]);
  818. FGetLineAfterComment := False;
  819. inherited DoBeforeCommand(ACaret, Command);
  820. end;
  821. procedure TSynBeautifierPascal.DoAfterCommand(const ACaret: TSynEditCaret;
  822. var Command: TSynEditorCommand; StartLinePos, EndLinePos: Integer);
  823. var
  824. WorkLine, PrevLineShlasCol: Integer;
  825. ReplacedPrefix: String; // Each run matches only one Type
  826. MatchResultIntern, MatchedBOLIntern: Array [Boolean] of Boolean; // Each run matches only one Type
  827. function CheckMatch(AType: TSynCommentType; AFailOnNoPattern: Boolean = False;
  828. AForceFirst: Boolean = False): Boolean;
  829. var
  830. p: TPoint;
  831. begin
  832. if (FMatch[AType] = '') and AFailOnNoPattern then begin
  833. Result := False;
  834. exit;
  835. end;
  836. if MatchedBOLIntern[AForceFirst] then begin
  837. Result := MatchResultIntern[AForceFirst];
  838. exit;
  839. end;
  840. p := GetMatchStartPos(-1, AForceFirst);
  841. FRegExprEngine.InputString:= copy(FCurrentLines[ToIdx(p.y)], p.x, MaxInt);
  842. FRegExprEngine.Expression := FMatch[AType];
  843. if not FRegExprEngine.ExecPos(1) then begin
  844. ReplacedPrefix := FRegExprEngine.Substitute(FPrefix[AType]);
  845. MatchedBOLIntern[AForceFirst] := True;
  846. MatchResultIntern[AForceFirst] := False;
  847. Result := MatchResultIntern[AForceFirst];
  848. exit;
  849. end;
  850. ReplacedPrefix := FRegExprEngine.Substitute(FPrefix[AType]);
  851. MatchedBOLIntern[AForceFirst] := True;
  852. MatchResultIntern[AForceFirst] := True;
  853. Result := MatchResultIntern[AForceFirst];
  854. end;
  855. function IsFoldTypeEnabled(AType: TSynCommentType): Boolean;
  856. begin
  857. Result := ( ( (AType <> sctSlash) or
  858. ( ((not FCaretAtEOL) and (FExtendSlashCommentMode <> sceNever)) or
  859. ((FCaretAtEOL) and not(FExtendSlashCommentMode in [sceNever, sceSplitLine, sceMatchingSplitLine]))
  860. )
  861. ) and
  862. ( (FIndentMode[AType] <> []) or
  863. (FCommentMode[AType] <> sccNoPrefix) or
  864. (FEolMode[AType] <> sccNoPrefix)
  865. ))
  866. end;
  867. var
  868. Indent, dummy: Integer;
  869. s: String;
  870. FoldTyp: TSynCommentType;
  871. AnyEnabled: Boolean;
  872. ExtendSlash, BeSmart, Matching: Boolean;
  873. PreviousIsFirst, IsAtBOL, DidAlignOpen: Boolean;
  874. IndentTypeBackup: TSynBeautifierIndentType;
  875. begin
  876. if (EndLinePos < 1) or
  877. ((Command <> ecLineBreak) and (Command <> ecInsertLine)) or
  878. (not IsPasHighlighter)
  879. then begin
  880. inherited DoAfterCommand(ACaret, Command, StartLinePos, EndLinePos);
  881. exit;
  882. end;
  883. AnyEnabled := False;
  884. for FoldTyp := low(TSynCommentType) to high(TSynCommentType) do
  885. AnyEnabled := AnyEnabled or IsFoldTypeEnabled(FoldTyp);
  886. if (not AnyEnabled) and (not FStringBreakEnabled) then begin
  887. inherited DoAfterCommand(ACaret, Command, StartLinePos, EndLinePos);
  888. exit;
  889. end;
  890. InitCache;
  891. InitPasHighlighter;
  892. FGetLineAfterComment := False;
  893. MatchedBOLIntern[True] := False;
  894. MatchedBOLIntern[False] := False;
  895. PrevLineShlasCol := -1;
  896. dummy := 0;
  897. if (Command = ecLineBreak)
  898. then WorkLine := ACaret.LinePos
  899. else WorkLine := ACaret.LinePos + 1;
  900. FWorkLine := WorkLine;
  901. // Find Foldtype
  902. case GetFoldTypeAtEndOfLine of
  903. cfbtAnsiComment: FoldTyp := sctAnsi;
  904. cfbtBorCommand: FoldTyp := sctBor;
  905. cfbtSlashComment: FoldTyp := sctSlash;
  906. else
  907. begin
  908. if (FCurrentLines[ToIdx(WorkLine)-1] <> '') and
  909. (FExtendSlashCommentMode <> sceNever) and
  910. ( (not FCaretAtEOL) or not(FExtendSlashCommentMode in [sceSplitLine, sceMatchingSplitLine]) )
  911. then begin
  912. PrevLineShlasCol := GetSlashStartColumn;
  913. if PrevLineShlasCol > 0 then
  914. FoldTyp := sctSlash;
  915. end;
  916. if PrevLineShlasCol < 0 then begin
  917. if (FCurrentLines[ToIdx(WorkLine)-1] <> '') and
  918. (GetLastHlTokenForIdx(ToIdx(WorkLine)-1, dummy) = SynHighlighterPas.tkString)
  919. then
  920. DoNewLineInString(WorkLine - 1, dummy, ACaret, Command, StartLinePos, EndLinePos)
  921. else
  922. inherited DoAfterCommand(ACaret, Command, StartLinePos, EndLinePos);
  923. exit;
  924. end;
  925. end;
  926. end;
  927. if not IsFoldTypeEnabled(FoldTyp) then begin
  928. inherited DoAfterCommand(ACaret, Command, StartLinePos, EndLinePos);
  929. exit;
  930. end;
  931. FWorkFoldType := FoldTyp;
  932. // Check if we need extend
  933. ExtendSlash := False;
  934. if (FoldTyp = sctSlash) and (ACaret.OldLineBytePos.x > GetSlashStartColumn+2) then begin
  935. // Check if extension is needed
  936. case FExtendSlashCommentMode of
  937. sceAlways: ExtendSlash := True;
  938. sceSplitLine: ExtendSlash := not FCaretAtEOL;
  939. sceMatching: ExtendSlash := CheckMatch(FoldTyp);
  940. sceMatchingSplitLine: ExtendSlash := CheckMatch(FoldTyp) and (not FCaretAtEOL);
  941. end;
  942. if not ExtendSlash then begin
  943. inherited DoAfterCommand(ACaret, Command, StartLinePos, EndLinePos);
  944. exit;
  945. end;
  946. end;
  947. // Indent
  948. Matching := CheckMatch(FoldTyp);
  949. PreviousIsFirst := (GetFirstCommentLine = WorkLine -1);
  950. DidAlignOpen := False;
  951. IsAtBOL := True;
  952. if PreviousIsFirst then
  953. IsAtBOL := GetCommentStartCol = 1 + GetIndentForLine(nil, FCurrentLines[ToIdx(GetFirstCommentLine)], False);
  954. // Aply indent before prefix
  955. if Matching or (FCommentMode[FoldTyp] = sccPrefixAlways) or (sciApplyIndentForNoMatch in FIndentMode[FoldTyp])
  956. then begin
  957. IndentTypeBackup := IndentType;
  958. try
  959. if IndentType = sbitPositionCaret then
  960. IndentType := sbitSpace;
  961. if IndentType = sbitConvertToTabOnly then
  962. IndentType := sbitConvertToTabSpace;
  963. if PreviousIsFirst and not IsAtBOL and
  964. (FIndentFirstLineMax[FoldTyp] > 0) and
  965. ( (FIndentFirstLineMax[FoldTyp] + 1 >= GetCommentStartCol) or
  966. (FIndentMode[FoldTyp] * [sciNone, sciAlignOpen] = [sciAlignOpen])
  967. )
  968. then begin
  969. // Use sciAlignOpen
  970. Indent := Min(
  971. FCurrentLines.LogicalToPhysicalCol(FCurrentLines[ToIdx(GetFirstCommentLine)], ToIdx(GetFirstCommentLine), GetCommentStartCol-1),
  972. FIndentFirstLineMax[FoldTyp]);
  973. s := GetCharMix(WorkLine, Indent, dummy);
  974. FLogicalIndentLen := length(s);
  975. FCurrentLines.EditInsert(1, WorkLine, s);
  976. DidAlignOpen := True;
  977. end
  978. else
  979. if (FIndentMode[FoldTyp] * [sciNone, sciAlignOpen] = [sciAlignOpen]) and
  980. (GetCommentStartCol > 0)
  981. then begin
  982. Indent := FCurrentLines.LogicalToPhysicalCol(FCurrentLines[ToIdx(GetFirstCommentLine)], ToIdx(GetFirstCommentLine), GetCommentStartCol-1);
  983. if FIndentFirstLineMax[FoldTyp] > 0
  984. then Indent := Min(Indent, FIndentFirstLineMax[FoldTyp]);
  985. s := GetCharMix(WorkLine, Indent, dummy);
  986. FLogicalIndentLen := length(s);
  987. FCurrentLines.EditInsert(1, WorkLine, s);
  988. DidAlignOpen := True;
  989. end
  990. else
  991. if (sciNone in FIndentMode[FoldTyp]) then begin
  992. // No indent
  993. end
  994. else
  995. begin
  996. inherited DoAfterCommand(ACaret, Command, StartLinePos, EndLinePos);
  997. end;
  998. finally
  999. IndentType := IndentTypeBackup;
  1000. end;
  1001. // AnsiIndentFirstLineExtra
  1002. if PreviousIsFirst and (not IsAtBOL) and (not DidAlignOpen) then begin
  1003. FCurrentLines.EditInsert(1 + FLogicalIndentLen, WorkLine, FIndentFirstLineExtra[FoldTyp]);
  1004. FLogicalIndentLen := FLogicalIndentLen + length(FIndentFirstLineExtra[FoldTyp]);
  1005. end;
  1006. end
  1007. else
  1008. inherited DoAfterCommand(ACaret, Command, StartLinePos, EndLinePos);
  1009. Indent := 0; // Extra Indent
  1010. BeSmart := (PreviousIsFirst or (sciAlignOpen in FIndentMode[FoldTyp])) and
  1011. (Matching or ExtendSlash or (sciApplyIndentForNoMatch in FIndentMode[FoldTyp]) or
  1012. (FCommentMode[FoldTyp] = sccPrefixAlways) );
  1013. // sciAddTokenLen -- Spaces for (* or {
  1014. if BeSmart and
  1015. ( (sciAddTokenLen in FIndentMode[FoldTyp]) and
  1016. ( (not(sciMatchOnlyTokenLen in FIndentMode[FoldTyp])) or CheckMatch(FoldTyp, False, True) ) and
  1017. ( (not(sciAlignOnlyTokenLen in FIndentMode[FoldTyp])) or DidAlignOpen )
  1018. )
  1019. then begin
  1020. case FoldTyp of
  1021. sctAnsi: if FMatchMode[FoldTyp] = scmMatchAtAsterisk
  1022. then Indent := 1
  1023. else Indent := 2;
  1024. sctBor: Indent := 1;
  1025. sctSlash: if ExtendSlash
  1026. then Indent := 0 // do the slashes
  1027. else Indent := 2;
  1028. end;
  1029. end;
  1030. // sciAddPastTokenIndent -- Spaces from after (* or { (to go befare prefix e.g " { * foo")
  1031. if BeSmart and
  1032. ( (sciAddPastTokenIndent in FIndentMode[FoldTyp]) and
  1033. ( (not(sciMatchOnlyPastTokenIndent in FIndentMode[FoldTyp])) or CheckMatch(FoldTyp, False, True) ) and
  1034. ( (not(sciAlignOnlyPastTokenIndent in FIndentMode[FoldTyp])) or DidAlignOpen )
  1035. ) and
  1036. (GetCommentStartCol > 0) // foundStartCol
  1037. then begin
  1038. case FoldTyp of
  1039. // ignores scmMatchAtAsterisk
  1040. sctAnsi: s := copy(FCurrentLines[ToIdx(GetFirstCommentLine)], GetCommentStartCol+2, MaxInt);
  1041. sctBor: s := copy(FCurrentLines[ToIdx(GetFirstCommentLine)], GetCommentStartCol+1, MaxInt);
  1042. sctSlash: s := copy(FCurrentLines[ToIdx(GetFirstCommentLine)], GetCommentStartCol+2, MaxInt);
  1043. end;
  1044. Indent := Indent + GetIndentForLine(nil, s, False);
  1045. end;
  1046. // Extend //
  1047. if ExtendSlash then begin
  1048. FCurrentLines.EditInsert(1 + FLogicalIndentLen, WorkLine, '//');
  1049. FLogicalIndentLen := FLogicalIndentLen + 2;
  1050. end;
  1051. if (Indent > 0) then begin
  1052. FCurrentLines.EditInsert(1 + FLogicalIndentLen, WorkLine, StringOfChar(' ', Indent ));
  1053. FLogicalIndentLen := FLogicalIndentLen + Indent;
  1054. end;
  1055. // Apply prefix
  1056. if (FCommentMode[FoldTyp] = sccPrefixAlways) or
  1057. ((FCommentMode[FoldTyp] = sccPrefixMatch) and Matching)
  1058. then begin
  1059. FCurrentLines.EditInsert(1 + FLogicalIndentLen, WorkLine, ReplacedPrefix);
  1060. FLogicalIndentLen := FLogicalIndentLen + length(ReplacedPrefix);
  1061. // Post prefix indent
  1062. FGetLineAfterComment := True;
  1063. try
  1064. GetIndentInfo(WorkLine, s, Indent, GetFirstCommentLine);
  1065. if s <> '' then begin
  1066. FCurrentLines.EditInsert(1 + FLogicalIndentLen, WorkLine, s);
  1067. FLogicalIndentLen := FLogicalIndentLen + length(s); // logical (Indent is phisical)
  1068. end
  1069. else
  1070. FLogicalIndentLen := FLogicalIndentLen + Indent; // maybe position caret
  1071. finally
  1072. FGetLineAfterComment := False;
  1073. end;
  1074. end;
  1075. if (Command = ecLineBreak) then begin
  1076. ACaret.IncForcePastEOL;
  1077. ACaret.BytePos := 1 + FLogicalIndentLen;
  1078. ACaret.DecForcePastEOL;
  1079. end;
  1080. end;
  1081. procedure TSynBeautifierPascal.DoNewLineInString(AStringStartY, AStringStartX: Integer;
  1082. const ACaret: TSynEditCaret; var Command: TSynEditorCommand; StartLinePos,
  1083. EndLinePos: Integer);
  1084. var
  1085. s: String;
  1086. WorkLine: Integer;
  1087. f: Boolean;
  1088. begin
  1089. inherited DoAfterCommand(ACaret, Command, StartLinePos, EndLinePos);
  1090. if not FStringBreakEnabled then
  1091. exit;
  1092. if (Command = ecLineBreak)
  1093. then WorkLine := ACaret.LinePos - 1
  1094. else WorkLine := ACaret.LinePos;
  1095. s := FCurrentLines[ToIdx(WorkLine)];
  1096. if (AStringStartX < 1) or (AStringStartX > Length(s)) or (s[AStringStartX] <> '''') then
  1097. exit;
  1098. f := False;
  1099. while AStringStartX <= length(s) do begin
  1100. if (s[AStringStartX] = '''') then f := not f;
  1101. inc(AStringStartX);
  1102. end;
  1103. if not f then exit;
  1104. ACaret.IncForcePastEOL;
  1105. FCurrentLines.EditInsert(1 + length(s), WorkLine, '''' + FStringBreakAppend);
  1106. //if Command = ecInsertLine then
  1107. // ACaret.BytePos := ACaret.BytePos + Length(FStringBreakAppend) + 1;
  1108. FCurrentLines.EditInsert(1 + FLogicalIndentLen, WorkLine + 1, FStringBreakPrefix + '''');
  1109. if (Command = ecLineBreak) then
  1110. ACaret.BytePos := ACaret.BytePos + Length(FStringBreakPrefix) + 1;
  1111. ACaret.DecForcePastEOL;
  1112. end;
  1113. constructor TSynBeautifierPascal.Create(AOwner: TComponent);
  1114. begin
  1115. inherited Create(AOwner);
  1116. FRegExprEngine := TRegExpr.Create;
  1117. FRegExprEngine.ModifierI := True;
  1118. FIndentMode[sctAnsi] := [sciAddTokenLen, sciAddPastTokenIndent];
  1119. FIndentFirstLineExtra[sctAnsi] := '';
  1120. FIndentFirstLineMax[sctAnsi] := 0;
  1121. FCommentMode[sctAnsi] := sccPrefixMatch;
  1122. FMatch[sctAnsi] := '^ ?(\*)';
  1123. FMatchMode[sctAnsi] := scmMatchAfterOpening;
  1124. FMatchLine[sctAnsi] := sclMatchPrev;
  1125. FPrefix[sctAnsi] := '$1';
  1126. FEolMatch[sctAnsi] := '';
  1127. FEolMode[sctAnsi] := sccNoPrefix;
  1128. FEolPostfix[sctAnsi] := '';
  1129. FEolSkipLongerLine[sctAnsi] := False;
  1130. FIndentMode[sctBor] := [sciAddTokenLen, sciAddPastTokenIndent];
  1131. FIndentFirstLineExtra[sctBor] := '';
  1132. FIndentFirstLineMax[sctBor] := 0;
  1133. FCommentMode[sctBor] := sccPrefixMatch;
  1134. FMatch[sctBor] := '^ ?(\*)';
  1135. FMatchMode[sctBor] := scmMatchAfterOpening;
  1136. FMatchLine[sctBor] := sclMatchPrev;
  1137. FPrefix[sctBor] := '$1';
  1138. FEolMatch[sctBor] := '';
  1139. FEolMode[sctBor] := sccNoPrefix;
  1140. FEolPostfix[sctBor] := '';
  1141. FEolSkipLongerLine[sctBor] := False;
  1142. FExtendSlashCommentMode := sceNever;
  1143. FIndentMode[sctSlash] := [];
  1144. FIndentFirstLineExtra[sctSlash] := '';
  1145. FIndentFirstLineMax[sctSlash] := 0;
  1146. FCommentMode[sctSlash] := sccPrefixMatch;
  1147. FMatch[sctSlash] := '^ ?(\*)';
  1148. FMatchMode[sctSlash] := scmMatchAfterOpening;
  1149. FMatchLine[sctSlash] := sclMatchPrev;
  1150. FPrefix[sctSlash] := '$1';
  1151. FEolMatch[sctSlash] := '';
  1152. FEolMode[sctSlash] := sccNoPrefix;
  1153. FEolPostfix[sctSlash] := '';
  1154. FEolSkipLongerLine[sctSlash] := False;
  1155. FStringBreakEnabled := False;
  1156. FStringBreakAppend := ' +';
  1157. FStringBreakPrefix := '';
  1158. end;
  1159. destructor TSynBeautifierPascal.Destroy;
  1160. begin
  1161. inherited Destroy;
  1162. FreeAndNil(FRegExprEngine);
  1163. end;
  1164. procedure TSynBeautifierPascal.Assign(Src: TPersistent);
  1165. var
  1166. i: TSynCommentType;
  1167. begin
  1168. inherited Assign(Src);
  1169. if not(Src is TSynBeautifierPascal) then exit;
  1170. FExtendSlashCommentMode := TSynBeautifierPascal(Src).FExtendSlashCommentMode;
  1171. for i := low(TSynCommentType) to high(TSynCommentType) do begin
  1172. FIndentMode[i] := TSynBeautifierPascal(Src).FIndentMode[i];
  1173. FIndentFirstLineExtra[i] := TSynBeautifierPascal(Src).FIndentFirstLineExtra[i];
  1174. FIndentFirstLineMax[i] := TSynBeautifierPascal(Src).FIndentFirstLineMax[i];
  1175. FCommentMode[i] := TSynBeautifierPascal(Src).FCommentMode[i];
  1176. FMatch[i] := TSynBeautifierPascal(Src).FMatch[i];
  1177. FMatchMode[i] := TSynBeautifierPascal(Src).FMatchMode[i];
  1178. FMatchLine[i] := TSynBeautifierPascal(Src).FMatchLine[i];
  1179. FPrefix[i] := TSynBeautifierPascal(Src).FPrefix[i];
  1180. FEolMatch[i] := TSynBeautifierPascal(Src).FEolMatch[i];
  1181. FEolMode[i] := TSynBeautifierPascal(Src).FEolMode[i];
  1182. FEolPostfix[i] := TSynBeautifierPascal(Src).FEolPostfix[i];
  1183. FEolSkipLongerLine[i] := TSynBeautifierPascal(Src).FEolSkipLongerLine[i];
  1184. end;
  1185. FStringBreakAppend := TSynBeautifierPascal(Src).FStringBreakAppend;
  1186. FStringBreakEnabled := TSynBeautifierPascal(Src).FStringBreakEnabled;
  1187. FStringBreakPrefix := TSynBeautifierPascal(Src).FStringBreakPrefix;
  1188. end;
  1189. { TSynCustomBeautifier }
  1190. procedure TSynCustomBeautifier.Assign(Src: TPersistent);
  1191. begin
  1192. if assigned(Src) and (src is TSynCustomBeautifier) then begin
  1193. FCurrentEditor := TSynCustomBeautifier(Src).FCurrentEditor;
  1194. FCurrentLines := TSynCustomBeautifier(Src).FCurrentLines;
  1195. FOnGetDesiredIndent := TSynCustomBeautifier(Src).FOnGetDesiredIndent;
  1196. FAutoIndent := TSynCustomBeautifier(Src).FAutoIndent;
  1197. end
  1198. else
  1199. inherited;
  1200. end;
  1201. function TSynCustomBeautifier.GetCopy: TSynCustomBeautifier;
  1202. begin
  1203. // Since all synedits share one beautifier, create a temp instance.
  1204. // Todo: have individual beautifiers
  1205. Result := TSynCustomBeautifierClass(self.ClassType).Create(nil);
  1206. Result.assign(self);
  1207. end;
  1208. procedure TSynCustomBeautifier.BeforeCommand(const Editor: TSynEditBase;
  1209. const Lines: TSynEditStrings; const ACaret: TSynEditCaret;
  1210. var Command: TSynEditorCommand; InitialCmd: TSynEditorCommand);
  1211. begin
  1212. // Must be called on GetCopy
  1213. // Todo: have individual beautifiers
  1214. FCurrentEditor := Editor;
  1215. FCurrentLines := Lines;
  1216. DoBeforeCommand(ACaret, Command);
  1217. end;
  1218. procedure TSynCustomBeautifier.AfterCommand(const Editor: TSynEditBase;
  1219. const Lines: TSynEditStrings; const ACaret: TSynEditCaret;
  1220. var Command: TSynEditorCommand; InitialCmd: TSynEditorCommand;
  1221. StartLinePos, EndLinePos: Integer);
  1222. begin
  1223. // Must be called on GetCopy
  1224. // Todo: have individual beautifiers
  1225. FCurrentEditor := Editor;
  1226. FCurrentLines := Lines;
  1227. DoAfterCommand(ACaret, Command, StartLinePos, EndLinePos);
  1228. end;
  1229. { TSynBeautifier }
  1230. procedure TSynBeautifier.Assign(Src: TPersistent);
  1231. begin
  1232. inherited Assign(Src);
  1233. if assigned(Src) and (src is TSynBeautifier) then begin
  1234. FIndentType := TSynBeautifier(Src).FIndentType;
  1235. FCurrentEditor := TSynBeautifier(Src).FCurrentEditor;
  1236. FCurrentLines := TSynBeautifier(Src).FCurrentLines;
  1237. end;
  1238. end;
  1239. function TSynBeautifier.GetLine(AnIndex: Integer): String;
  1240. begin
  1241. Result := FCurrentLines[AnIndex];
  1242. end;
  1243. procedure TSynBeautifier.GetIndentInfo(const LinePos: Integer; out ACharMix: String; out
  1244. AnIndent: Integer; AStopScanAtLine: Integer);
  1245. var
  1246. b: Integer;
  1247. begin
  1248. ACharMix := '';
  1249. if (GetLine(LinePos-2) = '') and (GetLine(LinePos-1) <> '') then
  1250. AnIndent := 0
  1251. else
  1252. AnIndent := GetIndent(LinePos, b, AStopScanAtLine);
  1253. if AnIndent > 0 then begin
  1254. ACharMix := GetCharMix(LinePos, AnIndent, b, True);
  1255. if (FIndentType = sbitPositionCaret) and (GetLine(LinePos-1) = '') then
  1256. ACharMix := '';
  1257. end;
  1258. end;
  1259. procedure TSynBeautifier.DoBeforeCommand(const ACaret: TSynEditCaret;
  1260. var Command: TSynEditorCommand);
  1261. var
  1262. x: Integer;
  1263. begin
  1264. if (Command = ecDeleteLastChar) and
  1265. (FAutoIndent) and
  1266. (ACaret.CharPos > 1) and
  1267. (not TCustomSynEdit(FCurrentEditor).ReadOnly) and
  1268. ( (not TCustomSynEdit(FCurrentEditor).SelAvail) or
  1269. (eoPersistentBlock in TCustomSynEdit(FCurrentEditor).Options2) ) and
  1270. (GetIndentForLine(FCurrentEditor, ACaret.LineText, True) = ACaret.CharPos - 1)
  1271. then begin
  1272. FCurrentLines.UndoList.CurrentReason := ecSmartUnindent;
  1273. UnIndentLine(ACaret, x);
  1274. ACaret.CharPos := x;
  1275. Command := ecNone;
  1276. end;
  1277. end;
  1278. procedure TSynBeautifier.DoAfterCommand(const ACaret: TSynEditCaret;
  1279. var Command: TSynEditorCommand; StartLinePos, EndLinePos: Integer);
  1280. var
  1281. y, Indent: Integer;
  1282. s: String;
  1283. begin
  1284. FLogicalIndentLen := 0;
  1285. if EndLinePos < 1 then
  1286. exit;
  1287. if assigned(FOnGetDesiredIndent) and
  1288. FOnGetDesiredIndent(self, FCurrentEditor, ACaret.LineBytePos,
  1289. ACaret.OldLineBytePos, StartLinePos, EndLinePos, Command,
  1290. @ApplyIndent)
  1291. then
  1292. exit;
  1293. if ((Command = ecLineBreak) or (Command = ecInsertLine)) and FAutoIndent then begin
  1294. if (Command = ecLineBreak) then
  1295. y := ACaret.LinePos
  1296. else
  1297. y := ACaret.LinePos + 1;
  1298. GetIndentInfo(y, s, Indent);
  1299. if s <> '' then begin;
  1300. FCurrentLines.EditInsert(1, y, s);
  1301. FLogicalIndentLen := length(s);
  1302. end;
  1303. if (Command = ecLineBreak) then begin
  1304. ACaret.IncForcePastEOL;
  1305. ACaret.CharPos := Indent + 1;
  1306. ACaret.DecForcePastEOL;
  1307. end;
  1308. end;
  1309. end;
  1310. function TSynBeautifier.UnIndentLine(const ACaret: TSynEditCaret;
  1311. out CaretNewX: Integer): Boolean;
  1312. var
  1313. SpaceCount1, SpaceCount2: Integer;
  1314. BackCounter, LogSpacePos, FillSpace: Integer;
  1315. LogCaret: TPoint;
  1316. Line, Temp: String;
  1317. begin
  1318. Line := ACaret.LineText;
  1319. SpaceCount1 := GetIndentForLine(FCurrentEditor, Line, true); // physical, desired pos
  1320. SpaceCount2 := 0;
  1321. if (SpaceCount1 > 0) then begin
  1322. BackCounter := ACaret.LinePos - 2;
  1323. while BackCounter >= 0 do begin
  1324. Temp := FCurrentLines[BackCounter];
  1325. SpaceCount2 := GetIndentForLine(FCurrentEditor, Temp, true);
  1326. if (SpaceCount2 < SpaceCount1) and (temp <> '') then
  1327. break;
  1328. Dec(BackCounter);
  1329. end;
  1330. end;
  1331. if SpaceCount2 >= SpaceCount1 then
  1332. SpaceCount2 := 0;
  1333. // remove visible spaces
  1334. LogSpacePos := FCurrentLines.PhysicalToLogicalCol(Line, ACaret.LinePos-1, SpaceCount2 + 1);
  1335. FillSpace := SpaceCount2 + 1 - FCurrentLines.LogicalToPhysicalCol(Line, ACaret.LinePos-1, LogSpacePos);
  1336. LogCaret := ACaret.LineBytePos;
  1337. CaretNewX := SpaceCount2 + 1;
  1338. FCurrentLines.EditDelete(LogSpacePos, ACaret.LinePos, LogCaret.X - LogSpacePos);
  1339. if FillSpace > 0 then
  1340. FCurrentLines.EditInsert(LogSpacePos, ACaret.LinePos, StringOfChar(' ', FillSpace));
  1341. Result := True;
  1342. end;
  1343. function TSynBeautifier.GetIndent(const LinePos: Integer; out BasedOnLine: Integer;
  1344. AStopScanAtLine: Integer): Integer;
  1345. var
  1346. Temp: string;
  1347. begin
  1348. if AStopScanAtLine > 0 then
  1349. dec(AStopScanAtLine); // Convert to index
  1350. BasedOnLine := LinePos - 1; // Convert to index
  1351. while (BasedOnLine > AStopScanAtLine) do begin
  1352. dec(BasedOnLine);
  1353. Temp := GetLine(BasedOnLine);
  1354. if Temp <> '' then begin
  1355. Result := GetIndentForLine(FCurrentEditor, Temp, True);
  1356. exit;
  1357. end;
  1358. end;
  1359. Result := 0;
  1360. //BasedOnLine := LinePos;
  1361. //Result := GetIndentForLine(FCurrentEditor, GetLine(BasedOnLine), True);
  1362. end;
  1363. function TSynBeautifier.AdjustCharMix(DesiredIndent: Integer; CharMix, AppendMix: String): String;
  1364. var
  1365. i: Integer;
  1366. CurLen: Integer;
  1367. begin
  1368. CurLen := FCurrentLines.LogicalToPhysicalCol(CharMix, -1, length(CharMix)+1) - 1; // TODO: Need the real index of the line
  1369. if AppendMix <> '' then begin
  1370. while CurLen < DesiredIndent do begin
  1371. CharMix := CharMix + AppendMix;
  1372. CurLen := FCurrentLines.LogicalToPhysicalCol(CharMix, -1, length(CharMix)+1) - 1; // TODO: Need the real index of the line
  1373. end
  1374. end;
  1375. i := length(CharMix);
  1376. while CurLen > DesiredIndent do begin
  1377. Dec(i);
  1378. CurLen := FCurrentLines.LogicalToPhysicalCol(CharMix, -1, i+1) - 1; // TODO: Need the real index of the line
  1379. end;
  1380. CharMix := copy(CharMix, 1, i) + StringOfChar(' ', DesiredIndent - CurLen);
  1381. Result := CharMix;
  1382. end;
  1383. function TSynBeautifier.CreateTabSpaceMix(var DesiredIndent: Integer;
  1384. OnlyTabs: Boolean): String;
  1385. var
  1386. CurLen: Integer;
  1387. begin
  1388. CurLen := 0;
  1389. Result := '';
  1390. while CurLen < DesiredIndent do begin
  1391. Result := Result + #9;
  1392. CurLen := FCurrentLines.LogicalToPhysicalCol(Result, -1, length(Result)+1) - 1; // TODO: Need the real index of the line
  1393. end;
  1394. if CurLen = DesiredIndent then
  1395. exit;
  1396. Delete(Result, Length(Result), 1);
  1397. if OnlyTabs then begin
  1398. CurLen := FCurrentLines.LogicalToPhysicalCol(Result, -1, length(Result)+1) - 1; // TODO: Need the real index of the line
  1399. DesiredIndent := CurLen;
  1400. exit;
  1401. end;
  1402. repeat
  1403. Result := Result + ' ';
  1404. CurLen := FCurrentLines.LogicalToPhysicalCol(Result, -1, length(Result)+1) - 1; // TODO: Need the real index of the line
  1405. until CurLen >= DesiredIndent;
  1406. end;
  1407. function TSynBeautifier.GetCharMix(const LinePos: Integer; var Indent: Integer;
  1408. var IndentCharsFromLinePos: Integer; ModifyIndent: Boolean): String;
  1409. var
  1410. Temp, KnownMix, BasedMix: string;
  1411. KnownPhysLen, PhysLen: Integer;
  1412. BackCounter: LongInt;
  1413. OrigIndent: Integer;
  1414. begin
  1415. OrigIndent := Indent;
  1416. case FIndentType of
  1417. sbitSpace, sbitPositionCaret:
  1418. begin
  1419. IndentCharsFromLinePos := 0;
  1420. Result := StringOfChar(' ', Indent);
  1421. if not ModifyIndent then Indent := OrigIndent;
  1422. exit;
  1423. end;
  1424. sbitConvertToTabSpace:
  1425. begin
  1426. IndentCharsFromLinePos := 0;
  1427. Result := CreateTabSpaceMix(Indent, False);
  1428. exit;
  1429. if not ModifyIndent then Indent := OrigIndent;
  1430. end;
  1431. sbitConvertToTabOnly:
  1432. begin
  1433. IndentCharsFromLinePos := 0;
  1434. Result := CreateTabSpaceMix(Indent, True);
  1435. if not ModifyIndent then Indent := OrigIndent;
  1436. exit;
  1437. end;
  1438. end;
  1439. if (IndentCharsFromLinePos > 0) and (IndentCharsFromLinePos <= FCurrentLines.Count) then
  1440. begin
  1441. Temp := GetLine(IndentCharsFromLinePos);
  1442. KnownMix := copy(Temp, 1, GetIndentForLine(FCurrentEditor, Temp, False));
  1443. end
  1444. else
  1445. KnownMix := '';
  1446. BasedMix := KnownMix;
  1447. KnownPhysLen := GetIndentForLine(FCurrentEditor, KnownMix, True);
  1448. BackCounter := LinePos;
  1449. while (BackCounter > 0) and (KnownPhysLen < Indent) do begin
  1450. dec(BackCounter);
  1451. Temp := GetLine(BackCounter);
  1452. if Temp <> '' then begin
  1453. Temp := copy(Temp, 1, GetIndentForLine(FCurrentEditor, Temp, False));
  1454. PhysLen := GetIndentForLine(FCurrentEditor, Temp, True);
  1455. if (PhysLen > KnownPhysLen) and (copy(temp, 1, length(BasedMix)) = BasedMix) then
  1456. begin
  1457. KnownMix := Temp;
  1458. KnownPhysLen := PhysLen;
  1459. IndentCharsFromLinePos := BackCounter + 1;
  1460. end;
  1461. end;
  1462. end;
  1463. Result := AdjustCharMix(Indent, KnownMix, '');
  1464. if not ModifyIndent then Indent := OrigIndent;
  1465. end;
  1466. procedure TSynBeautifier.ApplyIndent(LinePos: Integer;
  1467. Indent: Integer; RelativeToLinePos: Integer; IndentChars: String = '';
  1468. IndentCharsFromLinePos: Integer = 0);
  1469. var
  1470. CharMix: String;
  1471. i: Integer;
  1472. begin
  1473. if (LinePos < 1) or (LinePos > FCurrentEditor.Lines.Count) then
  1474. exit;
  1475. // calculate the final indent needed
  1476. if (RelativeToLinePos > 0) and (RelativeToLinePos <= FCurrentEditor.Lines.Count) then
  1477. Indent := Indent + GetIndentForLine(FCurrentEditor, GetLine(RelativeToLinePos-1), True);
  1478. if Indent< 0 then
  1479. Indent := 0;
  1480. // Calculate the charmix
  1481. CharMix := '';
  1482. if Indent > 0 then begin
  1483. if (IndentCharsFromLinePos > 0) and (IndentCharsFromLinePos <= FCurrentEditor.Lines.Count) then begin
  1484. CharMix := GetLine(IndentCharsFromLinePos-1);
  1485. i := GetIndentForLine(FCurrentEditor, CharMix, False);
  1486. CharMix := AdjustCharMix(Indent, copy(CharMix, 1, i), IndentChars);
  1487. end
  1488. else if IndentChars <> '' then begin
  1489. CharMix := AdjustCharMix(Indent, '', IndentChars);
  1490. end
  1491. else begin
  1492. i := LinePos;
  1493. CharMix := GetCharMix(LinePos, Indent, i);
  1494. end;
  1495. end;
  1496. {$IFDEF VerboseIndenter}
  1497. DebugLn(['TSynBeautifier.ApplyIndent IndentChars="',dbgstr(IndentChars),' Indent=',Indent]);
  1498. {$ENDIF}
  1499. i := GetIndentForLine(FCurrentEditor, GetLine(LinePos-1), False);
  1500. FCurrentLines.EditDelete(1, LinePos, i);
  1501. if (CharMix <> '') and not((FIndentType = sbitPositionCaret) and (GetLine(LinePos-1) = '')) then
  1502. FCurrentLines.EditInsert(1, LinePos, CharMix);
  1503. FLogicalIndentLen := length(CharMix);
  1504. {$IFDEF VerboseIndenter}
  1505. DebugLn(['TSynBeautifier.ApplyIndent Line="',dbgstr(FCurrentLines.ExpandedStrings[LinePos-1]),'"']);
  1506. {$ENDIF}
  1507. end;
  1508. function TSynBeautifier.GetIndentForLine(Editor: TSynEditBase; const Line: string; Physical: boolean): Integer;
  1509. var
  1510. p: PChar;
  1511. begin
  1512. p := PChar(Line);
  1513. if Assigned(p) then begin
  1514. Result := 0;
  1515. while p^ in [#1..#32] do begin
  1516. Inc(p);
  1517. Inc(Result);
  1518. end;
  1519. if Physical and (Result>0) then
  1520. Result := FCurrentLines.LogicalToPhysicalCol(Line, -1, Result+1)-1; // TODO, Need the real index of the line
  1521. end else
  1522. Result := 0;
  1523. end;
  1524. function TSynBeautifier.GetDesiredIndentForLine(Editor: TSynEditBase;
  1525. const Lines: TSynEditStrings; const ACaret: TSynEditCaret;
  1526. out ReplaceIndent: Boolean; out DesiredIndent: String): Integer;
  1527. var
  1528. BackCounter, PhysLen: Integer;
  1529. Temp: string;
  1530. FoundLine: LongInt;
  1531. begin
  1532. Result := 1;
  1533. FCurrentLines := Lines; // for GetCurrentIndent
  1534. BackCounter := ACaret.LinePos - 1;
  1535. if BackCounter > 0 then
  1536. repeat
  1537. Dec(BackCounter);
  1538. Temp := Lines[BackCounter];
  1539. Result := GetIndentForLine(Editor, Temp, True) + 1; // Physical
  1540. until (BackCounter = 0) or (Temp <> '');
  1541. FoundLine := BackCounter + 1;
  1542. ReplaceIndent := False;
  1543. //if assigned(FOnGetDesiredIndent) then
  1544. // FOnGetDesiredIndent(Editor, ACaret.LineBytePos, ACaret.LinePos, Result,
  1545. // FoundLine, ReplaceIndent);
  1546. //if Result < 0 then exit;
  1547. //if FoundLine > 0 then
  1548. // Temp := Lines[FoundLine-1]
  1549. //else
  1550. // FoundLine := BackCounter + 1;
  1551. Temp := copy(Temp, 1, GetIndentForLine(Editor, Temp, False));
  1552. case FIndentType of
  1553. sbitCopySpaceTab:
  1554. begin
  1555. DesiredIndent := copy(Temp, 1,
  1556. Lines.PhysicalToLogicalCol(Temp, FoundLine - 1, Result) - 1);
  1557. PhysLen := Lines.LogicalToPhysicalCol(Temp, ACaret.LinePos - 1, Length(Temp) + 1);
  1558. if PhysLen < Result then
  1559. DesiredIndent := DesiredIndent + StringOfChar(' ', Result - PhysLen);
  1560. end;
  1561. sbitConvertToTabSpace:
  1562. begin
  1563. dec(Result);
  1564. DesiredIndent := CreateTabSpaceMix(Result, False);
  1565. inc(Result);
  1566. end;
  1567. sbitConvertToTabOnly:
  1568. begin
  1569. dec(Result);
  1570. DesiredIndent := CreateTabSpaceMix(Result, True);
  1571. inc(Result);
  1572. end;
  1573. else
  1574. DesiredIndent := StringOfChar(' ', Result - 1);
  1575. end;
  1576. end;
  1577. function TSynBeautifier.GetDesiredIndentForLine(Editor: TSynEditBase;
  1578. const Lines: TSynEditStrings; const ACaret: TSynEditCaret): Integer;
  1579. var
  1580. Dummy: String;
  1581. Replace: Boolean;
  1582. begin
  1583. Result := GetDesiredIndentForLine(Editor, Lines, ACaret, Replace, Dummy);
  1584. end;
  1585. end.