Sign in Join
Wiki copytemporary

Codetools: what changed

Revision 3 to 4. Removed lines have a minus sign; added lines have a plus sign.

OldNewLine
11# What are the codetools?
22
33Codetools is a Lazarus package providing tools to parse, explore, edit and refactor Pascal sources. The codetools have been packaged in their own module and are licensed under GPL. Many examples showing how to use the codetools in your own programs can be found under components/codetools/examples.
44
55svn:
66
77- Lazarus: <http://svn.freepascal.org/svn/lazarus/trunk>
88- Only CodeTools: <http://svn.freepascal.org/svn/lazarus/trunk/components/codetools>
99
1010# Using the codetools without the IDE
1111
1212You can use the codetools without the IDE. This can be used to test a new tool. An easy example is
1313
14-` <lazarusdir>/components/codetools/examples/methodjumping.lpi`
14+```
15+ <lazarusdir>/components/codetools/examples/methodjumping.lpi
16+```
1517
1618To test find declaration, the codetools need to parse sources. Especially the RTL and FCL sources. The examples use the following environment variables:
1719
1820- FPCDIR: path to the FPC sources, the default is *~/freepascal/fpc*.
1921- PP: path to the compiler executable (/usr/bin/fpc or /usr/bin/ppc386 or C:\lazarus\ppc386.exe). The codetools need to ask the compiler for the settings. The default is to search for 'fpc' via the PATH variable.
2022- FPCTARGETOS: tell the codetools to scan for another operating system (cross compiling). For example: linux, freebsd, darwin, win32, win64, wince
2123- FPCTARGETCPU: when scanning for another CPU. For example: i386, powerpc, x86_64, arm, sparc
2224- LAZARUSDIR: path of the lazarus sources. Only needed if you want to scan them.
2325
2426FPC is a very complex project with lots of search paths, include files and macros. The codetools need to know all these paths and macros in order to parse this jungle. To setup all this easily the codetools contain predefined templates for FPC, Lazarus, Delphi and Kylix source directories. See for a find declaration example
2527
26-` <lazarusdir>/components/codetools/examples/finddeclaration.lpi`
28+```
29+ <lazarusdir>/components/codetools/examples/finddeclaration.lpi
30+```
2731
2832Because the FPC sources contain multiple versions of some units, and the FPC sources change often, the codetools do not use a fixed table of paths, but rather scan the whole FPC directory structure on first use, applying a set of rules specifying what is the correct source for the current TargetOS and TargetCPU. This scan may take a while depending on your disk speed. All examples save the result in **codetools.config**, so that the next time Lazarus is started this possibly lengthy scan is skipped.
2933
3034Whenever the FPC sources are changed or a unit is renamed, just delete the **codetools.config** file. The Lazarus IDE has its own config file and does a rescan whenever the compiler executable changes (or when the user forces a 'Tools \> Rescan FPC source directory').
3135
3236## Defining search paths and macros
3337
3438Important: Codetools assumes that you have multiple directories with different compiler settings, e.g. the FPC sources have different search paths than your sources. You can have FPC, Delphi and pas2js directories - all at the same time.
3539
3640The codetools uses "define templates" to generate search paths and macros. Define templates are trees of rules.
3741
3842### Example: Expand IncPath for a directory and/or its sub directories
3943
4044The following demo demonstrate various ways to expand the IncPath for a directory and/or its sub directories:
4145
4246``` pascal
4347program SetIncludePath;
4448
4549{$mode objfpc}{$H+}
4650
4751uses
4852 SysUtils, CodeToolManager, DefineTemplates, LazFileUtils;
4953
5054var
5155 Directory: String;
5256 DirectoryTemplate: TDefineTemplate;
5357 IncPathTemplate: TDefineTemplate;
5458 SubDirectory: String;
5559begin
5660 // create a template for the current directory
5761 // all child nodes of this template are only valid for this directory.
5862 Directory:=ExpandFileNameUTF8(GetCurrentDirUTF8);
5963 DirectoryTemplate:=TDefineTemplate.Create('Current working directory',
6064 'Example template for current working directory','',Directory,da_Directory);
6165
6266 // Example 1:
6367 // Add a sub template to extend the include search path #IncPath
6468 // only for the current directory, not its sub directories.
6569 // Note: 'myincludes' is a relative path, so it won't work for sub directories.
6670 IncPathTemplate:=TDefineTemplate.Create('Add myincludes to the IncPath',
6771 'Add myincludes to the include search path',
6872 IncludePathMacroName, // variable name: #IncPath
6973 IncludePathMacro+';myincludes' // new value: $(#IncPath);myincludes
7074 ,da_Define // da_Define extends the IncPath only for this directory
7175 );
7276 DirectoryTemplate.AddChild(IncPathTemplate);
7377
7478 // Example 2:
7579 // Add a sub template to extend the include search path #IncPath
7680 // with an absolute path for the current directory and all its sub directories.
7781 // Note: '/tmp/myincludes' is an absolute path, so works for sub directories.
7882 IncPathTemplate:=TDefineTemplate.Create('Add /tmp/myincludes to the IncPath',
7983 'Add /tmp/myincludes to the include search path',
8084 IncludePathMacroName, // variable name: #IncPath
8185 IncludePathMacro+';'+SetDirSeparators('/tmp/myincludes') // new value: $(#IncPath);/tmp/myincludes
8286 ,da_DefineRecurse // da_DefineRecuse extends the IncPath for this directory and all sub directories
8387 );
8488 DirectoryTemplate.AddChild(IncPathTemplate);
8589
8690 // Example 3:
8791 // Using the #DefinePath macro you can use the current directory to create absolute paths.
8892 IncPathTemplate:=TDefineTemplate.Create('Add ./myincludes to the IncPath',
8993 'Add ./myincludes to the include search path',
9094 IncludePathMacroName, // variable name: #IncPath
9195 IncludePathMacro+';'+DefinePathMacro+'/myincludes' // new value: $(#IncPath);$(#DefinePath)/myincludes
9296 ,da_DefineRecurse // da_DefineRecuse extends the IncPath for this directory and all sub directories
9397 );
9498 DirectoryTemplate.AddChild(IncPathTemplate);
9599
96100 // add the directory template to the tree
97101 CodeToolBoss.DefineTree.Add(DirectoryTemplate);
98102
99103 writeln('Directory="',Directory,'"',
100104 ' IncPath="',CodeToolBoss.GetIncludePathForDirectory(Directory),'"');
101105 SubDirectory:=AppendPathDelim(Directory)+'sub';
102106 writeln('SubDirectory="',SubDirectory,'"',
103107 ' IncPath="',CodeToolBoss.GetIncludePathForDirectory(SubDirectory),'"');
104108end.
105109```
106110
107111### Example: Expand global UnitPath
108112
109113Here is an example how to extend the global (all directories) unit search path and query the unit path of one directory. This might be useful for single minded command line tools.
110114
111115``` pascal
112116uses
113117 Classes, SysUtils, CodeToolManager, DefineTemplates, FileProcs;
114118
115119var
116120 Directory: String;
117121 UnitPathTemplate: TDefineTemplate;
118122begin
119123 // add a sub template to extend the include search path #UnitPath.
120124 UnitPathTemplate:=TDefineTemplate.Create(
121125 'Add myunits to the UnitPath', // optional: an arbitrary name of the template, useful for finding it later
122126 'Add /tmp/myunits to the unit search path', // optional: a description
123127 UnitPathMacroName, // variable name: #UnitPath
124128 UnitPathMacro+';/tmp/myunits' // new value: $(#UnitPath);/tmp/myunits
125129 ,da_DefineRecurse
126130 );
127131 // add the unit path template to the tree
128132 CodeToolBoss.DefineTree.Add(UnitPathTemplate);
129133
130134 Directory:=ExpandFileNameUTF8(GetCurrentDirUTF8);
131135 writeln('Directory="',Directory,'"',
132136 ' UnitPath="',CodeToolBoss.GetUnitPathForDirectory(Directory),'"');
133137end.
134138```
135139
136140## Define Template Macros
137141
138142Macros defined by define templates are used by the Pascal parser. The Codetools have functions to query the Free Pascal Compiler for its macros and convert them to define templates. This is done by CodeToolBoss.Init(Options); which you can find in many examples.
139143
140144Additionally there some predefined macros, which start with a hash (#). See unit definetemplates for the full list. Here are some important ones:
141145
142146``` pascal
143147 ExternalMacroStart = '#';
144148 // Standard macros
145149 DefinePathMacroName = ExternalMacroStart+'DefinePath'; // the current directory
146150 UnitPathMacroName = ExternalMacroStart+'UnitPath'; // unit search path separated by semicolon (same as given to FPC)
147151 IncludePathMacroName = ExternalMacroStart+'IncPath'; // include file search path separated by semicolon (same as given to FPC)
148152 SrcPathMacroName = ExternalMacroStart+'SrcPath'; // unit source search path separated by semicolon (not given to FPC)
149153```
150154
151155For example the **IncludePathMacroName** is **\#IncPath** and is used to define the include file search path. Keep in mind that macro values depend on the directory.
152156
153157Define templates values can contain macros. The macro has to be enclosed in dollar and brackets: **\$(macro)**.
154158
155159In the above example the value was *'\$('+IncludePathMacroName+');/tmp/myincludes'*, which is *'\$(#IncPath);/tmp/myincludes'*, which executes to the old include search path plus *';/tmp/myincludes'*, which means a path is appended. For readability you can use the constant IncludePathMacro instead of *'\$('+IncludePathMacroName+')'*:
156160
157161``` pascal
158162 DefinePathMacro = '$('+DefinePathMacroName+')'; // the path of the define template
159163 UnitPathMacro = '$('+UnitPathMacroName+')';
160164 IncludePathMacro = '$('+IncludePathMacroName+')';
161165 SrcPathMacro = '$('+SrcPathMacroName+')';
162166```
163167
164168## Define Template Actions
165169
166170A define template has a name, an optional description, a variable, a value and an action. The name and description are optional. The meaning of variable and value depends on the action. You can add define templates as children of define templates - creating a tree of define templates. You can see many examples for define templates in the Lazarus' dialog *Tools / CodeTools Defines Editor*.
167171
168172- da_Block - Used for grouping templates. When the block is executed all children are executed.
169173- da_Directory - Use this to define all rules of a directory. If this is a root directory set Value to the full expanded directory path (use function CleanAndExpandDirectory). If this is a sub directory (parent template is a directory) Value is the sub path. Can contain macros. Children are only executed if directory fits.
170174- da_Define - sets a macro (Variable) value (Value) of the current directory. The value can contain macros. Note that when a macro value is set to empty string *it is still defined, that means*{\$IFDEF variable}*will still result in*true''. Children are not executed.
171175- da_Undefine - clears a macro (Variable) of the current directory. *{\$IFDEF macro}* results in *false*. Children are not executed.
172176- da_DefineRecurse - as da_Define, but for current directory and sub directories.
173177- da_UndefineRecurse - as da_Undefine, but for current directory and sub directories.
174178- da_UndefineAll - clear all macro values.
175179- da_IfDef - if macro *Variable* is defined then execute children and skip following da_Else and da_ElseIf.
176180- da_IfNDef - if macro *Variable* is not defined then execute children and skip following da_Else and da_ElseIf.
177181- da_If,daElseIf - if the boolean expression *Value* executes to true then execute children and skip following da_Else and da_ElseIf. Value can contain macros.
178182- da_Else - When this template is executed then execute all children.
179183
180184## How to extend the include path of a directory
181185
182186See the example lazarus/components/codetools/examples/setincludepath.lpr. It demonstrates the use of relative paths, absolute paths, how to use the DefinePathMacro and the difference between da_Define and da_DefineRecurse.
183187
184188# Using the codetools in the IDE with the IDEIntf
185189
186190See \<lazarusdir\>/examples/idequickfix/quickfixexample.lpk package. It demonstrates:
187191
188192- How to write an IDE package. When you install this package it will register a Quick Fix item in the IDE.
189193- How to write a Quick Fix item for the compiler message: 'Parameter "Sender" not used'
190194- How to use the codetools to:
191195 - parse a unit
192196 - convert Filename, Line, and Column data into a codetools source position
193197 - find a codetools node at a cursor position
194198 - find a procedure node, and the begin..end node
195199 - create a nice insertion position for a statement at the beginning of the begin..end block
196200 - obtain line indentation information so that a new line will work in a sub-procedure as well
197201 - insert code using the codetools
198202
199203# Codetools rules for FPC sources
200204
201205When the codetools searches the source of a fpc ppu it uses a set of rules. You can write your own rules, but normally you will use the standard rules, which are defined in the include file components/codetools/fpcsrcrules.inc. You can test the rules with the command line utility: components/codetools/examples/testfpcsrcunitrules.lpi.
202206
203207## Usage of testfpcsrcunitrules
204208
205209 Usage: lazarus/components/codetools/examples/testfpcsrcunitrules -h
206210
207211 -c <compiler file name>, --compiler=<compiler file name>
208212 Default is to use environment variable PP.
209213 If this is not set, search for fpc
210214
211215 -T <target OS>, --targetos=<target OS>
212216 Default is to use environment variable FPCTARGET.
213217 If this is not set, use the default of the compiler.
214218
215219 -P <target CPU>, --targetcpu=<target CPU>
216220 Default is to use environment variable FPCTARGETCPU.
217221 If this is not set, use the default of the compiler.
218222
219223 -F <FPC source directory>, --fpcsrcdir=<FPC source directory>
220224 Default is to use environment variable FPCDIR.
221225 There is no default.
222226
223227 -u <unit name>, --checkunit=<unit name>
224228 Write a detailed report about this unit.
225229
226230## Example for testfpcsrcunitrules
227231
228232Open the testfpcsrcunitrules.lpi in the IDE and compile it. Then run the utility in a terminal/console:
229233
230-`./testfpcsrcunitrules -F ~/fpc/sources/2.5.1/fpc/`
234+```
235+./testfpcsrcunitrules -F ~/fpc/sources/2.5.1/fpc/
236+```
231237
232238This will tell you which compiler is used, which compiler executes, which config files were tested and parsed, and it warns you about duplicate units in the FPC search path and about duplicated unit source files. **Note:** This example caches results in the file *codetools.config*. You should delete *codetools.config* when you update the compiler or your fpc.cfg file.
233239
234240## Duplicate source files
235241
236242You find out that the codetools opens for target wince/arm the wrong source of the unit *mmsystem*. Run the tool with the -u parameter:
237243
238244./testfpcsrcunitrules -F ~/fpc/2.5.1/fpc/ -T wince -P arm -u mmsystem
239245
240246This will give you a detailed report where this unit was found and what score each source file got. For example:
241247
242-`Unit report for mmsystem`\
243-`  WARNING: mmsystem is not in PPU search path`\
244-`GatherUnitsInFPCSources UnitName=mmsystem File=packages/winunits-base/src/mmsystem.pp Score=11`\
245-`GatherUnitsInFPCSources UnitName=mmsystem File=packages/winceunits/src/mmsystem.pp Score=11 => duplicate`
248+``` pascal
249+Unit report for mmsystem
250+ WARNING: mmsystem is not in PPU search path
251+GatherUnitsInFPCSources UnitName=mmsystem File=packages/winunits-base/src/mmsystem.pp Score=11
252+GatherUnitsInFPCSources UnitName=mmsystem File=packages/winceunits/src/mmsystem.pp Score=11 => duplicate
253+```
246254
247255This means there are two source files with the same score, so the codetools took the first. The last one in winceunits is for target wince and the first one is for win32 and win64.
248256
249257Now open the rules file fpcsrcrules.inc.
250258
251259Rules work like this:
252260
253261``` pascal
254262Score:=10;
255263Targets:='wince';
256264Add('packages/winceunits');
257265```
258266
259267The **Add** adds a rule for all files beginning with 'packages/winceunits' that adds a score of 10 to all these files. The **Targets** is a comma separated list of target operating systems and/or target processors. For example Targets='wince,linux,i386' means: apply this rules to TargetOS wince or linux and to all TargetCPU i386.
260268
261269# How the codetools parses sources differently from the compiler
262270
263271A compiler is optimized to parse code linearly and to load needed units and include files as soon as it parses a uses section or a directive. The codetools are optimized to parse only certain code sections. For example jumping from the method declaration to the method body only needs the unit and its include files. When a codetool searches a declaration it searches backwards. That means it starts searching in the local variables, then upwards from the implementation. When it finds a uses section it searches the identifiers in the interface section of the units. When the identifier is found it stops. The result and some intervening steps are cached. Because it often only needs to parse a few interface sections it finds an individual identifier very quickly.
264272
265273The codetools do not parse a source in a single step (as the compiler does) but in several steps, which depend on what the current codetool needs:
266274
267275- First a source file is loaded in a TCodeBuffer. The IDE uses this step to change the encoding to UTF8. The files are kept in memory and only reloaded if the modification date changes or if a file is manually reverted. There are several tools and functions which work directly on the buffer.
268276- The next step is to parse the unit (or include file). A unit must be parsed from the beginning, so the codetools tries to find the main file, the first file of a unit. It does that by looking for a directive in the first line like {%MainUnit ../lclintf.pp}. If that does not exist, it searches in the includelink cache. The IDE saves this cache to disk, so the IDE learns over time.
269277- After finding the main file a TCodeTool is created and a TLinkScanner parses the source. It handles compiler directives, such as include directives and conditional directives. The scanner can be given a range, so it might, for instance, parse only a unit's interface. The scanner creates the **clean source**. The clean source is put together from all include files, having been stripped of code in the *else* part of conditional directives, which is skipped. It also creates a list of **links** which map the clean source to the real source files. The clean source is now Pascal containing no *else* code. Note: there are also tools designed to scan a single source for all directives. These tools create a tree of directives.
270278- After creating the clean source a TCodeTool parses it and creates a tree of TCodeTreeNode. It can also be given a range. This parser skips a few parts, for example class members, begin..end blocks and parameter lists. Many tools don't need them. These sub nodes are created on demand. A TCodeTreeNode has a range StartPos..EndPos which are clean positions, that means positions in the clean source. There are only nodes for the important parts. Creating nodes for every detail would need more memory than the source itself and is seldom needed. There are plenty of functions to find out the details. For example if a function has the 'cdecl' calling convention.
271279- When searching for an identifier the search stores the base types it finds and creates caches for all identifiers in the interface section.
272280
273281Every level has its own caches, which need to be checked and updated before calling a function. Many high level functions accessible via the CodeToolBoss do that automatically. For others it is the responsibility of the caller.
274282
275283Example for:
276284
277285unit1.pas:
278286
279287``` pascal
280288unit Unit1;
281289{$I settings.inc}
282290interface
283291uses
284292 {$IFDEF Flag}
285293 unix,
286294 {$ELSE}
287295 windows,
288296 {$ENDIF}
289297 Classes;
290298```
291299
292300settings.inc:
293301
294302``` pascal
295303{%MainUnit unit1.pas}
296304{$DEFINE Flag}
297305```
298306
299307clean source:
300308
301309``` pascal
302310unit Unit1;
303311{$I settings.inc}{%MainUnit unit1.pas}
304312{$DEFINE Flag}
305313interface
306314uses
307315 {$IFDEF Flag}
308316 unix,
309317 {$ELSE}{$ENDIF}
310318 Classes;
311319```
312320
313321Hint: To easily parse a unit and build the nodes, use **CodeToolBoss.Explore**.
314322
315323# Tool, Node, LinkScanner
316324
317325## Tool
318326
319327A tool is a TCodeTool. For Pascal files every unit, program and package gets its own TCodeTool. These are automatically created by the CodetoolBoss when parsing sources. A tool has a Scanner (TLinkScanner) and a Root (TCodeTreeNode), which is the root node of the tree of Pascal nodes of the module (e.g. unit).
320328
321329## LinkScanner
322330
323331Every TCodeTool creates a TLinkScanner to scan the files and creates the CleanedSrc, which is the parsed unit source, include files included and code in skipped \$IF directives removed. When only the unit interface was parsed, the CleanedSrc may not be complete. TLinkScanner is not shared between TCodeTools. There is a 1:1 mapping between TLinkScanner and TCodeTool.
324332
325333## Node
326334
327335- A TCodeTreeNode is part of a tree of such nodes. The root node is stored in a TCodeTool.Root. Nodes are not shared between tool. Every node is associated with exactly one TCodeTool.
328336- When a function returns a Node, it always returns the associated Tool as well.
329337- Node.StartPos/EndPos are positions 1-based in the Scanner.CleanedSrc.
330338- To get from the cleaned postion to the file, line, column use Tool.CleanPosToCaret(Node.StartPos,xyp).
331339
332340# CleanPos and CursorPos
333341
334342There are several methods to define a position in the codetools.
335343
336344The codetools Absolute position is related to the source as a continuous string starting at character 1. For example a TCodeBuffer holds the file contents as a single string in its Source property. Caret or cursor positions are given as X,Y where X is the column number and Y the line number. Each value (X and Y) starts at 1. A TCodeBuffer provides the member functions **LineColToPosition** and **AbsoluteToLineCol'' to convert between (X,Y) values and the Absolute codetools position. When working with multiple source files (such as a unit which may contain several include files), the clean position relates to the absolute position in the stripped code**Src**. Here Src is a string whose clean positions start at 1. Cursor positions are specified as TCodeXYPosition (Code,X,Y). A TCodeTool provides the functions**CaretToCleanPos**and**CleanPosToCaret''' to convert between them.
337345
338346# Inserting, deleting, replacing - the TSourceChangeCache
339347
340348When making changes to the source code of a unit (or its include files) you should use the CodetoolBoss.SourceChangeCache instead of altering the source directly.
341349
342350- Simple usage: Connect, Replace, Replace, ... Apply. See below.
343351- You can use cleanpos as given by the node tree OR you can use direct position in a file.
344352- You can use Replace to insert and delete, which automatically calls events, so connected editors are notified of changes.
345353- It can automatically insert needed spaces, line breaks or empty lines in front or behind each Replace. For example you define that there should be an empty line in front. The SourceChangeCache checks what is inserted and how much space there is already and will insert needed space.
346354- It checks if the replaced/deleted span is writable.
347355- You can do multiple Replaces and you control when they are applied. Keep in mind that inserting code means that the parsed tree becomes invalid and needs rebuilding.
348356- Multiple replaces are checked for intersection. For example an insert in the middle of deleted code gives an error.
349357- Mutiple insertions at the same place are added FIFO - first at the top.
350358- You can combine several functions altering code to one bigger function. See below.
351359
352360## Usage
353361
354362The SourceChangeCache works on a unit, so you need to get a TCodeTool and scan a unit/include file. For example:
355363
356364``` pascal
357365 // Step 1: load the file and parse it
358366 Code:=CodeToolBoss.LoadFile(Filename,false,false);
359367 if Code=nil then
360368 raise Exception.Create('loading failed '+Filename);
361369 if not CodeToolBoss.Explore(Code,Tool,false) then
362370 ...;// parse error ...
363371
364372 // Step 2: connect the SourceChangeCache
365373 CodeToolBoss.SourceChangeCache.MainScanner:=Tool.Scanner;
366374
367375 // Step 3: use Replace to insert and/or delete code
368376 // The first two parameters are the needed spaces in front and behind the insertion
369377 // The FromPos,ToPos defines the deleted/replaced range in CleanPos positions.
370378 // The NewCode is the string of new code. Use '' for a delete.
371379 if not CodeToolBoss.SourceChangeCache.Replace(gtNone,gtNone,FromPos,ToPos,NewCode) then
372380 exit; // e.g. source read only or a former Replace has deleted the place
373381 ...do some more Replace...
374382
375383 // Step 4: Apply the changes
376384 if not CodeToolBoss.SourceChangeCache.Apply then
377385 exit; // apply was aborted
378386```
379387
380388## BeginUpdate/EndUpdate
381389
382390BeginUpdate/EndUpdate delays the Apply. This is useful when combining several code changing functions. For example:
383391
384392You want to scan the unit, add a unit to the interface uses section, and remove the unit from the implementation uses section. The two functions AddUnitToMainUsesSection and RemoveUnitFromUsesSection use Apply, altering the source, so the second function would rescan the unit a second time. But since the two functions are independent of each other (they change different parts of the source) you can combine them and do it with one scan:
385393
386394``` pascal
387395 // Step 1: parse unit and connect SourceChangeCache
388396 if not CodeToolBoss.Explore(Code,Tool,false) then
389397 ...;// parse error ...
390398 CodeToolBoss.SourceChangeCache.MainScanner:=Tool.Scanner;
391399
392400 // Step 2: delay Apply
393401 CodeToolBoss.SourceChangeCache.BeginUpdate;
394402
395403 // Step 3: add unit to interface section
396404 // AddUnitToMainUsesSection would apply and change the code
397405 // Because of the BeginUpdate the change is not yet done, but stored in the SourceChangeCache
398406 if not Tool.AddUnitToMainUsesSection('Classes','',CodeToolBoss.SourceChangeCache) then exit;
399407
400408 // Step 4: remove unit from implementation section
401409 // Without the BeginUpdate the RemoveUnitFromUsesSection would rescan the unit
402410 if Tool.FindImplementationUsesSection<>nil then
403411 if not Tool.RemoveUnitFromUsesSection(Tool.FindImplementationUsesSection,'Classes',CodeToolBoss.SourceChangeCache) then exit;
404412
405413 // Step 5: apply all changes
406414 if not CodeToolBoss.SourceChangeCache.EndUpdate then
407415 exit; // apply was aborted
408416```
409417
410418BeginUpdate/EndUpdate work with a counter, so if you call BeginUpdate twice you need to call EndUpdate twice. This means you can put the above example in a function and combine that with another function.
411419
412420## Saving changes to disk
413421
414422The above changes are made to the code buffers and the buffers are marked modified. To save the changes to disk, you have to call Save for each modified buffer.
415423
416424- The buffers that will be modified in the next Apply/EndUpdate are in SourceChangeCache.BuffersToModify and BuffersToModifyCount.
417425- The events SourceChangeCache.OnBeforeApplyChanges/OnAfterApplyChanges are used by the CodeToolBoss, which connects it to its own OnBeforeApplyChanges/OnAfterApplyChanges. The Lazarus IDE sets these events and automatically opens modified files in the source editor, so all changes go into the undo list of synedit.
418426
419427# Hints/Tips/Guide Lines
420428
421429- BuildTree checks the current state and will only parse if needed. If a former call has parsed the interface and you need the interface again, BuildTree will not do anything. If you need the full unit, only the implementation will be parsed. Nodes are only freed if some files have changed on disk or some settings have changed (initial macros). BuildTree does not check all files on every call. Instead it uses the directory cache of the codetools. So, if nothing has changed it will return very quickly. You should call it before any search operation.
422430- Do **not** call BuildTree **after** an operation. That is a waste of CPU.
423431- BuildTree raises an exception when there are missing include files or syntax errors. You should enclose your code into
424432
425433``` pascal
426434try
427435 Tool.BuildTree(lsrEnd);
428436 ... search ... replace
429437except
430438 on E: Exception do
431439 CodeToolBoss.HandleException(E);
432440end;
433441```
434442
435443- After calling SourceChangeCache.Replace (multiple times) the sources (TCodeBuffers) have not changed immediately. You must call SourceChangeCache.Apply to change the sources. This will not save the changes to file.
436444
437445# Test suite
438446
439447The codetools test suite can be found here:
440448
441-`lazarus/components/codetools/tests/runtestscodetools`
449+```
450+lazarus/components/codetools/tests/runtestscodetools
451+```
442452
443453Compile it and run it:
444454
445-`cd lazarus/components/codetools/tests`\
446-`./runtestscodetools`
455+```
456+cd lazarus/components/codetools/tests
457+./runtestscodetools
458+```
447459
448460# Links
449461
450462- [[Lazarus IDE Tools]] - A tutorial about the built-in tools of the standard IDE
451463- [[Cody]] - IDE package adding advanced code tools to the IDE
452464- [[Extending the IDE]] - How to write your own codetools plugins for the IDE.
453465
454466---
455467*From the Free Pascal wiki, page "Codetools", as it stood on 2020-12-31 after a change by Mattias2, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*
456468Category: Components

Back to history