View Issue Details
ID | Project | Category | View Status | Date Submitted | Last Update |
---|---|---|---|---|---|
0038196 | Lazarus | IDE | public | 2020-12-10 09:07 | 2021-02-16 15:37 |
Reporter | OkobaPatino | Assigned To | Juha Manninen | ||
Priority | normal | Severity | minor | Reproducibility | always |
Status | resolved | Resolution | fixed | ||
Product Version | 2.1 (SVN) | ||||
Summary | 0038196: JCF will move the breakpoints | ||||
Description | After formatting a code with breakpoints, one of the breakpoints will move to the first line of the unit. | ||||
Steps To Reproduce | program Project1; begin b:=a; //<<Line with breakpoint end. After format: program Project1;//<<Line with breakpoint begin b:=a end. | ||||
Tags | No tags attached. | ||||
Fixed in Revision | r64347, r64348 | ||||
LazTarget | - | ||||
Widgetset | |||||
Attached Files |
|
related to | 0038195 | closed | Juha Manninen | JCF should set the cursor at the line end |
related to | 0038125 | closed | Juha Manninen | JCF fails formatting using defines in uses |
|
It is related to 0038178 or 0038081. |
|
@DomingoGP, Can you please check the last changes? |
|
Basically the problem with breakpoints and also with bookmarks is because we replace all the editor text after formatting, which in my opinion is better than the previous version that synchronized badly, replacing almost all the lines from the first difference and adding blank lines throughout the code at "random" positions. See 0038195 for more complete explanation. |
|
I can understand your view, but I can not accept it specifically because of breaking a working behavior for breakpoints and other things because of a not complete implementation. In the old way at least we could do day to day work with some issues, now and with breakpoints problem, JCF is broken. Please revert back the working version until your new way is working in all cases. |
|
This patch reverts to the old code. JCF_Revert.patch (1,429 bytes)
From 03e11911af0551eb5a561196a44b05dbd1cdda21 Mon Sep 17 00:00:00 2001 From: DomingoGP <dgalmesp@gmail.com> Date: Thu, 10 Dec 2020 22:23:30 +0100 Subject: [PATCH] revert copy of formated code to ide editor. --- components/jcf2/ReadWrite/EditorConverter.pas | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/components/jcf2/ReadWrite/EditorConverter.pas b/components/jcf2/ReadWrite/EditorConverter.pas index 571ae84595..c0bd3627db 100644 --- a/components/jcf2/ReadWrite/EditorConverter.pas +++ b/components/jcf2/ReadWrite/EditorConverter.pas @@ -135,6 +135,9 @@ begin Result := pcUnit.Lines.Text; end; +{.$DEFINE NEWCODE} +{$IFDEF NEWCODE} +// This code don't keep breakpoints. procedure TEditorConverter.WriteToIDE(const pcUnit: TSourceEditorInterface; const psText: string); var lLogicalCaretXY:TPoint; @@ -165,11 +168,12 @@ begin end; end; +{$ELSE} //BUGGY: inserts empty blank lines in "random" position in the editor. // and if only one line es added or deleted after formatting then doesn't syncronize well. // i think is better change all text in the editor. // TODO: delete -{ + procedure TEditorConverter.WriteToIDE(const pcUnit: TSourceEditorInterface; const psText: string); var lcSourceLines, lcDestLines: TStrings; @@ -226,7 +230,7 @@ begin lcSameEnd.Free; end; end; -} +{$ENDIF} procedure TEditorConverter.AfterConvert; begin -- 2.29.1.windows.1 |
|
This big patch merges the changes made by the JCF into the editor, changing only the modified lines, so preserves the breakpoints. For doing the diff / merge uses the TDiff component from Angus Johnson taken from http://github.com/rickard67/TextDiff. I have attached another patch adding the author of the component to the acknowledgments of lazarus in case you consider appropriate to apply it. JCF_diff_merge.patch (103,347 bytes)
From 7632e540e5e387f5ac1fd32571d8e7c61a31e219 Mon Sep 17 00:00:00 2001 From: DomingoGP <dgalmesp@gmail.com> Date: Wed, 6 Jan 2021 18:20:16 +0100 Subject: [PATCH] Merges the changes of JCF in the editor. Only modified lines are changed. --- .../jcf2/IdePlugin/lazarus/jcfidelazarus.lpk | 1472 +++++++++-------- .../jcf2/IdePlugin/lazarus/jcfidelazarus.pas | 110 +- .../jcf2/IdePlugin/lazarus/jcfidemain.pas | 7 +- components/jcf2/ReadWrite/Converter.pas | 11 +- components/jcf2/ReadWrite/Diff.pas | 901 ++++++++++ components/jcf2/ReadWrite/EditorConverter.pas | 91 +- components/jcf2/ReadWrite/diffmerge.pas | 202 +++ 7 files changed, 1913 insertions(+), 881 deletions(-) create mode 100644 components/jcf2/ReadWrite/Diff.pas create mode 100644 components/jcf2/ReadWrite/diffmerge.pas diff --git a/components/jcf2/IdePlugin/lazarus/jcfidelazarus.lpk b/components/jcf2/IdePlugin/lazarus/jcfidelazarus.lpk index b8527f3b21..f49f0546a1 100644 --- a/components/jcf2/IdePlugin/lazarus/jcfidelazarus.lpk +++ b/components/jcf2/IdePlugin/lazarus/jcfidelazarus.lpk @@ -1,732 +1,740 @@ -<?xml version="1.0" encoding="UTF-8"?> -<CONFIG> - <Package Version="5"> - <PathDelim Value="\"/> - <Name Value="jcfidelazarus"/> - <Type Value="RunAndDesignTime"/> - <AddToProjectUsesSection Value="True"/> - <CompilerOptions> - <Version Value="11"/> - <PathDelim Value="\"/> - <SearchPaths> - <IncludeFiles Value="..\..\Include;..\..\Ui\Settings"/> - <OtherUnitFiles Value="..\..;..\..\Parse;..\..\Parse\PreProcessor;..\..\Parse\UI;..\..\Process;..\..\Process\Align;..\..\Process\Capitalisation;..\..\Process\Indent;..\..\Process\Info;..\..\Process\Obfuscate;..\..\Process\Onceoffs;..\..\Process\Returns;..\..\Process\Spacing;..\..\Process\Transform;..\..\Process\Warnings;..\..\ReadWrite;..\..\Settings;..\..\Settings\Streams;..\..\Ui;..\..\Ui\Settings;..\..\Utils;..\..\Utils\DragDrop"/> - <UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)\$(LCLWidgetType)"/> - </SearchPaths> - <Parsing> - <SyntaxOptions> - <CStyleOperator Value="False"/> - <UseAnsiStrings Value="False"/> - </SyntaxOptions> - </Parsing> - <Other> - <CustomOptions Value="$(IDEBuildOptions)"/> - </Other> - </CompilerOptions> - <Description Value="JEDI Code Format IDE Plugin for Lazarus"/> - <Version Major="2"/> - <Files Count="171"> - <Item1> - <Filename Value="jcfidemain.pas"/> - <UnitName Value="JcfIdeMain"/> - </Item1> - <Item2> - <Filename Value="jcfideregister.pas"/> - <HasRegisterProc Value="True"/> - <UnitName Value="JcfIdeRegister"/> - </Item2> - <Item3> - <Filename Value="..\..\Parse\AsmKeywords.pas"/> - <UnitName Value="AsmKeywords"/> - </Item3> - <Item4> - <Filename Value="..\..\Parse\BuildParseTree.pas"/> - <UnitName Value="BuildParseTree"/> - </Item4> - <Item5> - <Filename Value="..\..\Parse\BuildTokenList.pas"/> - <UnitName Value="BuildTokenList"/> - </Item5> - <Item6> - <Filename Value="..\..\Parse\ParseError.pas"/> - <UnitName Value="ParseError"/> - </Item6> - <Item7> - <Filename Value="..\..\Parse\ParseTreeNode.pas"/> - <UnitName Value="ParseTreeNode"/> - </Item7> - <Item8> - <Filename Value="..\..\Parse\ParseTreeNodeType.pas"/> - <UnitName Value="ParseTreeNodeType"/> - </Item8> - <Item9> - <Filename Value="..\..\Parse\PreProcessor\PreProcessorExpressionParser.pas"/> - <UnitName Value="PreProcessorExpressionParser"/> - </Item9> - <Item10> - <Filename Value="..\..\Parse\PreProcessor\PreProcessorExpressionTokenise.pas"/> - <UnitName Value="PreProcessorExpressionTokenise"/> - </Item10> - <Item11> - <Filename Value="..\..\Parse\PreProcessor\PreProcessorExpressionTokens.pas"/> - <UnitName Value="PreProcessorExpressionTokens"/> - </Item11> - <Item12> - <Filename Value="..\..\Parse\PreProcessor\PreProcessorParseTree.pas"/> - <UnitName Value="PreProcessorParseTree"/> - </Item12> - <Item13> - <Filename Value="..\..\Parse\SourceToken.pas"/> - <UnitName Value="SourceToken"/> - </Item13> - <Item14> - <Filename Value="..\..\Parse\SourceTokenList.pas"/> - <UnitName Value="SourceTokenList"/> - </Item14> - <Item15> - <Filename Value="..\..\Parse\Tokens.pas"/> - <UnitName Value="Tokens"/> - </Item15> - <Item16> - <Filename Value="..\..\Parse\TokenUtils.pas"/> - <UnitName Value="TokenUtils"/> - </Item16> - <Item17> - <Filename Value="..\..\Parse\UI\fShowParseTree.pas"/> - <UnitName Value="fShowParseTree"/> - </Item17> - <Item18> - <Filename Value="..\..\Process\Align\AlignAssign.pas"/> - <UnitName Value="AlignAssign"/> - </Item18> - <Item19> - <Filename Value="..\..\Process\Align\AlignBase.pas"/> - <UnitName Value="AlignBase"/> - </Item19> - <Item20> - <Filename Value="..\..\Process\Align\AlignComment.pas"/> - <UnitName Value="AlignComment"/> - </Item20> - <Item21> - <Filename Value="..\..\Process\Align\AlignConst.pas"/> - <UnitName Value="AlignConst"/> - </Item21> - <Item22> - <Filename Value="..\..\Process\Align\AlignField.pas"/> - <UnitName Value="AlignField"/> - </Item22> - <Item23> - <Filename Value="..\..\Process\Align\AlignTypedef.pas"/> - <UnitName Value="AlignTypedef"/> - </Item23> - <Item24> - <Filename Value="..\..\Process\Align\AlignVars.pas"/> - <UnitName Value="AlignVars"/> - </Item24> - <Item25> - <Filename Value="..\..\Process\AllProcesses.pas"/> - <UnitName Value="AllProcesses"/> - </Item25> - <Item26> - <Filename Value="..\..\Process\BaseVisitor.pas"/> - <UnitName Value="BaseVisitor"/> - </Item26> - <Item27> - <Filename Value="..\..\Process\Capitalisation\Capitalisation.pas"/> - <UnitName Value="Capitalisation"/> - </Item27> - <Item28> - <Filename Value="..\..\Process\Capitalisation\IdentifierCaps.pas"/> - <UnitName Value="IdentifierCaps"/> - </Item28> - <Item29> - <Filename Value="..\..\Process\Capitalisation\SpecificWordCaps.pas"/> - <UnitName Value="SpecificWordCaps"/> - </Item29> - <Item30> - <Filename Value="..\..\Process\Capitalisation\UnitNameCaps.pas"/> - <UnitName Value="UnitNameCaps"/> - </Item30> - <Item31> - <Filename Value="..\..\Process\FormatFlags.pas"/> - <UnitName Value="FormatFlags"/> - </Item31> - <Item32> - <Filename Value="..\..\Process\Indent\IndentAsmParam.pas"/> - <UnitName Value="IndentAsmParam"/> - </Item32> - <Item33> - <Filename Value="..\..\Process\Indent\Indenter.pas"/> - <UnitName Value="Indenter"/> - </Item33> - <Item34> - <Filename Value="..\..\Process\Info\BasicStats.pas"/> - <UnitName Value="BasicStats"/> - </Item34> - <Item35> - <Filename Value="..\..\Process\Nesting.pas"/> - <UnitName Value="Nesting"/> - </Item35> - <Item36> - <Filename Value="..\..\Process\Obfuscate\FixCase.pas"/> - <UnitName Value="FixCase"/> - </Item36> - <Item37> - <Filename Value="..\..\Process\Obfuscate\RebreakLines.pas"/> - <UnitName Value="RebreakLines"/> - </Item37> - <Item38> - <Filename Value="..\..\Process\Obfuscate\ReduceWhiteSpace.pas"/> - <UnitName Value="ReduceWhiteSpace"/> - </Item38> - <Item39> - <Filename Value="..\..\Process\Obfuscate\RemoveBlankLine.pas"/> - <UnitName Value="RemoveBlankLine"/> - </Item39> - <Item40> - <Filename Value="..\..\Process\Obfuscate\RemoveComment.pas"/> - <UnitName Value="RemoveComment"/> - </Item40> - <Item41> - <Filename Value="..\..\Process\Obfuscate\RemoveConsecutiveWhiteSpace.pas"/> - <UnitName Value="RemoveConsecutiveWhiteSpace"/> - </Item41> - <Item42> - <Filename Value="..\..\Process\Obfuscate\RemoveReturn.pas"/> - <UnitName Value="RemoveReturn"/> - </Item42> - <Item43> - <Filename Value="..\..\Process\Obfuscate\RemoveUnneededWhiteSpace.pas"/> - <UnitName Value="RemoveUnneededWhiteSpace"/> - </Item43> - <Item44> - <Filename Value="..\..\Process\Onceoffs\MozComment.pas"/> - <UnitName Value="MozComment"/> - </Item44> - <Item45> - <Filename Value="..\..\Process\RemoveEmptyComment.pas"/> - <UnitName Value="RemoveEmptyComment"/> - </Item45> - <Item46> - <Filename Value="..\..\Process\Returns\BlockStyles.pas"/> - <UnitName Value="BlockStyles"/> - </Item46> - <Item47> - <Filename Value="..\..\Process\Returns\LongLineBreaker.pas"/> - <UnitName Value="LongLineBreaker"/> - </Item47> - <Item48> - <Filename Value="..\..\Process\Returns\NoReturnAfter.pas"/> - <UnitName Value="NoReturnAfter"/> - </Item48> - <Item49> - <Filename Value="..\..\Process\Returns\NoReturnBefore.pas"/> - <UnitName Value="NoReturnBefore"/> - </Item49> - <Item50> - <Filename Value="..\..\Process\Returns\PropertyOnOneLine.pas"/> - <UnitName Value="PropertyOnOneLine"/> - </Item50> - <Item51> - <Filename Value="..\..\Process\Returns\RemoveBlankLinesAfterProcHeader.pas"/> - <UnitName Value="RemoveBlankLinesAfterProcHeader"/> - </Item51> - <Item52> - <Filename Value="..\..\Process\Returns\RemoveBlankLinesInVars.pas"/> - <UnitName Value="RemoveBlankLinesInVars"/> - </Item52> - <Item53> - <Filename Value="..\..\Process\Returns\RemoveConsecutiveReturns.pas"/> - <UnitName Value="RemoveConsecutiveReturns"/> - </Item53> - <Item54> - <Filename Value="..\..\Process\Returns\RemoveReturnsAfter.pas"/> - <UnitName Value="RemoveReturnsAfter"/> - </Item54> - <Item55> - <Filename Value="..\..\Process\Returns\RemoveReturnsAfterBegin.pas"/> - <UnitName Value="RemoveReturnsAfterBegin"/> - </Item55> - <Item56> - <Filename Value="..\..\Process\Returns\RemoveReturnsBeforeEnd.pas"/> - <UnitName Value="RemoveReturnsBeforeEnd"/> - </Item56> - <Item57> - <Filename Value="..\..\Process\Returns\ReturnAfter.pas"/> - <UnitName Value="ReturnAfter"/> - </Item57> - <Item58> - <Filename Value="..\..\Process\Returns\ReturnBefore.pas"/> - <UnitName Value="ReturnBefore"/> - </Item58> - <Item59> - <Filename Value="..\..\Process\Returns\ReturnChars.pas"/> - <UnitName Value="ReturnChars"/> - </Item59> - <Item60> - <Filename Value="..\..\Process\Returns\ReturnsAfterFinalEnd.pas"/> - <UnitName Value="ReturnsAfterFinalEnd"/> - </Item60> - <Item61> - <Filename Value="..\..\Process\Spacing\MaxSpaces.pas"/> - <UnitName Value="MaxSpaces"/> - </Item61> - <Item62> - <Filename Value="..\..\Process\Spacing\NoSpaceAfter.pas"/> - <UnitName Value="NoSpaceAfter"/> - </Item62> - <Item63> - <Filename Value="..\..\Process\Spacing\NoSpaceBefore.pas"/> - <UnitName Value="NoSpaceBefore"/> - </Item63> - <Item64> - <Filename Value="..\..\Process\Spacing\RemoveSpaceAtLineEnd.pas"/> - <UnitName Value="RemoveSpaceAtLineEnd"/> - </Item64> - <Item65> - <Filename Value="..\..\Process\Spacing\SingleSpaceAfter.pas"/> - <UnitName Value="SingleSpaceAfter"/> - </Item65> - <Item66> - <Filename Value="..\..\Process\Spacing\SingleSpaceBefore.pas"/> - <UnitName Value="SingleSpaceBefore"/> - </Item66> - <Item67> - <Filename Value="..\..\Process\Spacing\SpaceBeforeColon.pas"/> - <UnitName Value="SpaceBeforeColon"/> - </Item67> - <Item68> - <Filename Value="..\..\Process\Spacing\SpaceToTab.pas"/> - <UnitName Value="SpaceToTab"/> - </Item68> - <Item69> - <Filename Value="..\..\Process\Spacing\TabToSpace.pas"/> - <UnitName Value="TabToSpace"/> - </Item69> - <Item70> - <Filename Value="..\..\Process\SwitchableVisitor.pas"/> - <UnitName Value="SwitchableVisitor"/> - </Item70> - <Item71> - <Filename Value="..\..\Process\Transform\AddBeginEnd.pas"/> - <UnitName Value="AddBeginEnd"/> - </Item71> - <Item72> - <Filename Value="..\..\Process\Transform\AddBlockEndSemicolon.pas"/> - <UnitName Value="AddBlockEndSemicolon"/> - </Item72> - <Item73> - <Filename Value="..\..\Process\Transform\FindReplace.pas"/> - <UnitName Value="FindReplace"/> - </Item73> - <Item74> - <Filename Value="..\..\Process\Transform\SortUses.pas"/> - <UnitName Value="SortUses"/> - </Item74> - <Item75> - <Filename Value="..\..\Process\Transform\SortUsesData.pas"/> - <UnitName Value="SortUsesData"/> - </Item75> - <Item76> - <Filename Value="..\..\Process\Transform\UsesClauseFindReplace.pas"/> - <UnitName Value="UsesClauseFindReplace"/> - </Item76> - <Item77> - <Filename Value="..\..\Process\Transform\UsesClauseInsert.pas"/> - <UnitName Value="UsesClauseInsert"/> - </Item77> - <Item78> - <Filename Value="..\..\Process\Transform\UsesClauseRemove.pas"/> - <UnitName Value="UsesClauseRemove"/> - </Item78> - <Item79> - <Filename Value="..\..\Process\TreeWalker.pas"/> - <UnitName Value="TreeWalker"/> - </Item79> - <Item80> - <Filename Value="..\..\Process\VisitSetNesting.pas"/> - <UnitName Value="VisitSetNesting"/> - </Item80> - <Item81> - <Filename Value="..\..\Process\VisitSetXY.pas"/> - <UnitName Value="VisitSetXY"/> - </Item81> - <Item82> - <Filename Value="..\..\Process\VisitStripEmptySpace.pas"/> - <UnitName Value="VisitStripEmptySpace"/> - </Item82> - <Item83> - <Filename Value="..\..\Process\Warnings\WarnAssignToFunctionName.pas"/> - <UnitName Value="WarnAssignToFunctionName"/> - </Item83> - <Item84> - <Filename Value="..\..\Process\Warnings\WarnCaseNoElse.pas"/> - <UnitName Value="WarnCaseNoElse"/> - </Item84> - <Item85> - <Filename Value="..\..\Process\Warnings\WarnDestroy.pas"/> - <UnitName Value="WarnDestroy"/> - </Item85> - <Item86> - <Filename Value="..\..\Process\Warnings\WarnEmptyBlock.pas"/> - <UnitName Value="WarnEmptyBlock"/> - </Item86> - <Item87> - <Filename Value="..\..\Process\Warnings\Warning.pas"/> - <UnitName Value="Warning"/> - </Item87> - <Item88> - <Filename Value="..\..\Process\Warnings\WarnRealType.pas"/> - <UnitName Value="WarnRealType"/> - </Item88> - <Item89> - <Filename Value="..\..\Process\Warnings\WarnUnusedParam.pas"/> - <UnitName Value="WarnUnusedParam"/> - </Item89> - <Item90> - <Filename Value="..\..\ReadWrite\Converter.pas"/> - <UnitName Value="Converter"/> - </Item90> - <Item91> - <Filename Value="..\..\ReadWrite\ConvertTypes.pas"/> - <UnitName Value="ConvertTypes"/> - </Item91> - <Item92> - <Filename Value="..\..\ReadWrite\EditorConverter.pas"/> - <UnitName Value="EditorConverter"/> - </Item92> - <Item93> - <Filename Value="..\..\ReadWrite\FileConverter.pas"/> - <UnitName Value="FileConverter"/> - </Item93> - <Item94> - <Filename Value="..\..\Settings\JcfRegistrySettings.pas"/> - <UnitName Value="JcfRegistrySettings"/> - </Item94> - <Item95> - <Filename Value="..\..\Settings\JcfSetBase.pas"/> - <UnitName Value="JcfSetBase"/> - </Item95> - <Item96> - <Filename Value="..\..\Settings\JcfSettings.pas"/> - <UnitName Value="JcfSettings"/> - </Item96> - <Item97> - <Filename Value="..\..\Settings\SetAlign.pas"/> - <UnitName Value="SetAlign"/> - </Item97> - <Item98> - <Filename Value="..\..\Settings\SetAsm.pas"/> - <UnitName Value="SetAsm"/> - </Item98> - <Item99> - <Filename Value="..\..\Settings\SetCaps.pas"/> - <UnitName Value="SetCaps"/> - </Item99> - <Item100> - <Filename Value="..\..\Settings\SetClarify.pas"/> - <UnitName Value="SetClarify"/> - </Item100> - <Item101> - <Filename Value="..\..\Settings\SetComments.pas"/> - <UnitName Value="SetComments"/> - </Item101> - <Item102> - <Filename Value="..\..\Settings\SetIndent.pas"/> - <UnitName Value="SetIndent"/> - </Item102> - <Item103> - <Filename Value="..\..\Settings\SetObfuscate.pas"/> - <UnitName Value="SetObfuscate"/> - </Item103> - <Item104> - <Filename Value="..\..\Settings\SetPreProcessor.pas"/> - <UnitName Value="SetPreProcessor"/> - </Item104> - <Item105> - <Filename Value="..\..\Settings\SetReplace.pas"/> - <UnitName Value="SetReplace"/> - </Item105> - <Item106> - <Filename Value="..\..\Settings\SetReturns.pas"/> - <UnitName Value="SetReturns"/> - </Item106> - <Item107> - <Filename Value="..\..\Settings\SetSpaces.pas"/> - <UnitName Value="SetSpaces"/> - </Item107> - <Item108> - <Filename Value="..\..\Settings\SettingsTypes.pas"/> - <UnitName Value="SettingsTypes"/> - </Item108> - <Item109> - <Filename Value="..\..\Settings\SetTransform.pas"/> - <UnitName Value="SetTransform"/> - </Item109> - <Item110> - <Filename Value="..\..\Settings\SetUses.pas"/> - <UnitName Value="SetUses"/> - </Item110> - <Item111> - <Filename Value="..\..\Settings\SetWordList.pas"/> - <UnitName Value="SetWordList"/> - </Item111> - <Item112> - <Filename Value="..\..\Settings\Streams\SettingsStream.pas"/> - <UnitName Value="SettingsStream"/> - </Item112> - <Item113> - <Filename Value="..\..\Ui\fJcfErrorDisplay.pas"/> - <UnitName Value="fJcfErrorDisplay"/> - </Item113> - <Item114> - <Filename Value="..\..\Utils\Delay.pas"/> - <UnitName Value="Delay"/> - </Item114> - <Item115> - <Filename Value="..\..\Utils\IntList.pas"/> - <UnitName Value="IntList"/> - </Item115> - <Item116> - <Filename Value="..\..\Utils\JcfFontSetFunctions.pas"/> - <UnitName Value="JcfFontSetFunctions"/> - </Item116> - <Item117> - <Filename Value="..\..\Utils\JcfHelp.pas"/> - <UnitName Value="JcfHelp"/> - </Item117> - <Item118> - <Filename Value="..\..\Utils\JcfLog.pas"/> - <UnitName Value="JcfLog"/> - </Item118> - <Item119> - <Filename Value="..\..\Utils\JcfMiscFunctions.pas"/> - <UnitName Value="JcfMiscFunctions"/> - </Item119> - <Item120> - <Filename Value="..\..\Ui\fAbout.lfm"/> - <Type Value="LFM"/> - </Item120> - <Item121> - <Filename Value="..\..\Ui\fAbout.pas"/> - <UnitName Value="fAbout"/> - </Item121> - <Item122> - <Filename Value="..\..\JcfVersionConsts.pas"/> - <UnitName Value="JcfVersionConsts"/> - </Item122> - <Item123> - <Filename Value="..\..\Ui\Settings\frFiles.lfm"/> - <Type Value="LFM"/> - </Item123> - <Item124> - <Filename Value="..\..\Ui\Settings\frFiles.pas"/> - <UnitName Value="frFiles"/> - </Item124> - <Item125> - <Filename Value="..\..\Ui\Settings\frObfuscateSettings.lfm"/> - <Type Value="LFM"/> - </Item125> - <Item126> - <Filename Value="..\..\Ui\Settings\frObfuscateSettings.pas"/> - <UnitName Value="frObfuscateSettings"/> - </Item126> - <Item127> - <Filename Value="..\..\Ui\Settings\frClarify.lfm"/> - <Type Value="LFM"/> - </Item127> - <Item128> - <Filename Value="..\..\Ui\Settings\frClarify.pas"/> - <UnitName Value="frClarify"/> - </Item128> - <Item129> - <Filename Value="..\..\Ui\Settings\frClarifySpaces.lfm"/> - <Type Value="LFM"/> - </Item129> - <Item130> - <Filename Value="..\..\Ui\Settings\frClarifySpaces.pas"/> - <UnitName Value="frClarifySpaces"/> - </Item130> - <Item131> - <Filename Value="..\..\Ui\Settings\frClarifyIndent.lfm"/> - <Type Value="LFM"/> - </Item131> - <Item132> - <Filename Value="..\..\Ui\Settings\frClarifyIndent.pas"/> - <UnitName Value="frClarifyIndent"/> - </Item132> - <Item133> - <Filename Value="..\..\Ui\Settings\frBlankLines.lfm"/> - <Type Value="LFM"/> - </Item133> - <Item134> - <Filename Value="..\..\Ui\Settings\frBlankLines.pas"/> - <UnitName Value="frBlankLines"/> - </Item134> - <Item135> - <Filename Value="..\..\Ui\Settings\frClarifyAlign.lfm"/> - <Type Value="LFM"/> - </Item135> - <Item136> - <Filename Value="..\..\Ui\Settings\frClarifyAlign.pas"/> - <UnitName Value="frClarifyAlign"/> - </Item136> - <Item137> - <Filename Value="..\..\Ui\Settings\frClarifyLongLineBreaker.lfm"/> - <Type Value="LFM"/> - </Item137> - <Item138> - <Filename Value="..\..\Ui\Settings\frClarifyLongLineBreaker.pas"/> - <UnitName Value="frClarifyLongLineBreaker"/> - </Item138> - <Item139> - <Filename Value="..\..\Ui\Settings\frClarifyReturns.lfm"/> - <Type Value="LFM"/> - </Item139> - <Item140> - <Filename Value="..\..\Ui\Settings\frClarifyReturns.pas"/> - <UnitName Value="frClarifyReturns"/> - </Item140> - <Item141> - <Filename Value="..\..\Ui\Settings\frCompilerDirectReturns.lfm"/> - <Type Value="LFM"/> - </Item141> - <Item142> - <Filename Value="..\..\Ui\Settings\frCompilerDirectReturns.pas"/> - <UnitName Value="frCompilerDirectReturns"/> - </Item142> - <Item143> - <Filename Value="..\..\Ui\Settings\frClarifyBlocks.lfm"/> - <Type Value="LFM"/> - </Item143> - <Item144> - <Filename Value="..\..\Ui\Settings\frClarifyBlocks.pas"/> - <UnitName Value="frClarifyBlocks"/> - </Item144> - <Item145> - <Filename Value="..\..\Ui\Settings\frClarifyCaseBlocks.lfm"/> - <Type Value="LFM"/> - </Item145> - <Item146> - <Filename Value="..\..\Ui\Settings\frClarifyCaseBlocks.pas"/> - <UnitName Value="frClarifyCaseBlocks"/> - </Item146> - <Item147> - <Filename Value="..\..\Ui\Settings\frComments.lfm"/> - <Type Value="LFM"/> - </Item147> - <Item148> - <Filename Value="..\..\Ui\Settings\frComments.pas"/> - <UnitName Value="frComments"/> - </Item148> - <Item149> - <Filename Value="..\..\Ui\Settings\frWarnings.lfm"/> - <Type Value="LFM"/> - </Item149> - <Item150> - <Filename Value="..\..\Ui\Settings\frWarnings.pas"/> - <UnitName Value="frWarnings"/> - </Item150> - <Item151> - <Filename Value="..\..\Ui\Settings\frReservedCapsSettings.lfm"/> - <Type Value="LFM"/> - </Item151> - <Item152> - <Filename Value="..\..\Ui\Settings\frReservedCapsSettings.pas"/> - <UnitName Value="frReservedCapsSettings"/> - </Item152> - <Item153> - <Filename Value="..\..\Ui\Settings\frAnyCapsSettings.lfm"/> - <Type Value="LFM"/> - </Item153> - <Item154> - <Filename Value="..\..\Ui\Settings\frAnyCapsSettings.pas"/> - <UnitName Value="frAnyCapsSettings"/> - </Item154> - <Item155> - <Filename Value="..\..\Ui\Settings\frIdentifierCapsSettings.lfm"/> - <Type Value="LFM"/> - </Item155> - <Item156> - <Filename Value="..\..\Ui\Settings\frIdentifierCapsSettings.pas"/> - <UnitName Value="frIdentifierCapsSettings"/> - </Item156> - <Item157> - <Filename Value="..\..\Ui\Settings\frNotIdentifierCapsSettings.lfm"/> - <Type Value="LFM"/> - </Item157> - <Item158> - <Filename Value="..\..\Ui\Settings\frNotIdentifierCapsSettings.pas"/> - <UnitName Value="frNotIdentifierCapsSettings"/> - </Item158> - <Item159> - <Filename Value="..\..\Ui\Settings\frUnitCaps.lfm"/> - <Type Value="LFM"/> - </Item159> - <Item160> - <Filename Value="..\..\Ui\Settings\frUnitCaps.pas"/> - <UnitName Value="frUnitCaps"/> - </Item160> - <Item161> - <Filename Value="..\..\Ui\Settings\frReplace.lfm"/> - <Type Value="LFM"/> - </Item161> - <Item162> - <Filename Value="..\..\Ui\Settings\frReplace.pas"/> - <UnitName Value="frReplace"/> - </Item162> - <Item163> - <Filename Value="..\..\Ui\Settings\frUses.lfm"/> - <Type Value="LFM"/> - </Item163> - <Item164> - <Filename Value="..\..\Ui\Settings\frUses.pas"/> - <UnitName Value="frUses"/> - </Item164> - <Item165> - <Filename Value="..\..\Ui\Settings\frTransform.lfm"/> - <Type Value="LFM"/> - </Item165> - <Item166> - <Filename Value="..\..\Ui\Settings\frTransform.pas"/> - <UnitName Value="frTransform"/> - </Item166> - <Item167> - <Filename Value="..\..\Ui\Settings\frAsm.lfm"/> - <Type Value="LFM"/> - </Item167> - <Item168> - <Filename Value="..\..\Ui\Settings\frAsm.pas"/> - <UnitName Value="frAsm"/> - </Item168> - <Item169> - <Filename Value="..\..\Ui\Settings\frPreProcessor.lfm"/> - <Type Value="LFM"/> - </Item169> - <Item170> - <Filename Value="..\..\Ui\Settings\frPreProcessor.pas"/> - <UnitName Value="frPreProcessor"/> - </Item170> - <Item171> - <Filename Value="jcfuiconsts.pas"/> - <UnitName Value="JcfUIConsts"/> - </Item171> - </Files> - <CompatibilityMode Value="True"/> - <i18n> - <EnableI18N Value="True"/> - <OutDir Value="languages"/> - </i18n> - <RequiredPkgs Count="1"> - <Item1> - <PackageName Value="IDEIntf"/> - </Item1> - </RequiredPkgs> - <UsageOptions> - <UnitPath Value="$(PkgOutDir)"/> - </UsageOptions> - <PublishOptions> - <Version Value="2"/> - </PublishOptions> - </Package> -</CONFIG> +<?xml version="1.0" encoding="UTF-8"?> +<CONFIG> + <Package Version="5"> + <PathDelim Value="\"/> + <Name Value="jcfidelazarus"/> + <Type Value="RunAndDesignTime"/> + <AddToProjectUsesSection Value="True"/> + <CompilerOptions> + <Version Value="11"/> + <PathDelim Value="\"/> + <SearchPaths> + <IncludeFiles Value="..\..\Include;..\..\Ui\Settings"/> + <OtherUnitFiles Value="..\..;..\..\Parse;..\..\Parse\PreProcessor;..\..\Parse\UI;..\..\Process;..\..\Process\Align;..\..\Process\Capitalisation;..\..\Process\Indent;..\..\Process\Info;..\..\Process\Obfuscate;..\..\Process\Onceoffs;..\..\Process\Returns;..\..\Process\Spacing;..\..\Process\Transform;..\..\Process\Warnings;..\..\ReadWrite;..\..\Settings;..\..\Settings\Streams;..\..\Ui;..\..\Ui\Settings;..\..\Utils;..\..\Utils\DragDrop"/> + <UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)\$(LCLWidgetType)"/> + </SearchPaths> + <Parsing> + <SyntaxOptions> + <CStyleOperator Value="False"/> + <UseAnsiStrings Value="False"/> + </SyntaxOptions> + </Parsing> + <Other> + <CustomOptions Value="$(IDEBuildOptions)"/> + </Other> + </CompilerOptions> + <Description Value="JEDI Code Format IDE Plugin for Lazarus"/> + <Version Major="2"/> + <Files Count="173"> + <Item1> + <Filename Value="jcfidemain.pas"/> + <UnitName Value="JcfIdeMain"/> + </Item1> + <Item2> + <Filename Value="jcfideregister.pas"/> + <HasRegisterProc Value="True"/> + <UnitName Value="JcfIdeRegister"/> + </Item2> + <Item3> + <Filename Value="..\..\Parse\AsmKeywords.pas"/> + <UnitName Value="AsmKeywords"/> + </Item3> + <Item4> + <Filename Value="..\..\Parse\BuildParseTree.pas"/> + <UnitName Value="BuildParseTree"/> + </Item4> + <Item5> + <Filename Value="..\..\Parse\BuildTokenList.pas"/> + <UnitName Value="BuildTokenList"/> + </Item5> + <Item6> + <Filename Value="..\..\Parse\ParseError.pas"/> + <UnitName Value="ParseError"/> + </Item6> + <Item7> + <Filename Value="..\..\Parse\ParseTreeNode.pas"/> + <UnitName Value="ParseTreeNode"/> + </Item7> + <Item8> + <Filename Value="..\..\Parse\ParseTreeNodeType.pas"/> + <UnitName Value="ParseTreeNodeType"/> + </Item8> + <Item9> + <Filename Value="..\..\Parse\PreProcessor\PreProcessorExpressionParser.pas"/> + <UnitName Value="PreProcessorExpressionParser"/> + </Item9> + <Item10> + <Filename Value="..\..\Parse\PreProcessor\PreProcessorExpressionTokenise.pas"/> + <UnitName Value="PreProcessorExpressionTokenise"/> + </Item10> + <Item11> + <Filename Value="..\..\Parse\PreProcessor\PreProcessorExpressionTokens.pas"/> + <UnitName Value="PreProcessorExpressionTokens"/> + </Item11> + <Item12> + <Filename Value="..\..\Parse\PreProcessor\PreProcessorParseTree.pas"/> + <UnitName Value="PreProcessorParseTree"/> + </Item12> + <Item13> + <Filename Value="..\..\Parse\SourceToken.pas"/> + <UnitName Value="SourceToken"/> + </Item13> + <Item14> + <Filename Value="..\..\Parse\SourceTokenList.pas"/> + <UnitName Value="SourceTokenList"/> + </Item14> + <Item15> + <Filename Value="..\..\Parse\Tokens.pas"/> + <UnitName Value="Tokens"/> + </Item15> + <Item16> + <Filename Value="..\..\Parse\TokenUtils.pas"/> + <UnitName Value="TokenUtils"/> + </Item16> + <Item17> + <Filename Value="..\..\Parse\UI\fShowParseTree.pas"/> + <UnitName Value="fShowParseTree"/> + </Item17> + <Item18> + <Filename Value="..\..\Process\Align\AlignAssign.pas"/> + <UnitName Value="AlignAssign"/> + </Item18> + <Item19> + <Filename Value="..\..\Process\Align\AlignBase.pas"/> + <UnitName Value="AlignBase"/> + </Item19> + <Item20> + <Filename Value="..\..\Process\Align\AlignComment.pas"/> + <UnitName Value="AlignComment"/> + </Item20> + <Item21> + <Filename Value="..\..\Process\Align\AlignConst.pas"/> + <UnitName Value="AlignConst"/> + </Item21> + <Item22> + <Filename Value="..\..\Process\Align\AlignField.pas"/> + <UnitName Value="AlignField"/> + </Item22> + <Item23> + <Filename Value="..\..\Process\Align\AlignTypedef.pas"/> + <UnitName Value="AlignTypedef"/> + </Item23> + <Item24> + <Filename Value="..\..\Process\Align\AlignVars.pas"/> + <UnitName Value="AlignVars"/> + </Item24> + <Item25> + <Filename Value="..\..\Process\AllProcesses.pas"/> + <UnitName Value="AllProcesses"/> + </Item25> + <Item26> + <Filename Value="..\..\Process\BaseVisitor.pas"/> + <UnitName Value="BaseVisitor"/> + </Item26> + <Item27> + <Filename Value="..\..\Process\Capitalisation\Capitalisation.pas"/> + <UnitName Value="Capitalisation"/> + </Item27> + <Item28> + <Filename Value="..\..\Process\Capitalisation\IdentifierCaps.pas"/> + <UnitName Value="IdentifierCaps"/> + </Item28> + <Item29> + <Filename Value="..\..\Process\Capitalisation\SpecificWordCaps.pas"/> + <UnitName Value="SpecificWordCaps"/> + </Item29> + <Item30> + <Filename Value="..\..\Process\Capitalisation\UnitNameCaps.pas"/> + <UnitName Value="UnitNameCaps"/> + </Item30> + <Item31> + <Filename Value="..\..\Process\FormatFlags.pas"/> + <UnitName Value="FormatFlags"/> + </Item31> + <Item32> + <Filename Value="..\..\Process\Indent\IndentAsmParam.pas"/> + <UnitName Value="IndentAsmParam"/> + </Item32> + <Item33> + <Filename Value="..\..\Process\Indent\Indenter.pas"/> + <UnitName Value="Indenter"/> + </Item33> + <Item34> + <Filename Value="..\..\Process\Info\BasicStats.pas"/> + <UnitName Value="BasicStats"/> + </Item34> + <Item35> + <Filename Value="..\..\Process\Nesting.pas"/> + <UnitName Value="Nesting"/> + </Item35> + <Item36> + <Filename Value="..\..\Process\Obfuscate\FixCase.pas"/> + <UnitName Value="FixCase"/> + </Item36> + <Item37> + <Filename Value="..\..\Process\Obfuscate\RebreakLines.pas"/> + <UnitName Value="RebreakLines"/> + </Item37> + <Item38> + <Filename Value="..\..\Process\Obfuscate\ReduceWhiteSpace.pas"/> + <UnitName Value="ReduceWhiteSpace"/> + </Item38> + <Item39> + <Filename Value="..\..\Process\Obfuscate\RemoveBlankLine.pas"/> + <UnitName Value="RemoveBlankLine"/> + </Item39> + <Item40> + <Filename Value="..\..\Process\Obfuscate\RemoveComment.pas"/> + <UnitName Value="RemoveComment"/> + </Item40> + <Item41> + <Filename Value="..\..\Process\Obfuscate\RemoveConsecutiveWhiteSpace.pas"/> + <UnitName Value="RemoveConsecutiveWhiteSpace"/> + </Item41> + <Item42> + <Filename Value="..\..\Process\Obfuscate\RemoveReturn.pas"/> + <UnitName Value="RemoveReturn"/> + </Item42> + <Item43> + <Filename Value="..\..\Process\Obfuscate\RemoveUnneededWhiteSpace.pas"/> + <UnitName Value="RemoveUnneededWhiteSpace"/> + </Item43> + <Item44> + <Filename Value="..\..\Process\Onceoffs\MozComment.pas"/> + <UnitName Value="MozComment"/> + </Item44> + <Item45> + <Filename Value="..\..\Process\RemoveEmptyComment.pas"/> + <UnitName Value="RemoveEmptyComment"/> + </Item45> + <Item46> + <Filename Value="..\..\Process\Returns\BlockStyles.pas"/> + <UnitName Value="BlockStyles"/> + </Item46> + <Item47> + <Filename Value="..\..\Process\Returns\LongLineBreaker.pas"/> + <UnitName Value="LongLineBreaker"/> + </Item47> + <Item48> + <Filename Value="..\..\Process\Returns\NoReturnAfter.pas"/> + <UnitName Value="NoReturnAfter"/> + </Item48> + <Item49> + <Filename Value="..\..\Process\Returns\NoReturnBefore.pas"/> + <UnitName Value="NoReturnBefore"/> + </Item49> + <Item50> + <Filename Value="..\..\Process\Returns\PropertyOnOneLine.pas"/> + <UnitName Value="PropertyOnOneLine"/> + </Item50> + <Item51> + <Filename Value="..\..\Process\Returns\RemoveBlankLinesAfterProcHeader.pas"/> + <UnitName Value="RemoveBlankLinesAfterProcHeader"/> + </Item51> + <Item52> + <Filename Value="..\..\Process\Returns\RemoveBlankLinesInVars.pas"/> + <UnitName Value="RemoveBlankLinesInVars"/> + </Item52> + <Item53> + <Filename Value="..\..\Process\Returns\RemoveConsecutiveReturns.pas"/> + <UnitName Value="RemoveConsecutiveReturns"/> + </Item53> + <Item54> + <Filename Value="..\..\Process\Returns\RemoveReturnsAfter.pas"/> + <UnitName Value="RemoveReturnsAfter"/> + </Item54> + <Item55> + <Filename Value="..\..\Process\Returns\RemoveReturnsAfterBegin.pas"/> + <UnitName Value="RemoveReturnsAfterBegin"/> + </Item55> + <Item56> + <Filename Value="..\..\Process\Returns\RemoveReturnsBeforeEnd.pas"/> + <UnitName Value="RemoveReturnsBeforeEnd"/> + </Item56> + <Item57> + <Filename Value="..\..\Process\Returns\ReturnAfter.pas"/> + <UnitName Value="ReturnAfter"/> + </Item57> + <Item58> + <Filename Value="..\..\Process\Returns\ReturnBefore.pas"/> + <UnitName Value="ReturnBefore"/> + </Item58> + <Item59> + <Filename Value="..\..\Process\Returns\ReturnChars.pas"/> + <UnitName Value="ReturnChars"/> + </Item59> + <Item60> + <Filename Value="..\..\Process\Returns\ReturnsAfterFinalEnd.pas"/> + <UnitName Value="ReturnsAfterFinalEnd"/> + </Item60> + <Item61> + <Filename Value="..\..\Process\Spacing\MaxSpaces.pas"/> + <UnitName Value="MaxSpaces"/> + </Item61> + <Item62> + <Filename Value="..\..\Process\Spacing\NoSpaceAfter.pas"/> + <UnitName Value="NoSpaceAfter"/> + </Item62> + <Item63> + <Filename Value="..\..\Process\Spacing\NoSpaceBefore.pas"/> + <UnitName Value="NoSpaceBefore"/> + </Item63> + <Item64> + <Filename Value="..\..\Process\Spacing\RemoveSpaceAtLineEnd.pas"/> + <UnitName Value="RemoveSpaceAtLineEnd"/> + </Item64> + <Item65> + <Filename Value="..\..\Process\Spacing\SingleSpaceAfter.pas"/> + <UnitName Value="SingleSpaceAfter"/> + </Item65> + <Item66> + <Filename Value="..\..\Process\Spacing\SingleSpaceBefore.pas"/> + <UnitName Value="SingleSpaceBefore"/> + </Item66> + <Item67> + <Filename Value="..\..\Process\Spacing\SpaceBeforeColon.pas"/> + <UnitName Value="SpaceBeforeColon"/> + </Item67> + <Item68> + <Filename Value="..\..\Process\Spacing\SpaceToTab.pas"/> + <UnitName Value="SpaceToTab"/> + </Item68> + <Item69> + <Filename Value="..\..\Process\Spacing\TabToSpace.pas"/> + <UnitName Value="TabToSpace"/> + </Item69> + <Item70> + <Filename Value="..\..\Process\SwitchableVisitor.pas"/> + <UnitName Value="SwitchableVisitor"/> + </Item70> + <Item71> + <Filename Value="..\..\Process\Transform\AddBeginEnd.pas"/> + <UnitName Value="AddBeginEnd"/> + </Item71> + <Item72> + <Filename Value="..\..\Process\Transform\AddBlockEndSemicolon.pas"/> + <UnitName Value="AddBlockEndSemicolon"/> + </Item72> + <Item73> + <Filename Value="..\..\Process\Transform\FindReplace.pas"/> + <UnitName Value="FindReplace"/> + </Item73> + <Item74> + <Filename Value="..\..\Process\Transform\SortUses.pas"/> + <UnitName Value="SortUses"/> + </Item74> + <Item75> + <Filename Value="..\..\Process\Transform\SortUsesData.pas"/> + <UnitName Value="SortUsesData"/> + </Item75> + <Item76> + <Filename Value="..\..\Process\Transform\UsesClauseFindReplace.pas"/> + <UnitName Value="UsesClauseFindReplace"/> + </Item76> + <Item77> + <Filename Value="..\..\Process\Transform\UsesClauseInsert.pas"/> + <UnitName Value="UsesClauseInsert"/> + </Item77> + <Item78> + <Filename Value="..\..\Process\Transform\UsesClauseRemove.pas"/> + <UnitName Value="UsesClauseRemove"/> + </Item78> + <Item79> + <Filename Value="..\..\Process\TreeWalker.pas"/> + <UnitName Value="TreeWalker"/> + </Item79> + <Item80> + <Filename Value="..\..\Process\VisitSetNesting.pas"/> + <UnitName Value="VisitSetNesting"/> + </Item80> + <Item81> + <Filename Value="..\..\Process\VisitSetXY.pas"/> + <UnitName Value="VisitSetXY"/> + </Item81> + <Item82> + <Filename Value="..\..\Process\VisitStripEmptySpace.pas"/> + <UnitName Value="VisitStripEmptySpace"/> + </Item82> + <Item83> + <Filename Value="..\..\Process\Warnings\WarnAssignToFunctionName.pas"/> + <UnitName Value="WarnAssignToFunctionName"/> + </Item83> + <Item84> + <Filename Value="..\..\Process\Warnings\WarnCaseNoElse.pas"/> + <UnitName Value="WarnCaseNoElse"/> + </Item84> + <Item85> + <Filename Value="..\..\Process\Warnings\WarnDestroy.pas"/> + <UnitName Value="WarnDestroy"/> + </Item85> + <Item86> + <Filename Value="..\..\Process\Warnings\WarnEmptyBlock.pas"/> + <UnitName Value="WarnEmptyBlock"/> + </Item86> + <Item87> + <Filename Value="..\..\Process\Warnings\Warning.pas"/> + <UnitName Value="Warning"/> + </Item87> + <Item88> + <Filename Value="..\..\Process\Warnings\WarnRealType.pas"/> + <UnitName Value="WarnRealType"/> + </Item88> + <Item89> + <Filename Value="..\..\Process\Warnings\WarnUnusedParam.pas"/> + <UnitName Value="WarnUnusedParam"/> + </Item89> + <Item90> + <Filename Value="..\..\ReadWrite\Converter.pas"/> + <UnitName Value="Converter"/> + </Item90> + <Item91> + <Filename Value="..\..\ReadWrite\ConvertTypes.pas"/> + <UnitName Value="ConvertTypes"/> + </Item91> + <Item92> + <Filename Value="..\..\ReadWrite\EditorConverter.pas"/> + <UnitName Value="EditorConverter"/> + </Item92> + <Item93> + <Filename Value="..\..\ReadWrite\FileConverter.pas"/> + <UnitName Value="FileConverter"/> + </Item93> + <Item94> + <Filename Value="..\..\Settings\JcfRegistrySettings.pas"/> + <UnitName Value="JcfRegistrySettings"/> + </Item94> + <Item95> + <Filename Value="..\..\Settings\JcfSetBase.pas"/> + <UnitName Value="JcfSetBase"/> + </Item95> + <Item96> + <Filename Value="..\..\Settings\JcfSettings.pas"/> + <UnitName Value="JcfSettings"/> + </Item96> + <Item97> + <Filename Value="..\..\Settings\SetAlign.pas"/> + <UnitName Value="SetAlign"/> + </Item97> + <Item98> + <Filename Value="..\..\Settings\SetAsm.pas"/> + <UnitName Value="SetAsm"/> + </Item98> + <Item99> + <Filename Value="..\..\Settings\SetCaps.pas"/> + <UnitName Value="SetCaps"/> + </Item99> + <Item100> + <Filename Value="..\..\Settings\SetClarify.pas"/> + <UnitName Value="SetClarify"/> + </Item100> + <Item101> + <Filename Value="..\..\Settings\SetComments.pas"/> + <UnitName Value="SetComments"/> + </Item101> + <Item102> + <Filename Value="..\..\Settings\SetIndent.pas"/> + <UnitName Value="SetIndent"/> + </Item102> + <Item103> + <Filename Value="..\..\Settings\SetObfuscate.pas"/> + <UnitName Value="SetObfuscate"/> + </Item103> + <Item104> + <Filename Value="..\..\Settings\SetPreProcessor.pas"/> + <UnitName Value="SetPreProcessor"/> + </Item104> + <Item105> + <Filename Value="..\..\Settings\SetReplace.pas"/> + <UnitName Value="SetReplace"/> + </Item105> + <Item106> + <Filename Value="..\..\Settings\SetReturns.pas"/> + <UnitName Value="SetReturns"/> + </Item106> + <Item107> + <Filename Value="..\..\Settings\SetSpaces.pas"/> + <UnitName Value="SetSpaces"/> + </Item107> + <Item108> + <Filename Value="..\..\Settings\SettingsTypes.pas"/> + <UnitName Value="SettingsTypes"/> + </Item108> + <Item109> + <Filename Value="..\..\Settings\SetTransform.pas"/> + <UnitName Value="SetTransform"/> + </Item109> + <Item110> + <Filename Value="..\..\Settings\SetUses.pas"/> + <UnitName Value="SetUses"/> + </Item110> + <Item111> + <Filename Value="..\..\Settings\SetWordList.pas"/> + <UnitName Value="SetWordList"/> + </Item111> + <Item112> + <Filename Value="..\..\Settings\Streams\SettingsStream.pas"/> + <UnitName Value="SettingsStream"/> + </Item112> + <Item113> + <Filename Value="..\..\Ui\fJcfErrorDisplay.pas"/> + <UnitName Value="fJcfErrorDisplay"/> + </Item113> + <Item114> + <Filename Value="..\..\Utils\Delay.pas"/> + <UnitName Value="Delay"/> + </Item114> + <Item115> + <Filename Value="..\..\Utils\IntList.pas"/> + <UnitName Value="IntList"/> + </Item115> + <Item116> + <Filename Value="..\..\Utils\JcfFontSetFunctions.pas"/> + <UnitName Value="JcfFontSetFunctions"/> + </Item116> + <Item117> + <Filename Value="..\..\Utils\JcfHelp.pas"/> + <UnitName Value="JcfHelp"/> + </Item117> + <Item118> + <Filename Value="..\..\Utils\JcfLog.pas"/> + <UnitName Value="JcfLog"/> + </Item118> + <Item119> + <Filename Value="..\..\Utils\JcfMiscFunctions.pas"/> + <UnitName Value="JcfMiscFunctions"/> + </Item119> + <Item120> + <Filename Value="..\..\Ui\fAbout.lfm"/> + <Type Value="LFM"/> + </Item120> + <Item121> + <Filename Value="..\..\Ui\fAbout.pas"/> + <UnitName Value="fAbout"/> + </Item121> + <Item122> + <Filename Value="..\..\JcfVersionConsts.pas"/> + <UnitName Value="JcfVersionConsts"/> + </Item122> + <Item123> + <Filename Value="..\..\Ui\Settings\frFiles.lfm"/> + <Type Value="LFM"/> + </Item123> + <Item124> + <Filename Value="..\..\Ui\Settings\frFiles.pas"/> + <UnitName Value="frFiles"/> + </Item124> + <Item125> + <Filename Value="..\..\Ui\Settings\frObfuscateSettings.lfm"/> + <Type Value="LFM"/> + </Item125> + <Item126> + <Filename Value="..\..\Ui\Settings\frObfuscateSettings.pas"/> + <UnitName Value="frObfuscateSettings"/> + </Item126> + <Item127> + <Filename Value="..\..\Ui\Settings\frClarify.lfm"/> + <Type Value="LFM"/> + </Item127> + <Item128> + <Filename Value="..\..\Ui\Settings\frClarify.pas"/> + <UnitName Value="frClarify"/> + </Item128> + <Item129> + <Filename Value="..\..\Ui\Settings\frClarifySpaces.lfm"/> + <Type Value="LFM"/> + </Item129> + <Item130> + <Filename Value="..\..\Ui\Settings\frClarifySpaces.pas"/> + <UnitName Value="frClarifySpaces"/> + </Item130> + <Item131> + <Filename Value="..\..\Ui\Settings\frClarifyIndent.lfm"/> + <Type Value="LFM"/> + </Item131> + <Item132> + <Filename Value="..\..\Ui\Settings\frClarifyIndent.pas"/> + <UnitName Value="frClarifyIndent"/> + </Item132> + <Item133> + <Filename Value="..\..\Ui\Settings\frBlankLines.lfm"/> + <Type Value="LFM"/> + </Item133> + <Item134> + <Filename Value="..\..\Ui\Settings\frBlankLines.pas"/> + <UnitName Value="frBlankLines"/> + </Item134> + <Item135> + <Filename Value="..\..\Ui\Settings\frClarifyAlign.lfm"/> + <Type Value="LFM"/> + </Item135> + <Item136> + <Filename Value="..\..\Ui\Settings\frClarifyAlign.pas"/> + <UnitName Value="frClarifyAlign"/> + </Item136> + <Item137> + <Filename Value="..\..\Ui\Settings\frClarifyLongLineBreaker.lfm"/> + <Type Value="LFM"/> + </Item137> + <Item138> + <Filename Value="..\..\Ui\Settings\frClarifyLongLineBreaker.pas"/> + <UnitName Value="frClarifyLongLineBreaker"/> + </Item138> + <Item139> + <Filename Value="..\..\Ui\Settings\frClarifyReturns.lfm"/> + <Type Value="LFM"/> + </Item139> + <Item140> + <Filename Value="..\..\Ui\Settings\frClarifyReturns.pas"/> + <UnitName Value="frClarifyReturns"/> + </Item140> + <Item141> + <Filename Value="..\..\Ui\Settings\frCompilerDirectReturns.lfm"/> + <Type Value="LFM"/> + </Item141> + <Item142> + <Filename Value="..\..\Ui\Settings\frCompilerDirectReturns.pas"/> + <UnitName Value="frCompilerDirectReturns"/> + </Item142> + <Item143> + <Filename Value="..\..\Ui\Settings\frClarifyBlocks.lfm"/> + <Type Value="LFM"/> + </Item143> + <Item144> + <Filename Value="..\..\Ui\Settings\frClarifyBlocks.pas"/> + <UnitName Value="frClarifyBlocks"/> + </Item144> + <Item145> + <Filename Value="..\..\Ui\Settings\frClarifyCaseBlocks.lfm"/> + <Type Value="LFM"/> + </Item145> + <Item146> + <Filename Value="..\..\Ui\Settings\frClarifyCaseBlocks.pas"/> + <UnitName Value="frClarifyCaseBlocks"/> + </Item146> + <Item147> + <Filename Value="..\..\Ui\Settings\frComments.lfm"/> + <Type Value="LFM"/> + </Item147> + <Item148> + <Filename Value="..\..\Ui\Settings\frComments.pas"/> + <UnitName Value="frComments"/> + </Item148> + <Item149> + <Filename Value="..\..\Ui\Settings\frWarnings.lfm"/> + <Type Value="LFM"/> + </Item149> + <Item150> + <Filename Value="..\..\Ui\Settings\frWarnings.pas"/> + <UnitName Value="frWarnings"/> + </Item150> + <Item151> + <Filename Value="..\..\Ui\Settings\frReservedCapsSettings.lfm"/> + <Type Value="LFM"/> + </Item151> + <Item152> + <Filename Value="..\..\Ui\Settings\frReservedCapsSettings.pas"/> + <UnitName Value="frReservedCapsSettings"/> + </Item152> + <Item153> + <Filename Value="..\..\Ui\Settings\frAnyCapsSettings.lfm"/> + <Type Value="LFM"/> + </Item153> + <Item154> + <Filename Value="..\..\Ui\Settings\frAnyCapsSettings.pas"/> + <UnitName Value="frAnyCapsSettings"/> + </Item154> + <Item155> + <Filename Value="..\..\Ui\Settings\frIdentifierCapsSettings.lfm"/> + <Type Value="LFM"/> + </Item155> + <Item156> + <Filename Value="..\..\Ui\Settings\frIdentifierCapsSettings.pas"/> + <UnitName Value="frIdentifierCapsSettings"/> + </Item156> + <Item157> + <Filename Value="..\..\Ui\Settings\frNotIdentifierCapsSettings.lfm"/> + <Type Value="LFM"/> + </Item157> + <Item158> + <Filename Value="..\..\Ui\Settings\frNotIdentifierCapsSettings.pas"/> + <UnitName Value="frNotIdentifierCapsSettings"/> + </Item158> + <Item159> + <Filename Value="..\..\Ui\Settings\frUnitCaps.lfm"/> + <Type Value="LFM"/> + </Item159> + <Item160> + <Filename Value="..\..\Ui\Settings\frUnitCaps.pas"/> + <UnitName Value="frUnitCaps"/> + </Item160> + <Item161> + <Filename Value="..\..\Ui\Settings\frReplace.lfm"/> + <Type Value="LFM"/> + </Item161> + <Item162> + <Filename Value="..\..\Ui\Settings\frReplace.pas"/> + <UnitName Value="frReplace"/> + </Item162> + <Item163> + <Filename Value="..\..\Ui\Settings\frUses.lfm"/> + <Type Value="LFM"/> + </Item163> + <Item164> + <Filename Value="..\..\Ui\Settings\frUses.pas"/> + <UnitName Value="frUses"/> + </Item164> + <Item165> + <Filename Value="..\..\Ui\Settings\frTransform.lfm"/> + <Type Value="LFM"/> + </Item165> + <Item166> + <Filename Value="..\..\Ui\Settings\frTransform.pas"/> + <UnitName Value="frTransform"/> + </Item166> + <Item167> + <Filename Value="..\..\Ui\Settings\frAsm.lfm"/> + <Type Value="LFM"/> + </Item167> + <Item168> + <Filename Value="..\..\Ui\Settings\frAsm.pas"/> + <UnitName Value="frAsm"/> + </Item168> + <Item169> + <Filename Value="..\..\Ui\Settings\frPreProcessor.lfm"/> + <Type Value="LFM"/> + </Item169> + <Item170> + <Filename Value="..\..\Ui\Settings\frPreProcessor.pas"/> + <UnitName Value="frPreProcessor"/> + </Item170> + <Item171> + <Filename Value="jcfuiconsts.pas"/> + <UnitName Value="JcfUIConsts"/> + </Item171> + <Item172> + <Filename Value="..\..\ReadWrite\Diff.pas"/> + <UnitName Value="Diff"/> + </Item172> + <Item173> + <Filename Value="..\..\ReadWrite\diffmerge.pas"/> + <UnitName Value="diffmerge"/> + </Item173> + </Files> + <CompatibilityMode Value="True"/> + <i18n> + <EnableI18N Value="True"/> + <OutDir Value="languages"/> + </i18n> + <RequiredPkgs Count="1"> + <Item1> + <PackageName Value="IDEIntf"/> + </Item1> + </RequiredPkgs> + <UsageOptions> + <UnitPath Value="$(PkgOutDir)"/> + </UsageOptions> + <PublishOptions> + <Version Value="2"/> + </PublishOptions> + </Package> +</CONFIG> diff --git a/components/jcf2/IdePlugin/lazarus/jcfidelazarus.pas b/components/jcf2/IdePlugin/lazarus/jcfidelazarus.pas index e01392abdf..23669991a5 100644 --- a/components/jcf2/IdePlugin/lazarus/jcfidelazarus.pas +++ b/components/jcf2/IdePlugin/lazarus/jcfidelazarus.pas @@ -1,55 +1,55 @@ -{ This file was automatically created by Lazarus. Do not edit! - This source is only used to compile and install the package. - } - -unit jcfidelazarus; - -{$warn 5023 off : no warning about unused units} -interface - -uses - JcfIdeMain, JcfIdeRegister, AsmKeywords, BuildParseTree, BuildTokenList, - ParseError, ParseTreeNode, ParseTreeNodeType, PreProcessorExpressionParser, - PreProcessorExpressionTokenise, PreProcessorExpressionTokens, - PreProcessorParseTree, SourceToken, SourceTokenList, Tokens, TokenUtils, - fShowParseTree, AlignAssign, AlignBase, AlignComment, AlignConst, - AlignField, AlignTypedef, AlignVars, AllProcesses, BaseVisitor, - Capitalisation, IdentifierCaps, SpecificWordCaps, UnitNameCaps, FormatFlags, - IndentAsmParam, Indenter, BasicStats, Nesting, FixCase, RebreakLines, - ReduceWhiteSpace, RemoveBlankLine, RemoveComment, - RemoveConsecutiveWhiteSpace, RemoveReturn, RemoveUnneededWhiteSpace, - MozComment, RemoveEmptyComment, BlockStyles, LongLineBreaker, NoReturnAfter, - NoReturnBefore, PropertyOnOneLine, RemoveBlankLinesAfterProcHeader, - RemoveBlankLinesInVars, RemoveConsecutiveReturns, RemoveReturnsAfter, - RemoveReturnsAfterBegin, RemoveReturnsBeforeEnd, ReturnAfter, ReturnBefore, - ReturnChars, ReturnsAfterFinalEnd, MaxSpaces, NoSpaceAfter, NoSpaceBefore, - RemoveSpaceAtLineEnd, SingleSpaceAfter, SingleSpaceBefore, SpaceBeforeColon, - SpaceToTab, TabToSpace, SwitchableVisitor, AddBeginEnd, - AddBlockEndSemicolon, FindReplace, SortUses, SortUsesData, - UsesClauseFindReplace, UsesClauseInsert, UsesClauseRemove, TreeWalker, - VisitSetNesting, VisitSetXY, VisitStripEmptySpace, WarnAssignToFunctionName, - WarnCaseNoElse, WarnDestroy, WarnEmptyBlock, Warning, WarnRealType, - WarnUnusedParam, Converter, ConvertTypes, EditorConverter, FileConverter, - JcfRegistrySettings, JcfSetBase, JcfSettings, SetAlign, SetAsm, SetCaps, - SetClarify, SetComments, SetIndent, SetObfuscate, SetPreProcessor, - SetReplace, SetReturns, SetSpaces, SettingsTypes, SetTransform, SetUses, - SetWordList, SettingsStream, fJcfErrorDisplay, Delay, IntList, - JcfFontSetFunctions, JcfHelp, JcfLog, JcfMiscFunctions, fAbout, - JcfVersionConsts, frFiles, frObfuscateSettings, frClarify, frClarifySpaces, - frClarifyIndent, frBlankLines, frClarifyAlign, frClarifyLongLineBreaker, - frClarifyReturns, frCompilerDirectReturns, frClarifyBlocks, - frClarifyCaseBlocks, frComments, frWarnings, frReservedCapsSettings, - frAnyCapsSettings, frIdentifierCapsSettings, frNotIdentifierCapsSettings, - frUnitCaps, frReplace, frUses, frTransform, frAsm, frPreProcessor, - jcfuiconsts, LazarusPackageIntf; - -implementation - -procedure Register; -begin - RegisterUnit('JcfIdeRegister', @JcfIdeRegister.Register); -end; - -initialization - RegisterPackage('jcfidelazarus', @Register); -end. +{ This file was automatically created by Lazarus. Do not edit! + This source is only used to compile and install the package. + } + +unit jcfidelazarus; + +{$warn 5023 off : no warning about unused units} +interface + +uses + JcfIdeMain, JcfIdeRegister, AsmKeywords, BuildParseTree, BuildTokenList, + ParseError, ParseTreeNode, ParseTreeNodeType, PreProcessorExpressionParser, + PreProcessorExpressionTokenise, PreProcessorExpressionTokens, + PreProcessorParseTree, SourceToken, SourceTokenList, Tokens, TokenUtils, + fShowParseTree, AlignAssign, AlignBase, AlignComment, AlignConst, + AlignField, AlignTypedef, AlignVars, AllProcesses, BaseVisitor, + Capitalisation, IdentifierCaps, SpecificWordCaps, UnitNameCaps, FormatFlags, + IndentAsmParam, Indenter, BasicStats, Nesting, FixCase, RebreakLines, + ReduceWhiteSpace, RemoveBlankLine, RemoveComment, + RemoveConsecutiveWhiteSpace, RemoveReturn, RemoveUnneededWhiteSpace, + MozComment, RemoveEmptyComment, BlockStyles, LongLineBreaker, NoReturnAfter, + NoReturnBefore, PropertyOnOneLine, RemoveBlankLinesAfterProcHeader, + RemoveBlankLinesInVars, RemoveConsecutiveReturns, RemoveReturnsAfter, + RemoveReturnsAfterBegin, RemoveReturnsBeforeEnd, ReturnAfter, ReturnBefore, + ReturnChars, ReturnsAfterFinalEnd, MaxSpaces, NoSpaceAfter, NoSpaceBefore, + RemoveSpaceAtLineEnd, SingleSpaceAfter, SingleSpaceBefore, SpaceBeforeColon, + SpaceToTab, TabToSpace, SwitchableVisitor, AddBeginEnd, + AddBlockEndSemicolon, FindReplace, SortUses, SortUsesData, + UsesClauseFindReplace, UsesClauseInsert, UsesClauseRemove, TreeWalker, + VisitSetNesting, VisitSetXY, VisitStripEmptySpace, WarnAssignToFunctionName, + WarnCaseNoElse, WarnDestroy, WarnEmptyBlock, Warning, WarnRealType, + WarnUnusedParam, Converter, ConvertTypes, EditorConverter, FileConverter, + JcfRegistrySettings, JcfSetBase, JcfSettings, SetAlign, SetAsm, SetCaps, + SetClarify, SetComments, SetIndent, SetObfuscate, SetPreProcessor, + SetReplace, SetReturns, SetSpaces, SettingsTypes, SetTransform, SetUses, + SetWordList, SettingsStream, fJcfErrorDisplay, Delay, IntList, + JcfFontSetFunctions, JcfHelp, JcfLog, JcfMiscFunctions, fAbout, + JcfVersionConsts, frFiles, frObfuscateSettings, frClarify, frClarifySpaces, + frClarifyIndent, frBlankLines, frClarifyAlign, frClarifyLongLineBreaker, + frClarifyReturns, frCompilerDirectReturns, frClarifyBlocks, + frClarifyCaseBlocks, frComments, frWarnings, frReservedCapsSettings, + frAnyCapsSettings, frIdentifierCapsSettings, frNotIdentifierCapsSettings, + frUnitCaps, frReplace, frUses, frTransform, frAsm, frPreProcessor, + JcfUIConsts, Diff, diffmerge, LazarusPackageIntf; + +implementation + +procedure Register; +begin + RegisterUnit('JcfIdeRegister', @JcfIdeRegister.Register); +end; + +initialization + RegisterPackage('jcfidelazarus', @Register); +end. diff --git a/components/jcf2/IdePlugin/lazarus/jcfidemain.pas b/components/jcf2/IdePlugin/lazarus/jcfidemain.pas index 058399b740..3c151210ff 100644 --- a/components/jcf2/IdePlugin/lazarus/jcfidemain.pas +++ b/components/jcf2/IdePlugin/lazarus/jcfidemain.pas @@ -82,6 +82,9 @@ type implementation +uses + diffmerge; + function FileIsAllowedType(const psFileName: string): boolean; const ALLOWED_FILE_TYPES: array[1..5] of string = ('.pas', '.pp', '.dpr', '.lpr', '.dpk'); @@ -275,7 +278,7 @@ begin while (wI > 1) and (fcConverter.OutputCode[wI] in [#10, #13, ' ']) do Dec(wI); outputstr := Copy(fcConverter.OutputCode, 1, wI); - srcEditor.ReplaceLines(BlockBegin.Y, BlockEnd.Y, outputstr, false); + DiffMergeEditor(srcEditor,outputstr,BlockBegin.Y,BlockEnd.Y); end else begin //try formating wrapping selected code in fake unit. @@ -291,7 +294,7 @@ begin fcConverter.GuiMessages := true; fcConverter.ConvertUsingFakeUnit; if not fcConverter.ConvertError then - srcEditor.ReplaceText(BlockBegin, BlockEnd, fcConverter.OutputCode); + DiffMergeEditor(srcEditor,fcConverter.OutputCode,BlockBegin.Y,BlockEnd.Y); end; finally fcConverter.Free; diff --git a/components/jcf2/ReadWrite/Converter.pas b/components/jcf2/ReadWrite/Converter.pas index 9a3c51bd33..5a7b9c7e1e 100644 --- a/components/jcf2/ReadWrite/Converter.pas +++ b/components/jcf2/ReadWrite/Converter.pas @@ -392,14 +392,17 @@ begin { put markers into the input } fsInputCode := StrInsert(FORMAT_END, fsInputCode, liRealInputEnd); - fsInputCode := StrInsert(FORMAT_START, fsInputCode, liRealInputStart); - + { add a new line after FORMAT_START, prevents bad formating of first selected line. } + fsInputCode := StrInsert(FORMAT_START+#10, fsInputCode, liRealInputStart); Convert; - { locate the markers in the output, and replace before and after } liOutputStart := Pos(FORMAT_START, fsOutputCode) + Length(FORMAT_START); + {remode new line added after FORMAT_START } + if (liOutputStart<length(fsOutputCode)) and (fsOutputCode[liOutputStart]=#13) then + inc(liOutputStart); + if (liOutputStart<length(fsOutputCode)) and (fsOutputCode[liOutputStart]=#10) then + inc(liOutputStart); liOutputEnd := PosEx(FORMAT_END, fsOutputCode,liOutputStart); - { splice } if aOnlyOutputSelection then lsNewOutput := Copy(fsOutputCode, liOutputStart, (liOutputEnd - liOutputStart)) diff --git a/components/jcf2/ReadWrite/Diff.pas b/components/jcf2/ReadWrite/Diff.pas new file mode 100644 index 0000000000..ddea5f9310 --- /dev/null +++ b/components/jcf2/ReadWrite/Diff.pas @@ -0,0 +1,901 @@ +unit Diff; + +{$IFDEF FPC} + {$mode delphi}{$H+} +{$ENDIF} + +(******************************************************************************* +* Component TDiff * +* Version: 5.02 * +* Date: 23 May 2020 * +* Compilers: Delphi 10.x * +* Author: Angus Johnson - angusj-AT-myrealbox-DOT-com * +* Copyright: © 2001-2009 Angus Johnson * +* Updated by: Rickard Johansson (RJ TextEd) * +* * +* Licence to use, terms and conditions: * +* The code in the TDiff component is released as freeware * +* provided you agree to the following terms & conditions: * +* 1. the copyright notice, terms and conditions are * +* left unchanged * +* 2. modifications to the code by other authors must be * +* clearly documented and accompanied by the modifier's name. * +* 3. the TDiff component may be freely compiled into binary * +* format and no acknowledgement is required. However, a * +* discrete acknowledgement would be appreciated (eg. in a * +* program's 'About Box'). * +* * +* Description: Component to list differences between two integer arrays * +* using a "longest common subsequence" algorithm. * +* Typically, this component is used to diff 2 text files * +* once their individuals lines have been hashed. * +* * +* Acknowledgements: The key algorithm in this component is based on: * +* "An O(NP) Sequence Comparison Algorithm" * +* by Sun Wu, Udi Manber & Gene Myers * +* and uses a "divide-and-conquer" technique to avoid * +* using exponential amounts of memory as described in * +* "An O(ND) Difference Algorithm and its Variations" * +* By E Myers - Algorithmica Vol. 1 No. 2, 1986, pp. 251-266 * +*******************************************************************************) + +(******************************************************************************* +* History: * +* 13 December 2001 - Original release (used Myer's O(ND) Difference Algorithm) * +* 22 April 2008 - Complete rewrite to greatly improve the code and * +* provide a much simpler view of differences through a new * +* 'Compares' property. * +* 21 May 2008 - Another complete code rewrite to use Sun Wu et al.'s * +* O(NP) Sequence Comparison Algorithm which more than * +* halves times of typical comparisons. * +* 24 May 2008 - Reimplemented "divide-and-conquer" technique (which was * +* omitted in 21 May release) so memory use is again minimal.* +* 25 May 2008 - Removed recursion to avoid the possibility of running out * +* of stack memory during massive comparisons. * +* 2 June 2008 - Bugfix: incorrect number of appended AddChangeInt() calls * +* in Execute() for integer arrays. (It was OK with Chars) * +* Added check to prevent repeat calls to Execute() while * +* already executing. * +* Added extra parse of differences to find occasional * +* missed matches. (See readme.txt for further discussion) * +* 7 November 2009 - Updated so now compiles in newer versions of Delphi. * +* * +* 11 November 2018 - Added TList<Cardinal> to store hash values * +* Made some minor code formatting and code changes * +* 19 May 2020 Added Lazarus support * +* 23 May 2020 - Minor changes and fixed an issue in AddChangeChr() * +*******************************************************************************) + +interface + +uses +{$IFnDEF FPC} + Generics.Collections, Windows, +{$ELSE} + LCLIntf, LCLType, Fgl, +{$ENDIF} + SysUtils, + Forms, + Classes; + +const + MAX_DIAGONAL = $FFFFFF; //~16 million + +type + + P8Bits = PByte; + + PDiags = ^TDiags; + TDiags = array [-MAX_DIAGONAL .. MAX_DIAGONAL] of integer; + + TChangeKind = (ckNone, ckAdd, ckDelete, ckModify); + + PCompareRec = ^TCompareRec; + TCompareRec = record + Kind : TChangeKind; + oldIndex1 : Integer; + oldIndex2 : Integer; + case boolean of + false : (chr1, chr2 : Char); + true : (int1, int2 : Cardinal); + end; + + PDiffVars = ^TDiffVars; + TDiffVars = record + offset1 : integer; + offset2 : integer; + len1 : integer; + len2 : integer; + end; + + TDiffStats = record + matches : integer; + adds : integer; + deletes : integer; + modifies : integer; + end; + + {$IFDEF FPC} + TIntegerList = TFPGList<Cardinal>; + {$ENDIF} + + TDiff = class(TComponent) + private + FCompareList: TList; + FDiffList: TList; //this TList circumvents the need for recursion + FCancelled: boolean; + FExecuting: boolean; + FCompareInts: boolean; //ie are we comparing integer arrays or char arrays + DiagBufferF: pointer; + DiagBufferB: pointer; + DiagF, DiagB: PDiags; + FDiffStats: TDiffStats; + FLastCompareRec: TCompareRec; + {$IFDEF FPC} + FList1: TIntegerList; + FList2: TIntegerList; + {$ELSE} + FList1: TList<Cardinal>; + FList2: TList<Cardinal>; + {$ENDIF} + FStr1: string; + FStr2: string; + procedure PushDiff(offset1, offset2, len1, len2: integer); + function PopDiff: boolean; + procedure InitDiagArrays(len1, len2: integer); + procedure DiffInt(offset1, offset2, len1, len2: integer); + procedure DiffChr(offset1, offset2, len1, len2: integer); + function SnakeChrF(k,offset1,offset2,len1,len2: integer): boolean; + function SnakeChrB(k,offset1,offset2,len1,len2: integer): boolean; + function SnakeIntF(k,offset1,offset2,len1,len2: integer): boolean; + function SnakeIntB(k,offset1,offset2,len1,len2: integer): boolean; + procedure AddChangeChr(offset1, range: integer; ChangeKind: TChangeKind); + procedure AddChangeInt(offset1, range: integer; ChangeKind: TChangeKind); + function GetCompareCount: integer; + function GetCompare(index: integer): TCompareRec; + public + constructor Create(aOwner: TComponent); override; + destructor Destroy; override; + + // Compare strings or list of Cardinals ... + {$IFDEF FPC} + function Execute(const alist1, alist2: TIntegerList): boolean; overload; + {$ELSE} + function Execute(const alist1, alist2: TList<Cardinal>): boolean; overload; + {$ENDIF} + function Execute(const s1, s2: string): boolean; overload; + // Cancel allows interrupting excessively prolonged comparisons + procedure Cancel; + procedure Clear; + property Cancelled: boolean read FCancelled; + property Count: integer read GetCompareCount; + property Compares[index: integer]: TCompareRec read GetCompare; default; + property DiffStats: TDiffStats read FDiffStats; + end; + +implementation + +procedure Register; +begin + RegisterComponents('Samples', [TDiff]); +end; + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + +constructor TDiff.Create(aOwner: TComponent); +begin + inherited; + FCompareList := TList.create; + FDiffList := TList.Create; +end; +//------------------------------------------------------------------------------ + +destructor TDiff.Destroy; +begin + Clear; + FCompareList.free; + FDiffList.Free; + inherited; +end; +//------------------------------------------------------------------------------ + +{$IFDEF FPC} +function TDiff.Execute(const alist1, alist2: TIntegerList): boolean; +{$ELSE} +function TDiff.Execute(const alist1, alist2: TList<Cardinal>): boolean; +{$ENDIF} +var + i, Len1Minus1: integer; + len1,len2: Integer; +begin + Result := not FExecuting; + if not Result then exit; + FCancelled := false; + FExecuting := true; + try + FList1 := alist1; + FList2 := alist2; + len1 := FList1.Count; + len2 := FList2.Count; + + Clear; + + Len1Minus1 := len1 -1; + FCompareList.Capacity := len1 + len2; + FCompareInts := true; + + GetMem(DiagBufferF, sizeof(integer)*(len1+len2+3)); + GetMem(DiagBufferB, sizeof(integer)*(len1+len2+3)); + try + PushDiff(0, 0, len1, len2); + while PopDiff do; + finally + freeMem(DiagBufferF); + freeMem(DiagBufferB); + end; + + if FCancelled then + begin + Result := false; + Clear; + exit; + end; + + //correct the occasional missed match ... + for i := 1 to count -1 do + with PCompareRec(FCompareList[i])^ do + if (Kind = ckModify) and (int1 = int2) then + begin + Kind := ckNone; + Dec(FDiffStats.modifies); + Inc(FDiffStats.matches); + end; + + //finally, append any trailing matches onto compareList ... + with FLastCompareRec do + AddChangeInt(oldIndex1,len1Minus1-oldIndex1, ckNone); + finally + FExecuting := false; + end; +end; +//------------------------------------------------------------------------------ + +function TDiff.Execute(const s1, s2: string): boolean; +var + i, Len1Minus1: integer; + len1,len2: Integer; +begin + Result := not FExecuting; + if not Result then exit; + FCancelled := false; + FExecuting := true; + try + Clear; + len1 := Length(s1); + len2 := Length(s2); + Len1Minus1 := len1 -1; + FCompareList.Capacity := len1 + len2; + FDiffList.Capacity := 1024; + FCompareInts := false; + + GetMem(DiagBufferF, sizeof(integer)*(len1+len2+3)); + GetMem(DiagBufferB, sizeof(integer)*(len1+len2+3)); + FStr1 := s1; + FStr2 := s2; + try + PushDiff(1, 1, len1, len2); + while PopDiff do; + finally + freeMem(DiagBufferF); + freeMem(DiagBufferB); + end; + + if FCancelled then + begin + Result := false; + Clear; + exit; + end; + + //correct the occasional missed match ... + for i := 1 to count -1 do + with PCompareRec(FCompareList[i])^ do + if (Kind = ckModify) and (chr1 = chr2) then + begin + Kind := ckNone; + Dec(FDiffStats.modifies); + Inc(FDiffStats.matches); + end; + + //finally, append any trailing matches onto compareList ... + with FLastCompareRec do + begin + AddChangeChr(oldIndex1,len1Minus1-oldIndex1, ckNone); + end; + finally + FExecuting := false; + end; +end; +//------------------------------------------------------------------------------ + +procedure TDiff.PushDiff(offset1, offset2, len1, len2: integer); +var + DiffVars: PDiffVars; +begin + new(DiffVars); + DiffVars.offset1 := offset1; + DiffVars.offset2 := offset2; + DiffVars.len1 := len1; + DiffVars.len2 := len2; + FDiffList.Add(DiffVars); +end; +//------------------------------------------------------------------------------ + +function TDiff.PopDiff: boolean; +var + DiffVars: PDiffVars; + idx: integer; +begin + idx := FDiffList.Count -1; + Result := idx >= 0; + if not Result then exit; + DiffVars := PDiffVars(FDiffList[idx]); + with DiffVars^ do + if FCompareInts then + DiffInt(offset1, offset2, len1, len2) else + DiffChr(offset1, offset2, len1, len2); + Dispose(DiffVars); + FDiffList.Delete(idx); +end; +//------------------------------------------------------------------------------ + +procedure TDiff.InitDiagArrays(len1, len2: integer); +var + i: integer; +begin + //assumes that top and bottom matches have been excluded + P8Bits(DiagF) := P8Bits(DiagBufferF) - sizeof(integer)*(MAX_DIAGONAL-(len1+1)); + for i := - (len1+1) to (len2+1) do + DiagF^[i] := -MAXINT; + DiagF^[1] := -1; + + P8Bits(DiagB) := P8Bits(DiagBufferB) - sizeof(integer)*(MAX_DIAGONAL-(len1+1)); + for i := - (len1+1) to (len2+1) do + DiagB^[i] := MAXINT; + DiagB^[len2-len1+1] := len2; +end; +//------------------------------------------------------------------------------ + +procedure TDiff.DiffInt(offset1, offset2, len1, len2: integer); +var + p, k, delta: integer; +begin + if offset1+len1 > FList1.Count then len1 := FList1.Count - offset1; + if offset2+len2 > FList2.Count then len2 := FList2.Count - offset2; + //trim matching bottoms ... + while (len1 > 0) and (len2 > 0) and (FList1[offset1] = FList2[offset2]) do + begin + inc(offset1); inc(offset2); dec(len1); dec(len2); + end; + //trim matching tops ... + while (len1 > 0) and (len2 > 0) and (FList1[offset1+len1-1] = FList2[offset2+len2-1]) do + begin + dec(len1); dec(len2); + end; + + //stop diff'ing if minimal conditions reached ... + if (len1 = 0) then + begin + AddChangeInt(offset1 ,len2, ckAdd); + exit; + end + else if (len2 = 0) then + begin + AddChangeInt(offset1 ,len1, ckDelete); + exit; + end + else if (len1 = 1) and (len2 = 1) then + begin + AddChangeInt(offset1, 1, ckDelete); + AddChangeInt(offset1, 1, ckAdd); + exit; + end; + + p := -1; + delta := len2 - len1; + InitDiagArrays(len1, len2); + if delta < 0 then + begin + repeat + inc(p); + if (p mod 1024) = 1023 then + begin + Application.ProcessMessages; + if FCancelled then exit; + end; + //nb: the Snake order is important here + for k := p downto delta +1 do + if SnakeIntF(k,offset1,offset2,len1,len2) then exit; + for k := -p + delta to delta-1 do + if SnakeIntF(k,offset1,offset2,len1,len2) then exit; + for k := delta -p to -1 do + if SnakeIntB(k,offset1,offset2,len1,len2) then exit; + for k := p downto 1 do + if SnakeIntB(k,offset1,offset2,len1,len2) then exit; + if SnakeIntF(delta,offset1,offset2,len1,len2) then exit; + if SnakeIntB(0,offset1,offset2,len1,len2) then exit; + until(false); + end else + begin + repeat + inc(p); + if (p mod 1024) = 1023 then + begin + Application.ProcessMessages; + if FCancelled then exit; + end; + //nb: the Snake order is important here + for k := -p to delta -1 do + if SnakeIntF(k,offset1,offset2,len1,len2) then exit; + for k := p + delta downto delta +1 do + if SnakeIntF(k,offset1,offset2,len1,len2) then exit; + for k := delta + p downto 1 do + if SnakeIntB(k,offset1,offset2,len1,len2) then exit; + for k := -p to -1 do + if SnakeIntB(k,offset1,offset2,len1,len2) then exit; + if SnakeIntF(delta,offset1,offset2,len1,len2) then exit; + if SnakeIntB(0,offset1,offset2,len1,len2) then exit; + until(false); + end; +end; +//------------------------------------------------------------------------------ + +procedure TDiff.DiffChr(offset1, offset2, len1, len2: integer); +var + p, k, delta: integer; +begin + //trim matching bottoms ... + while (len1 > 0) and (len2 > 0) and (FStr1[offset1] = FStr2[offset2]) do + begin + inc(offset1); inc(offset2); dec(len1); dec(len2); + end; + //trim matching tops ... + while (len1 > 0) and (len2 > 0) and (FStr1[offset1+len1-1] = FStr2[offset2+len2-1]) do + begin + dec(len1); dec(len2); + end; + + //stop diff'ing if minimal conditions reached ... + if (len1 = 0) then + begin + AddChangeChr(offset1 ,len2, ckAdd); + exit; + end + else if (len2 = 0) then + begin + AddChangeChr(offset1, len1, ckDelete); + exit; + end + else if (len1 = 1) and (len2 = 1) then + begin + AddChangeChr(offset1, 1, ckDelete); + AddChangeChr(offset1, 1, ckAdd); + exit; + end; + + p := -1; + delta := len2 - len1; + InitDiagArrays(len1, len2); + if delta < 0 then + begin + repeat + inc(p); + if (p mod 1024 = 1023) then + begin + Application.ProcessMessages; + if FCancelled then exit; + end; + //nb: the Snake order is important here + for k := p downto delta +1 do + if SnakeChrF(k,offset1,offset2,len1,len2) then exit; + for k := -p + delta to delta-1 do + if SnakeChrF(k,offset1,offset2,len1,len2) then exit; + for k := delta -p to -1 do + if SnakeChrB(k,offset1,offset2,len1,len2) then exit; + for k := p downto 1 do + if SnakeChrB(k,offset1,offset2,len1,len2) then exit; + if SnakeChrF(delta,offset1,offset2,len1,len2) then exit; + if SnakeChrB(0,offset1,offset2,len1,len2) then exit; + until(false); + end else + begin + repeat + inc(p); + if (p mod 1024 = 1023) then + begin + Application.ProcessMessages; + if FCancelled then exit; + end; + //nb: the Snake order is important here + for k := -p to delta -1 do + if SnakeChrF(k,offset1,offset2,len1,len2) then exit; + for k := p + delta downto delta +1 do + if SnakeChrF(k,offset1,offset2,len1,len2) then exit; + for k := delta + p downto 1 do + if SnakeChrB(k,offset1,offset2,len1,len2) then exit; + for k := -p to -1 do + if SnakeChrB(k,offset1,offset2,len1,len2) then exit; + if SnakeChrF(delta,offset1,offset2,len1,len2) then exit; + if SnakeChrB(0,offset1,offset2,len1,len2) then exit; + until(false); + end; +end; +//------------------------------------------------------------------------------ + +function TDiff.SnakeChrF(k,offset1,offset2,len1,len2: integer): boolean; +var + x,y: integer; +begin + if DiagF[k+1] > DiagF[k-1] then + y := DiagF[k+1] else + y := DiagF[k-1]+1; + x := y - k; + while (x < len1-1) and (y < len2-1) and (FStr1[offset1+x+1] = FStr2[offset2+y+1]) do + begin + inc(x); inc(y); + end; + DiagF[k] := y; + Result := (DiagF[k] >= DiagB[k]); + if not Result then exit; + + inc(x); inc(y); + PushDiff(offset1+x, offset2+y, len1-x, len2-y); + PushDiff(offset1, offset2, x, y); +end; +//------------------------------------------------------------------------------ + +function TDiff.SnakeChrB(k,offset1,offset2,len1,len2: integer): boolean; +var + x,y: integer; +begin + if DiagB[k-1] < DiagB[k+1] then + y := DiagB[k-1] + else + y := DiagB[k+1]-1; + + x := y - k; + while (x >= 0) and (y >= 0) and (FStr1[offset1+x] = FStr2[offset2+y]) do + begin + dec(x); dec(y); + end; + DiagB[k] := y; + Result := DiagB[k] <= DiagF[k]; + if not Result then exit; + + inc(x); inc(y); + PushDiff(offset1+x, offset2+y, len1-x, len2-y); + PushDiff(offset1, offset2, x, y); +end; +//------------------------------------------------------------------------------ + +function TDiff.SnakeIntF(k,offset1,offset2,len1,len2: integer): boolean; +var + x,y: integer; +begin + if DiagF^[k+1] > DiagF^[k-1] then + y := DiagF^[k+1] + else + y := DiagF^[k-1]+1; + x := y - k; + while (x < len1-1) and (y < len2-1) and (FList1[offset1+x+1] = FList2[offset2+y+1]) do + begin + inc(x); inc(y); + end; + DiagF^[k] := y; + Result := (DiagF^[k] >= DiagB^[k]); + if not Result then exit; + + inc(x); inc(y); + PushDiff(offset1+x, offset2+y, len1-x, len2-y); + PushDiff(offset1, offset2, x, y); +end; +//------------------------------------------------------------------------------ + +function TDiff.SnakeIntB(k,offset1,offset2,len1,len2: integer): boolean; +var + x,y: integer; +begin + if DiagB^[k-1] < DiagB^[k+1] then + y := DiagB^[k-1] + else + y := DiagB^[k+1]-1; + x := y - k; + while (x >= 0) and (y >= 0) and (FList1[offset1+x] = FList2[offset2+y]) do + begin + dec(x); dec(y); + end; + DiagB^[k] := y; + Result := DiagB^[k] <= DiagF^[k]; + if not Result then exit; + + inc(x); inc(y); + PushDiff(offset1+x, offset2+y, len1-x, len2-y); + PushDiff(offset1, offset2, x, y); +end; +//------------------------------------------------------------------------------ + +procedure TDiff.AddChangeChr(offset1, range: integer; ChangeKind: TChangeKind); +var + i,j: integer; + compareRec: PCompareRec; +begin + //first, add any unchanged items into this list ... + while (FLastCompareRec.oldIndex1 < offset1 -1) do + begin + with FLastCompareRec do + begin + chr1 := #0; + chr2 := #0; + Kind := ckNone; + inc(oldIndex1); + inc(oldIndex2); + if (oldIndex1 > 0) and (oldIndex1 <= Length(FStr1)) then + chr1 := FStr1[oldIndex1]; + if (oldIndex2 > 0) and (oldIndex2 <= Length(FStr2)) then + chr2 := FStr2[oldIndex2]; + end; + New(compareRec); + compareRec^ := FLastCompareRec; + FCompareList.Add(compareRec); + inc(FDiffStats.matches); + end; + + case ChangeKind of + ckNone: + for i := 1 to range do + begin + with FLastCompareRec do + begin + Kind := ckNone; + inc(oldIndex1); + inc(oldIndex2); + chr1 := FStr1[oldIndex1]; + chr2 := FStr2[oldIndex2]; + end; + New(compareRec); + compareRec^ := FLastCompareRec; + FCompareList.Add(compareRec); + inc(FDiffStats.matches); + end; + ckAdd : + begin + for i := 1 to range do + begin + with FLastCompareRec do + begin + + //check if a range of adds are following a range of deletes + //and convert them to modifies ... + if Kind = ckDelete then + begin + j := FCompareList.Count -1; + while (j > 0) and (PCompareRec(FCompareList[j-1]).Kind = ckDelete) do + dec(j); + PCompareRec(FCompareList[j]).Kind := ckModify; + dec(FDiffStats.deletes); + inc(FDiffStats.modifies); + inc(FLastCompareRec.oldIndex2); + PCompareRec(FCompareList[j]).oldIndex2 := FLastCompareRec.oldIndex2; + PCompareRec(FCompareList[j]).chr2 := FStr2[oldIndex2]; + if j = FCompareList.Count-1 then + FLastCompareRec.Kind := ckModify; + continue; + end; + + Kind := ckAdd; + chr1 := #0; + inc(oldIndex2); + chr2 := FStr2[oldIndex2]; //ie what we added + end; + New(compareRec); + compareRec^ := FLastCompareRec; + FCompareList.Add(compareRec); + inc(FDiffStats.adds); + end; + end; + ckDelete : + begin + for i := 1 to range do + begin + with FLastCompareRec do + begin + + //check if a range of deletes are following a range of adds + //and convert them to modifies ... + if Kind = ckAdd then + begin + j := FCompareList.Count -1; + while (j > 0) and (PCompareRec(FCompareList[j-1]).Kind = ckAdd) do + dec(j); + PCompareRec(FCompareList[j]).Kind := ckModify; + dec(FDiffStats.adds); + inc(FDiffStats.modifies); + inc(FLastCompareRec.oldIndex1); + PCompareRec(FCompareList[j]).oldIndex1 := FLastCompareRec.oldIndex1; + PCompareRec(FCompareList[j]).chr1 := FStr1[oldIndex1]; + if j = FCompareList.Count-1 then + FLastCompareRec.Kind := ckModify; + continue; + end; + + Kind := ckDelete; + chr2 := #0; + inc(oldIndex1); + chr1 := FStr1[oldIndex1]; //ie what we deleted + end; + New(compareRec); + compareRec^ := FLastCompareRec; + FCompareList.Add(compareRec); + inc(FDiffStats.deletes); + end; + end; + end; +end; +//------------------------------------------------------------------------------ + +procedure TDiff.AddChangeInt(offset1, range: integer; ChangeKind: TChangeKind); +var + i,j: integer; + compareRec: PCompareRec; +begin + //first, add any unchanged items into this list ... + while (FLastCompareRec.oldIndex1 < offset1 -1) do + begin + with FLastCompareRec do + begin + Kind := ckNone; + inc(oldIndex1); + inc(oldIndex2); + if (oldIndex1 >= 0) and (oldIndex1 < FList1.Count) then + int1 := FList1[oldIndex1]; + if (oldIndex2 >= 0) and (oldIndex2 < FList2.Count) then + int2 := FList2[oldIndex2]; + end; + New(compareRec); + compareRec^ := FLastCompareRec; + FCompareList.Add(compareRec); + inc(FDiffStats.matches); + end; + + case ChangeKind of + ckNone: + for i := 1 to range do + begin + with FLastCompareRec do + begin + Kind := ckNone; + inc(oldIndex1); + inc(oldIndex2); + if (oldIndex1 >= 0) and (oldIndex1 < FList1.Count) then + int1 := FList1[oldIndex1]; + if (oldIndex2 >= 0) and (oldIndex2 < FList2.Count) then + int2 := FList2[oldIndex2]; + end; + New(compareRec); + compareRec^ := FLastCompareRec; + FCompareList.Add(compareRec); + inc(FDiffStats.matches); + end; + ckAdd : + begin + for i := 1 to range do + begin + with FLastCompareRec do + begin + + //check if a range of adds are following a range of deletes + //and convert them to modifies ... + if Kind = ckDelete then + begin + j := FCompareList.Count -1; + while (j > 0) and (PCompareRec(FCompareList[j-1]).Kind = ckDelete) do + dec(j); + PCompareRec(FCompareList[j]).Kind := ckModify; + dec(FDiffStats.deletes); + inc(FDiffStats.modifies); + inc(FLastCompareRec.oldIndex2); + PCompareRec(FCompareList[j]).oldIndex2 := FLastCompareRec.oldIndex2; + PCompareRec(FCompareList[j]).int2 := FList2[oldIndex2]; + if j = FCompareList.Count-1 then FLastCompareRec.Kind := ckModify; + continue; + end; + + Kind := ckAdd; + int1 := $0; + inc(oldIndex2); + if (oldIndex2 >= 0) and (oldIndex2 < FList2.Count) then + int2 := FList2[oldIndex2]; //ie what we added + end; + New(compareRec); + compareRec^ := FLastCompareRec; + FCompareList.Add(compareRec); + inc(FDiffStats.adds); + end; + end; + ckDelete : + begin + for i := 1 to range do + begin + with FLastCompareRec do + begin + + //check if a range of deletes are following a range of adds + //and convert them to modifies ... + if Kind = ckAdd then + begin + j := FCompareList.Count -1; + while (j > 0) and (PCompareRec(FCompareList[j-1]).Kind = ckAdd) do + dec(j); + PCompareRec(FCompareList[j]).Kind := ckModify; + dec(FDiffStats.adds); + inc(FDiffStats.modifies); + inc(FLastCompareRec.oldIndex1); + PCompareRec(FCompareList[j]).oldIndex1 := FLastCompareRec.oldIndex1; + PCompareRec(FCompareList[j]).int1 := FList1[oldIndex1]; + if j = FCompareList.Count-1 then FLastCompareRec.Kind := ckModify; + continue; + end; + + Kind := ckDelete; + int2 := $0; + inc(oldIndex1); + if (oldIndex1 >= 0) and (oldIndex1 < FList1.Count) then + int1 := FList1[oldIndex1]; //ie what we deleted + end; + New(compareRec); + compareRec^ := FLastCompareRec; + FCompareList.Add(compareRec); + inc(FDiffStats.deletes); + end; + end; + end; +end; +//------------------------------------------------------------------------------ + +procedure TDiff.Clear; +var + i: integer; +begin + for i := 0 to FCompareList.Count-1 do + dispose(PCompareRec(FCompareList[i])); + FCompareList.clear; + FLastCompareRec.Kind := ckNone; + FLastCompareRec.oldIndex1 := -1; + FLastCompareRec.oldIndex2 := -1; + FDiffStats.matches := 0; + FDiffStats.adds := 0; + FDiffStats.deletes :=0; + FDiffStats.modifies :=0; +end; +//------------------------------------------------------------------------------ + +function TDiff.GetCompareCount: integer; +begin + Result := FCompareList.count; +end; +//------------------------------------------------------------------------------ + +function TDiff.GetCompare(index: integer): TCompareRec; +begin + Result := PCompareRec(FCompareList[index])^; +end; +//------------------------------------------------------------------------------ + +procedure TDiff.Cancel; +begin + FCancelled := true; +end; +//------------------------------------------------------------------------------ + +end. diff --git a/components/jcf2/ReadWrite/EditorConverter.pas b/components/jcf2/ReadWrite/EditorConverter.pas index 571ae84595..7770882512 100644 --- a/components/jcf2/ReadWrite/EditorConverter.pas +++ b/components/jcf2/ReadWrite/EditorConverter.pas @@ -34,7 +34,7 @@ See http://www.gnu.org/licenses/gpl.html interface uses - Classes, SysUtils, Math, + Classes, SysUtils, // IdeIntf SrcEditorIntf, { local } @@ -85,7 +85,7 @@ implementation uses { local } - JcfLog, JcfRegistrySettings, JcfMiscFunctions; + JcfLog, JcfRegistrySettings, diffmerge; constructor TEditorConverter.Create; begin @@ -136,98 +136,13 @@ begin end; procedure TEditorConverter.WriteToIDE(const pcUnit: TSourceEditorInterface; const psText: string); -var - lLogicalCaretXY:TPoint; - lStart,lEnd:TPoint; begin if pcUnit = nil then exit; if psText <> fcConverter.InputCode then - begin - try - lLogicalCaretXY:=pcUnit.CursorTextXY; - pcUnit.BeginUpdate; - pcUnit.BeginUndoBlock; - lStart.X:=0; //select all text. - lStart.Y:=0; - lEnd.X:=0; - if pcUnit.LineCount>0 then - lEnd.X:=length(pcUnit.Lines[pcUnit.LineCount-1])+1; - lEnd.Y:=pcUnit.LineCount; - //pcUnit.Lines.Text := psText; // removes undo history. - pcUnit.ReplaceText(lStart,lEnd,psText); - pcUnit.CursorTextXY:=lLogicalCaretXY; - pcUnit.Modified := True; - finally - pcUnit.EndUndoBlock; - pcUnit.EndUpdate; - end; - end; + DiffMergeEditor(pcUnit, psText); end; -//BUGGY: inserts empty blank lines in "random" position in the editor. -// and if only one line es added or deleted after formatting then doesn't syncronize well. -// i think is better change all text in the editor. -// TODO: delete -{ -procedure TEditorConverter.WriteToIDE(const pcUnit: TSourceEditorInterface; const psText: string); -var - lcSourceLines, lcDestLines: TStrings; - lcSameStart, lcSameEnd: TStrings; - lsSourceLine, lsDestLine: string; - liStart, liIndex, liMaxIndex: integer; - hasSourceLine: Boolean; -begin - if pcUnit = nil then - exit; - lcSourceLines := TStringList.Create; - lcSourceLines.Text := fcConverter.InputCode; - lcDestLines := TStringList.Create; - lcDestLines.Text := psText; - lcSameStart := TStringList.Create; - lcSameEnd := TStringList.Create; - - SplitIntoChangeSections(lcSourceLines, lcDestLines, lcSameStart, lcSameEnd); - try - pcUnit.BeginUpdate; - pcUnit.BeginUndoBlock; - - liStart := lcSameStart.Count; - liIndex := 0; - liMaxIndex := Max(lcSourceLines.Count, lcDestLines.Count); - while (liIndex < liMaxIndex) do - begin - hasSourceLine := liIndex < lcSourceLines.Count; - if hasSourceLine then - lsSourceLine := lcSourceLines[liIndex] - else - lsSourceLine := ''; - - if liIndex < lcDestLines.Count then - lsDestLine := lcDestLines[liIndex] - else - lsDestLine := ''; - - if not hasSourceLine then - pcUnit.InsertLine(liStart + liIndex + 1, lsDestLine, True) - else - if not AnsiSameStr(lsSourceLine, lsDestLine) then - // the line is different, replace it - pcUnit.ReplaceLines(liStart + liIndex + 1, liStart + liIndex + 1, lsDestLine, True); - - inc(liIndex); - end; - finally - pcUnit.EndUndoBlock; - pcUnit.EndUpdate; - lcSourceLines.Free; - lcDestLines.Free; - lcSameStart.Free; - lcSameEnd.Free; - end; -end; -} - procedure TEditorConverter.AfterConvert; begin FinalSummary; diff --git a/components/jcf2/ReadWrite/diffmerge.pas b/components/jcf2/ReadWrite/diffmerge.pas new file mode 100644 index 0000000000..7b3c98685e --- /dev/null +++ b/components/jcf2/ReadWrite/diffmerge.pas @@ -0,0 +1,202 @@ +{ + /*************************************************************************** + diffmerge.pas - functions to merge new text with the JCF changes into the + editor + + ***************************************************************************/ + + *************************************************************************** + * * + * This source is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This code is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * General Public License for more details. * + * * + * A copy of the GNU General Public License is available on the World * + * Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also * + * obtain it by writing to the Free Software Foundation, * + * Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA. * + * * + *************************************************************************** + + Author: Domingo Galmés + + Abstract: + Methods for creating diffs and applying them to the editor. + + Uses TextDiff + https://github.com/rickard67/TextDiff + http://www.angusj.com/delphi/textdiff.html + Author : Angus Johnson - angusj-AT-myrealbox-DOT-com + Copyright : © 2001-2008 Angus Johnson + Updated by : Rickard Johansson (RJ TextEd) +} +unit diffmerge; + +{$mode objfpc}{$H+} +interface + +uses + Classes, SysUtils, SrcEditorIntf, Diff; + +procedure DiffMergeEditor(aEdit: TSourceEditorInterface; const aNewText: string; aFromLine: integer = 1; aToLine: integer = maxint); + +implementation + +uses + crc; + +function HashLine(const line: string; aIgnoreFinalSpaces: boolean = False): cardinal; +var + lLen: integer; +begin + lLen := Length(line); + if aIgnoreFinalSpaces then + begin + while (lLen > 0) and (line[lLen] in [' ', #9]) do + begin + Dec(lLen); + end; + end; + if lLen = 0 then + Result := CRC32(0, nil, lLen) + else + Result := CRC32(0, pbyte(@line[1]), lLen); +end; + +//Line number 1 based as TSourceEditorInterface. +procedure FillHashList(aList: TIntegerList; aLines: TStrings; aFirstLine: integer = 1; aLastLine: integer = maxint); +var + i: integer; +begin + aList.Clear; + Dec(aFirstLine); + Dec(aLastLine); + if aFirstLine < 0 then + aFirstLine := 0; + if aLastLine > aLines.Count - 1 then + aLastLine := aLines.Count - 1; + for i := aFirstLine to aLastLine do + begin + aList.Add(HashLine(aLines[i])); + end; +end; + +function EditorGetLastColumn(aEdit: TSourceEditorInterface; aLineNumber: integer): integer; +begin + Result := length(aEdit.Lines[aLineNumber - 1]) + 1; +end; + +procedure EditorDeleteLine(aEdit: TSourceEditorInterface; aLineNumber: integer); +var + lStartPoint, lEndPoint: Tpoint; +begin + if (aLineNumber < 0) or (aLineNumber > aEdit.Lines.Count) then + Exit; + lStartPoint.X := 1; + lStartPoint.Y := aLineNumber; + lEndPoint.X := 1; + lEndPoint.Y := aLineNumber + 1; + if lEndPoint.Y > aEdit.Lines.Count then + begin + lEndPoint.Y := aLineNumber; + lEndPoint.X := EditorGetLastColumn(aEdit, aLineNumber); + if aLineNumber > 1 then + begin + lStartPoint.X := EditorGetLastColumn(aEdit, aLineNumber - 1); + lStartPoint.Y := aLineNumber - 1; + end; + end; + aEdit.ReplaceText(lStartPoint, lEndPoint, ''); +end; + + +procedure EditorInsertLine(aEdit: TSourceEditorInterface; aLineNumber: integer; aText: string); +begin + aEdit.InsertLine(aLineNumber, aText, True); +end; + +procedure EditorReplaceLine(aEdit: TSourceEditorInterface; aLineNumber: integer; aNewText: string); +begin + aEdit.ReplaceLines(aLineNumber, aLineNumber, aNewText, True); +end; + +procedure DiffMergeEditor(aEdit: TSourceEditorInterface; const aNewText: string; aFromLine: integer = 1; aToLine: integer = maxint); +var + lDiff: TDiff; + lI: integer; + lListOldTextHashes, lListNewTextHashes: TIntegerList; + lDeltaLines: integer; + lStartLine: integer; + lNewText: TStringList; + lCursor: TPoint; + lCursorAdjusted: boolean; + lCursorNeedAdjustX: boolean; +begin + lDiff := nil; + lNewText := nil; + lListOldTextHashes := nil; + lListNewTextHashes := nil; + lDeltaLines := 0; + lStartLine := aFromLine - 1; // 0 based offset. + lCursor := aEdit.CursorTextXY; + lCursorAdjusted := False; + lCursorNeedAdjustX := False; + try + aEdit.BeginUpdate; + aEdit.BeginUndoBlock; + lDiff := TDiff.Create(nil); + lNewText := TStringList.Create; + lNewText.Text := aNewText; + lListOldTextHashes := TIntegerList.Create; + lListNewTextHashes := TIntegerList.Create; + FillHashList(lListOldTextHashes, aEdit.Lines, aFromLine, aToLine); + FillHashList(lListNewTextHashes, lNewText); + lDiff.Execute(lListOldTextHashes, lListNewTextHashes); + for lI := 0 to lDiff.Count - 1 do + begin + with lDiff.Compares[lI] do + begin + if (lCursorAdjusted = False) and (lCursor.Y = lStartLine + oldIndex1 + 1) then + begin + lCursor.Y := lCursor.Y + lDeltaLines; + lCursorAdjusted := True; + lCursorNeedAdjustX := Kind <> ckNone; + end; + case Kind of + ckAdd: + begin + EditorInsertLine(aEdit, lStartLine + oldIndex2 + 1, lNewText[oldIndex2]); + Inc(lDeltaLines); + end; + ckDelete: + begin + EditorDeleteLine(aEdit, lStartLine + oldIndex1 + 1 + lDeltaLines); + Dec(lDeltaLines); + end; + ckModify: + begin + EditorReplaceLine(aEdit, lStartLine + oldIndex2 + 1, lNewText[oldIndex2]); + end; + end; + end; + end; + if lCursorNeedAdjustX then + lCursor.X := EditorGetLastColumn(aEdit, lCursor.Y); + aEdit.CursorTextXY := lCursor; + finally + lDiff.Free; + lListOldTextHashes.Free; + lListNewTextHashes.Free; + lNewText.Free; + aEdit.EndUndoBlock; + aEdit.EndUpdate; + end; +end; + +end. -- 2.30.0.windows.1 JCF_acknowledgemnt_tdiff.patch (483 bytes)
diff --git a/docs/acknowledgements.txt b/docs/acknowledgements.txt index 9d5a5bca53..5c2f9a864b 100644 --- a/docs/acknowledgements.txt +++ b/docs/acknowledgements.txt @@ -23,3 +23,7 @@ http://p.yusukekamiyamane.com/ Created by Carlo Kok / RemObjects Software Fixed for Lazarus by Bogusіaw Brandys http://www.remobjects.com/ps.aspx + +TDiff by Angus Johnson +Fixed for Freepascal by Rickard Johansson (RJ TextEd) +http://github.com/rickard67/TextDiff \ No newline at end of file |
|
Could the same diff code be used in Lazarus IDE? Tools menu has a comparison entry. The same code is used elsewhere in the IDE, too. |
|
My first intention was to use the lazarus code to make the comparison, but it gave me inacurate results with blank lines that i was not being able to solve so I tryed to use the TDiff component. TDiff could be used in the ide comparison code, but the patch code I provide does not create a "patch" itself how does the comparison tool in the ide, it applies the changes found with TDiff directly to the editor. To use TDiff in the comparison, one would have to rewrite all the logic for creating the patch file format generated by the IDE utility. Practically the IDE comparison tool would have to be rewritten. Sample of inacurate comparation of files. In the diff only adds a line "+" betwen par1line3 and par2line1 I thig it shoult add 3 "+" and the last lines it marks "!" as modifications for me they are additions "+" file1 ----------------- par1line1 par1line2 par1line3 par2line1 par2line2 par2line3 File2 --------------- parnewline1 parnewline2 par1line1 par1line2 par1line3 par2line1 par2line2 par2line3 par3line1 new Result --------------------- *************** *** 1,10 **** par1line1 par1line2 par1line3 par2line1 par2line2 ! par2line3 --- 1,20 ---- + parnewline1 + parnewline2 + + par1line1 par1line2 par1line3 + par2line1 par2line2 ! par2line3 ! ! ! par3line1 ! new |
|
> Practically the IDE comparison tool would have to be rewritten. A good idea actually. The current code does not scale well. Time grows polynomially with big files. I changed you new files a little. Please take a look. LazUtils already has unit IntegerList. In this case type TCardinalList is the right one. IFDEFs for Delphi are not needed. We removed them from all JCF code. Unit diffmerge does hashing of text lines. Does it mean that unit Diff cannot be used by itself? A caller must provide hashed data for it. I would place unit Diff in LazUtils package if it was standalone with a clear API. The Application.ProcessMessages could be refactored out by an event. The author of unit Diff should accept the same license as JCF2 code has. It is dual Mozilla / GPL. In LazUtils it would be Modified LGPL2. In the comment header Angus Johnson practically explains Modified LGPL2 with linking exception but it should be made explicit. There are people who would spot this as a license violation. diffmerge.pas (6,462 bytes)
{ /*************************************************************************** diffmerge.pas - functions to merge new text with the JCF changes into the editor ***************************************************************************/ *************************************************************************** * * * This source is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This code is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * A copy of the GNU General Public License is available on the World * * Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also * * obtain it by writing to the Free Software Foundation, * * Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA. * * * *************************************************************************** Author: Domingo Galmés Abstract: Methods for creating diffs and applying them to the editor. Uses TextDiff https://github.com/rickard67/TextDiff http://www.angusj.com/delphi/textdiff.html Author : Angus Johnson - angusj-AT-myrealbox-DOT-com Copyright : © 2001-2008 Angus Johnson Updated by : Rickard Johansson (RJ TextEd) } unit diffmerge; {$mode objfpc}{$H+} interface uses Classes, SysUtils, crc, // LazUtils IntegerList, // IdeIntf SrcEditorIntf, Diff; procedure DiffMergeEditor(aEdit: TSourceEditorInterface; const aNewText: string; aFromLine: integer = 1; aToLine: integer = maxint); implementation function HashLine(const line: string; aIgnoreFinalSpaces: boolean = False): cardinal; var lLen: integer; begin lLen := Length(line); if aIgnoreFinalSpaces then begin while (lLen > 0) and (line[lLen] in [' ', #9]) do Dec(lLen); end; if lLen = 0 then Result := CRC32(0, nil, lLen) else Result := CRC32(0, pbyte(@line[1]), lLen); end; //Line number 1 based as TSourceEditorInterface. procedure FillHashList(aList: TCardinalList; aLines: TStrings; aFirstLine: integer = 1; aLastLine: integer = maxint); var i: integer; begin aList.Clear; Dec(aFirstLine); Dec(aLastLine); if aFirstLine < 0 then aFirstLine := 0; if aLastLine > aLines.Count - 1 then aLastLine := aLines.Count - 1; for i := aFirstLine to aLastLine do aList.Add(HashLine(aLines[i])); end; function EditorGetLastColumn(aEdit: TSourceEditorInterface; aLineNumber: integer): integer; begin Result := length(aEdit.Lines[aLineNumber - 1]) + 1; end; procedure EditorDeleteLine(aEdit: TSourceEditorInterface; aLineNumber: integer); var lStartPoint, lEndPoint: Tpoint; begin if (aLineNumber < 0) or (aLineNumber > aEdit.Lines.Count) then Exit; lStartPoint.X := 1; lStartPoint.Y := aLineNumber; lEndPoint.X := 1; lEndPoint.Y := aLineNumber + 1; if lEndPoint.Y > aEdit.Lines.Count then begin lEndPoint.Y := aLineNumber; lEndPoint.X := EditorGetLastColumn(aEdit, aLineNumber); if aLineNumber > 1 then begin lStartPoint.X := EditorGetLastColumn(aEdit, aLineNumber - 1); lStartPoint.Y := aLineNumber - 1; end; end; aEdit.ReplaceText(lStartPoint, lEndPoint, ''); end; procedure EditorInsertLine(aEdit: TSourceEditorInterface; aLineNumber: integer; aText: string); begin aEdit.InsertLine(aLineNumber, aText, True); end; procedure EditorReplaceLine(aEdit: TSourceEditorInterface; aLineNumber: integer; aNewText: string); begin aEdit.ReplaceLines(aLineNumber, aLineNumber, aNewText, True); end; procedure DiffMergeEditor(aEdit: TSourceEditorInterface; const aNewText: string; aFromLine: integer = 1; aToLine: integer = maxint); var lDiff: TDiff; lI: integer; lListOldTextHashes, lListNewTextHashes: TCardinalList; lDeltaLines: integer; lStartLine: integer; lNewText: TStringList; lCursor: TPoint; lCursorAdjusted: boolean; lCursorNeedAdjustX: boolean; begin lDiff := nil; lNewText := nil; lListOldTextHashes := nil; lListNewTextHashes := nil; lDeltaLines := 0; lStartLine := aFromLine - 1; // 0 based offset. lCursor := aEdit.CursorTextXY; lCursorAdjusted := False; lCursorNeedAdjustX := False; try aEdit.BeginUpdate; aEdit.BeginUndoBlock; lDiff := TDiff.Create(nil); lNewText := TStringList.Create; lNewText.Text := aNewText; lListOldTextHashes := TCardinalList.Create; lListNewTextHashes := TCardinalList.Create; FillHashList(lListOldTextHashes, aEdit.Lines, aFromLine, aToLine); FillHashList(lListNewTextHashes, lNewText); lDiff.Execute(lListOldTextHashes, lListNewTextHashes); for lI := 0 to lDiff.Count - 1 do begin with lDiff.Compares[lI] do begin if (lCursorAdjusted = False) and (lCursor.Y = lStartLine + oldIndex1 + 1) then begin lCursor.Y := lCursor.Y + lDeltaLines; lCursorAdjusted := True; lCursorNeedAdjustX := Kind <> ckNone; end; case Kind of ckAdd: begin EditorInsertLine(aEdit, lStartLine + oldIndex2 + 1, lNewText[oldIndex2]); Inc(lDeltaLines); end; ckDelete: begin EditorDeleteLine(aEdit, lStartLine + oldIndex1 + 1 + lDeltaLines); Dec(lDeltaLines); end; ckModify: begin EditorReplaceLine(aEdit, lStartLine + oldIndex2 + 1, lNewText[oldIndex2]); end; end; end; end; if lCursorNeedAdjustX then lCursor.X := EditorGetLastColumn(aEdit, lCursor.Y); aEdit.CursorTextXY := lCursor; finally lDiff.Free; lListOldTextHashes.Free; lListNewTextHashes.Free; lNewText.Free; aEdit.EndUndoBlock; aEdit.EndUpdate; end; end; end. Diff.pas (28,415 bytes)
unit Diff; {$mode delphi}{$H+} (******************************************************************************* * Component TDiff * * Version: 5.02 * * Date: 23 May 2020 * * Compilers: Delphi 10.x * * Author: Angus Johnson - angusj-AT-myrealbox-DOT-com * * Copyright: © 2001-2009 Angus Johnson * * Updated by: Rickard Johansson (RJ TextEd) * * * * Licence to use, terms and conditions: * * The code in the TDiff component is released as freeware * * provided you agree to the following terms & conditions: * * 1. the copyright notice, terms and conditions are * * left unchanged * * 2. modifications to the code by other authors must be * * clearly documented and accompanied by the modifier's name. * * 3. the TDiff component may be freely compiled into binary * * format and no acknowledgement is required. However, a * * discrete acknowledgement would be appreciated (eg. in a * * program's 'About Box'). * * * * Description: Component to list differences between two integer arrays * * using a "longest common subsequence" algorithm. * * Typically, this component is used to diff 2 text files * * once their individuals lines have been hashed. * * * * Acknowledgements: The key algorithm in this component is based on: * * "An O(NP) Sequence Comparison Algorithm" * * by Sun Wu, Udi Manber & Gene Myers * * and uses a "divide-and-conquer" technique to avoid * * using exponential amounts of memory as described in * * "An O(ND) Difference Algorithm and its Variations" * * By E Myers - Algorithmica Vol. 1 No. 2, 1986, pp. 251-266 * *******************************************************************************) (******************************************************************************* * History: * * 13 December 2001 - Original release (used Myer's O(ND) Difference Algorithm) * * 22 April 2008 - Complete rewrite to greatly improve the code and * * provide a much simpler view of differences through a new * * 'Compares' property. * * 21 May 2008 - Another complete code rewrite to use Sun Wu et al.'s * * O(NP) Sequence Comparison Algorithm which more than * * halves times of typical comparisons. * * 24 May 2008 - Reimplemented "divide-and-conquer" technique (which was * * omitted in 21 May release) so memory use is again minimal.* * 25 May 2008 - Removed recursion to avoid the possibility of running out * * of stack memory during massive comparisons. * * 2 June 2008 - Bugfix: incorrect number of appended AddChangeInt() calls * * in Execute() for integer arrays. (It was OK with Chars) * * Added check to prevent repeat calls to Execute() while * * already executing. * * Added extra parse of differences to find occasional * * missed matches. (See readme.txt for further discussion) * * 7 November 2009 - Updated so now compiles in newer versions of Delphi. * * * * 11 November 2018 - Added TList<Cardinal> to store hash values * * Made some minor code formatting and code changes * * 19 May 2020 Added Lazarus support * * 23 May 2020 - Minor changes and fixed an issue in AddChangeChr() * *******************************************************************************) interface uses Classes, SysUtils, // LCL Forms, // LazUtils IntegerList; const MAX_DIAGONAL = $FFFFFF; //~16 million type P8Bits = PByte; PDiags = ^TDiags; TDiags = array [-MAX_DIAGONAL .. MAX_DIAGONAL] of integer; TChangeKind = (ckNone, ckAdd, ckDelete, ckModify); PCompareRec = ^TCompareRec; TCompareRec = record Kind : TChangeKind; oldIndex1 : Integer; oldIndex2 : Integer; case boolean of false : (chr1, chr2 : Char); true : (int1, int2 : Cardinal); end; PDiffVars = ^TDiffVars; TDiffVars = record offset1 : integer; offset2 : integer; len1 : integer; len2 : integer; end; TDiffStats = record matches : integer; adds : integer; deletes : integer; modifies : integer; end; TDiff = class(TComponent) private FCompareList: TList; FDiffList: TList; //this TList circumvents the need for recursion FCancelled: boolean; FExecuting: boolean; FCompareInts: boolean; //ie are we comparing integer arrays or char arrays DiagBufferF: pointer; DiagBufferB: pointer; DiagF, DiagB: PDiags; FDiffStats: TDiffStats; FLastCompareRec: TCompareRec; FList1: TCardinalList; FList2: TCardinalList; FStr1: string; FStr2: string; procedure PushDiff(offset1, offset2, len1, len2: integer); function PopDiff: boolean; procedure InitDiagArrays(len1, len2: integer); procedure DiffInt(offset1, offset2, len1, len2: integer); procedure DiffChr(offset1, offset2, len1, len2: integer); function SnakeChrF(k,offset1,offset2,len1,len2: integer): boolean; function SnakeChrB(k,offset1,offset2,len1,len2: integer): boolean; function SnakeIntF(k,offset1,offset2,len1,len2: integer): boolean; function SnakeIntB(k,offset1,offset2,len1,len2: integer): boolean; procedure AddChangeChr(offset1, range: integer; ChangeKind: TChangeKind); procedure AddChangeInt(offset1, range: integer; ChangeKind: TChangeKind); function GetCompareCount: integer; function GetCompare(index: integer): TCompareRec; public constructor Create(aOwner: TComponent); override; destructor Destroy; override; // Compare strings or list of Cardinals ... function Execute(const alist1, alist2: TCardinalList): boolean; overload; function Execute(const s1, s2: string): boolean; overload; // Cancel allows interrupting excessively prolonged comparisons procedure Cancel; procedure Clear; property Cancelled: boolean read FCancelled; property Count: integer read GetCompareCount; property Compares[index: integer]: TCompareRec read GetCompare; default; property DiffStats: TDiffStats read FDiffStats; end; implementation procedure Register; begin RegisterComponents('Samples', [TDiff]); end; //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ constructor TDiff.Create(aOwner: TComponent); begin inherited; FCompareList := TList.create; FDiffList := TList.Create; end; //------------------------------------------------------------------------------ destructor TDiff.Destroy; begin Clear; FCompareList.free; FDiffList.Free; inherited; end; //------------------------------------------------------------------------------ function TDiff.Execute(const alist1, alist2: TCardinalList): boolean; var i, Len1Minus1: integer; len1,len2: Integer; begin Result := not FExecuting; if not Result then exit; FCancelled := false; FExecuting := true; try FList1 := alist1; FList2 := alist2; len1 := FList1.Count; len2 := FList2.Count; Clear; Len1Minus1 := len1 -1; FCompareList.Capacity := len1 + len2; FCompareInts := true; GetMem(DiagBufferF, sizeof(integer)*(len1+len2+3)); GetMem(DiagBufferB, sizeof(integer)*(len1+len2+3)); try PushDiff(0, 0, len1, len2); while PopDiff do; finally freeMem(DiagBufferF); freeMem(DiagBufferB); end; if FCancelled then begin Result := false; Clear; exit; end; //correct the occasional missed match ... for i := 1 to count -1 do with PCompareRec(FCompareList[i])^ do if (Kind = ckModify) and (int1 = int2) then begin Kind := ckNone; Dec(FDiffStats.modifies); Inc(FDiffStats.matches); end; //finally, append any trailing matches onto compareList ... with FLastCompareRec do AddChangeInt(oldIndex1,len1Minus1-oldIndex1, ckNone); finally FExecuting := false; end; end; //------------------------------------------------------------------------------ function TDiff.Execute(const s1, s2: string): boolean; var i, Len1Minus1: integer; len1,len2: Integer; begin Result := not FExecuting; if not Result then exit; FCancelled := false; FExecuting := true; try Clear; len1 := Length(s1); len2 := Length(s2); Len1Minus1 := len1 -1; FCompareList.Capacity := len1 + len2; FDiffList.Capacity := 1024; FCompareInts := false; GetMem(DiagBufferF, sizeof(integer)*(len1+len2+3)); GetMem(DiagBufferB, sizeof(integer)*(len1+len2+3)); FStr1 := s1; FStr2 := s2; try PushDiff(1, 1, len1, len2); while PopDiff do; finally freeMem(DiagBufferF); freeMem(DiagBufferB); end; if FCancelled then begin Result := false; Clear; exit; end; //correct the occasional missed match ... for i := 1 to count -1 do with PCompareRec(FCompareList[i])^ do if (Kind = ckModify) and (chr1 = chr2) then begin Kind := ckNone; Dec(FDiffStats.modifies); Inc(FDiffStats.matches); end; //finally, append any trailing matches onto compareList ... with FLastCompareRec do begin AddChangeChr(oldIndex1,len1Minus1-oldIndex1, ckNone); end; finally FExecuting := false; end; end; //------------------------------------------------------------------------------ procedure TDiff.PushDiff(offset1, offset2, len1, len2: integer); var DiffVars: PDiffVars; begin new(DiffVars); DiffVars.offset1 := offset1; DiffVars.offset2 := offset2; DiffVars.len1 := len1; DiffVars.len2 := len2; FDiffList.Add(DiffVars); end; //------------------------------------------------------------------------------ function TDiff.PopDiff: boolean; var DiffVars: PDiffVars; idx: integer; begin idx := FDiffList.Count -1; Result := idx >= 0; if not Result then exit; DiffVars := PDiffVars(FDiffList[idx]); with DiffVars^ do if FCompareInts then DiffInt(offset1, offset2, len1, len2) else DiffChr(offset1, offset2, len1, len2); Dispose(DiffVars); FDiffList.Delete(idx); end; //------------------------------------------------------------------------------ procedure TDiff.InitDiagArrays(len1, len2: integer); var i: integer; begin //assumes that top and bottom matches have been excluded P8Bits(DiagF) := P8Bits(DiagBufferF) - sizeof(integer)*(MAX_DIAGONAL-(len1+1)); for i := - (len1+1) to (len2+1) do DiagF^[i] := -MAXINT; DiagF^[1] := -1; P8Bits(DiagB) := P8Bits(DiagBufferB) - sizeof(integer)*(MAX_DIAGONAL-(len1+1)); for i := - (len1+1) to (len2+1) do DiagB^[i] := MAXINT; DiagB^[len2-len1+1] := len2; end; //------------------------------------------------------------------------------ procedure TDiff.DiffInt(offset1, offset2, len1, len2: integer); var p, k, delta: integer; begin if offset1+len1 > FList1.Count then len1 := FList1.Count - offset1; if offset2+len2 > FList2.Count then len2 := FList2.Count - offset2; //trim matching bottoms ... while (len1 > 0) and (len2 > 0) and (FList1[offset1] = FList2[offset2]) do begin inc(offset1); inc(offset2); dec(len1); dec(len2); end; //trim matching tops ... while (len1 > 0) and (len2 > 0) and (FList1[offset1+len1-1] = FList2[offset2+len2-1]) do begin dec(len1); dec(len2); end; //stop diff'ing if minimal conditions reached ... if (len1 = 0) then begin AddChangeInt(offset1 ,len2, ckAdd); exit; end else if (len2 = 0) then begin AddChangeInt(offset1 ,len1, ckDelete); exit; end else if (len1 = 1) and (len2 = 1) then begin AddChangeInt(offset1, 1, ckDelete); AddChangeInt(offset1, 1, ckAdd); exit; end; p := -1; delta := len2 - len1; InitDiagArrays(len1, len2); if delta < 0 then begin repeat inc(p); if (p mod 1024) = 1023 then begin Application.ProcessMessages; if FCancelled then exit; end; //nb: the Snake order is important here for k := p downto delta +1 do if SnakeIntF(k,offset1,offset2,len1,len2) then exit; for k := -p + delta to delta-1 do if SnakeIntF(k,offset1,offset2,len1,len2) then exit; for k := delta -p to -1 do if SnakeIntB(k,offset1,offset2,len1,len2) then exit; for k := p downto 1 do if SnakeIntB(k,offset1,offset2,len1,len2) then exit; if SnakeIntF(delta,offset1,offset2,len1,len2) then exit; if SnakeIntB(0,offset1,offset2,len1,len2) then exit; until(false); end else begin repeat inc(p); if (p mod 1024) = 1023 then begin Application.ProcessMessages; if FCancelled then exit; end; //nb: the Snake order is important here for k := -p to delta -1 do if SnakeIntF(k,offset1,offset2,len1,len2) then exit; for k := p + delta downto delta +1 do if SnakeIntF(k,offset1,offset2,len1,len2) then exit; for k := delta + p downto 1 do if SnakeIntB(k,offset1,offset2,len1,len2) then exit; for k := -p to -1 do if SnakeIntB(k,offset1,offset2,len1,len2) then exit; if SnakeIntF(delta,offset1,offset2,len1,len2) then exit; if SnakeIntB(0,offset1,offset2,len1,len2) then exit; until(false); end; end; //------------------------------------------------------------------------------ procedure TDiff.DiffChr(offset1, offset2, len1, len2: integer); var p, k, delta: integer; begin //trim matching bottoms ... while (len1 > 0) and (len2 > 0) and (FStr1[offset1] = FStr2[offset2]) do begin inc(offset1); inc(offset2); dec(len1); dec(len2); end; //trim matching tops ... while (len1 > 0) and (len2 > 0) and (FStr1[offset1+len1-1] = FStr2[offset2+len2-1]) do begin dec(len1); dec(len2); end; //stop diff'ing if minimal conditions reached ... if (len1 = 0) then begin AddChangeChr(offset1 ,len2, ckAdd); exit; end else if (len2 = 0) then begin AddChangeChr(offset1, len1, ckDelete); exit; end else if (len1 = 1) and (len2 = 1) then begin AddChangeChr(offset1, 1, ckDelete); AddChangeChr(offset1, 1, ckAdd); exit; end; p := -1; delta := len2 - len1; InitDiagArrays(len1, len2); if delta < 0 then begin repeat inc(p); if (p mod 1024 = 1023) then begin Application.ProcessMessages; if FCancelled then exit; end; //nb: the Snake order is important here for k := p downto delta +1 do if SnakeChrF(k,offset1,offset2,len1,len2) then exit; for k := -p + delta to delta-1 do if SnakeChrF(k,offset1,offset2,len1,len2) then exit; for k := delta -p to -1 do if SnakeChrB(k,offset1,offset2,len1,len2) then exit; for k := p downto 1 do if SnakeChrB(k,offset1,offset2,len1,len2) then exit; if SnakeChrF(delta,offset1,offset2,len1,len2) then exit; if SnakeChrB(0,offset1,offset2,len1,len2) then exit; until(false); end else begin repeat inc(p); if (p mod 1024 = 1023) then begin Application.ProcessMessages; if FCancelled then exit; end; //nb: the Snake order is important here for k := -p to delta -1 do if SnakeChrF(k,offset1,offset2,len1,len2) then exit; for k := p + delta downto delta +1 do if SnakeChrF(k,offset1,offset2,len1,len2) then exit; for k := delta + p downto 1 do if SnakeChrB(k,offset1,offset2,len1,len2) then exit; for k := -p to -1 do if SnakeChrB(k,offset1,offset2,len1,len2) then exit; if SnakeChrF(delta,offset1,offset2,len1,len2) then exit; if SnakeChrB(0,offset1,offset2,len1,len2) then exit; until(false); end; end; //------------------------------------------------------------------------------ function TDiff.SnakeChrF(k,offset1,offset2,len1,len2: integer): boolean; var x,y: integer; begin if DiagF[k+1] > DiagF[k-1] then y := DiagF[k+1] else y := DiagF[k-1]+1; x := y - k; while (x < len1-1) and (y < len2-1) and (FStr1[offset1+x+1] = FStr2[offset2+y+1]) do begin inc(x); inc(y); end; DiagF[k] := y; Result := (DiagF[k] >= DiagB[k]); if not Result then exit; inc(x); inc(y); PushDiff(offset1+x, offset2+y, len1-x, len2-y); PushDiff(offset1, offset2, x, y); end; //------------------------------------------------------------------------------ function TDiff.SnakeChrB(k,offset1,offset2,len1,len2: integer): boolean; var x,y: integer; begin if DiagB[k-1] < DiagB[k+1] then y := DiagB[k-1] else y := DiagB[k+1]-1; x := y - k; while (x >= 0) and (y >= 0) and (FStr1[offset1+x] = FStr2[offset2+y]) do begin dec(x); dec(y); end; DiagB[k] := y; Result := DiagB[k] <= DiagF[k]; if not Result then exit; inc(x); inc(y); PushDiff(offset1+x, offset2+y, len1-x, len2-y); PushDiff(offset1, offset2, x, y); end; //------------------------------------------------------------------------------ function TDiff.SnakeIntF(k,offset1,offset2,len1,len2: integer): boolean; var x,y: integer; begin if DiagF^[k+1] > DiagF^[k-1] then y := DiagF^[k+1] else y := DiagF^[k-1]+1; x := y - k; while (x < len1-1) and (y < len2-1) and (FList1[offset1+x+1] = FList2[offset2+y+1]) do begin inc(x); inc(y); end; DiagF^[k] := y; Result := (DiagF^[k] >= DiagB^[k]); if not Result then exit; inc(x); inc(y); PushDiff(offset1+x, offset2+y, len1-x, len2-y); PushDiff(offset1, offset2, x, y); end; //------------------------------------------------------------------------------ function TDiff.SnakeIntB(k,offset1,offset2,len1,len2: integer): boolean; var x,y: integer; begin if DiagB^[k-1] < DiagB^[k+1] then y := DiagB^[k-1] else y := DiagB^[k+1]-1; x := y - k; while (x >= 0) and (y >= 0) and (FList1[offset1+x] = FList2[offset2+y]) do begin dec(x); dec(y); end; DiagB^[k] := y; Result := DiagB^[k] <= DiagF^[k]; if not Result then exit; inc(x); inc(y); PushDiff(offset1+x, offset2+y, len1-x, len2-y); PushDiff(offset1, offset2, x, y); end; //------------------------------------------------------------------------------ procedure TDiff.AddChangeChr(offset1, range: integer; ChangeKind: TChangeKind); var i,j: integer; compareRec: PCompareRec; begin //first, add any unchanged items into this list ... while (FLastCompareRec.oldIndex1 < offset1 -1) do begin with FLastCompareRec do begin chr1 := #0; chr2 := #0; Kind := ckNone; inc(oldIndex1); inc(oldIndex2); if (oldIndex1 > 0) and (oldIndex1 <= Length(FStr1)) then chr1 := FStr1[oldIndex1]; if (oldIndex2 > 0) and (oldIndex2 <= Length(FStr2)) then chr2 := FStr2[oldIndex2]; end; New(compareRec); compareRec^ := FLastCompareRec; FCompareList.Add(compareRec); inc(FDiffStats.matches); end; case ChangeKind of ckNone: for i := 1 to range do begin with FLastCompareRec do begin Kind := ckNone; inc(oldIndex1); inc(oldIndex2); chr1 := FStr1[oldIndex1]; chr2 := FStr2[oldIndex2]; end; New(compareRec); compareRec^ := FLastCompareRec; FCompareList.Add(compareRec); inc(FDiffStats.matches); end; ckAdd : begin for i := 1 to range do begin with FLastCompareRec do begin //check if a range of adds are following a range of deletes //and convert them to modifies ... if Kind = ckDelete then begin j := FCompareList.Count -1; while (j > 0) and (PCompareRec(FCompareList[j-1]).Kind = ckDelete) do dec(j); PCompareRec(FCompareList[j]).Kind := ckModify; dec(FDiffStats.deletes); inc(FDiffStats.modifies); inc(FLastCompareRec.oldIndex2); PCompareRec(FCompareList[j]).oldIndex2 := FLastCompareRec.oldIndex2; PCompareRec(FCompareList[j]).chr2 := FStr2[oldIndex2]; if j = FCompareList.Count-1 then FLastCompareRec.Kind := ckModify; continue; end; Kind := ckAdd; chr1 := #0; inc(oldIndex2); chr2 := FStr2[oldIndex2]; //ie what we added end; New(compareRec); compareRec^ := FLastCompareRec; FCompareList.Add(compareRec); inc(FDiffStats.adds); end; end; ckDelete : begin for i := 1 to range do begin with FLastCompareRec do begin //check if a range of deletes are following a range of adds //and convert them to modifies ... if Kind = ckAdd then begin j := FCompareList.Count -1; while (j > 0) and (PCompareRec(FCompareList[j-1]).Kind = ckAdd) do dec(j); PCompareRec(FCompareList[j]).Kind := ckModify; dec(FDiffStats.adds); inc(FDiffStats.modifies); inc(FLastCompareRec.oldIndex1); PCompareRec(FCompareList[j]).oldIndex1 := FLastCompareRec.oldIndex1; PCompareRec(FCompareList[j]).chr1 := FStr1[oldIndex1]; if j = FCompareList.Count-1 then FLastCompareRec.Kind := ckModify; continue; end; Kind := ckDelete; chr2 := #0; inc(oldIndex1); chr1 := FStr1[oldIndex1]; //ie what we deleted end; New(compareRec); compareRec^ := FLastCompareRec; FCompareList.Add(compareRec); inc(FDiffStats.deletes); end; end; end; end; //------------------------------------------------------------------------------ procedure TDiff.AddChangeInt(offset1, range: integer; ChangeKind: TChangeKind); var i,j: integer; compareRec: PCompareRec; begin //first, add any unchanged items into this list ... while (FLastCompareRec.oldIndex1 < offset1 -1) do begin with FLastCompareRec do begin Kind := ckNone; inc(oldIndex1); inc(oldIndex2); if (oldIndex1 >= 0) and (oldIndex1 < FList1.Count) then int1 := FList1[oldIndex1]; if (oldIndex2 >= 0) and (oldIndex2 < FList2.Count) then int2 := FList2[oldIndex2]; end; New(compareRec); compareRec^ := FLastCompareRec; FCompareList.Add(compareRec); inc(FDiffStats.matches); end; case ChangeKind of ckNone: for i := 1 to range do begin with FLastCompareRec do begin Kind := ckNone; inc(oldIndex1); inc(oldIndex2); if (oldIndex1 >= 0) and (oldIndex1 < FList1.Count) then int1 := FList1[oldIndex1]; if (oldIndex2 >= 0) and (oldIndex2 < FList2.Count) then int2 := FList2[oldIndex2]; end; New(compareRec); compareRec^ := FLastCompareRec; FCompareList.Add(compareRec); inc(FDiffStats.matches); end; ckAdd : begin for i := 1 to range do begin with FLastCompareRec do begin //check if a range of adds are following a range of deletes //and convert them to modifies ... if Kind = ckDelete then begin j := FCompareList.Count -1; while (j > 0) and (PCompareRec(FCompareList[j-1]).Kind = ckDelete) do dec(j); PCompareRec(FCompareList[j]).Kind := ckModify; dec(FDiffStats.deletes); inc(FDiffStats.modifies); inc(FLastCompareRec.oldIndex2); PCompareRec(FCompareList[j]).oldIndex2 := FLastCompareRec.oldIndex2; PCompareRec(FCompareList[j]).int2 := FList2[oldIndex2]; if j = FCompareList.Count-1 then FLastCompareRec.Kind := ckModify; continue; end; Kind := ckAdd; int1 := $0; inc(oldIndex2); if (oldIndex2 >= 0) and (oldIndex2 < FList2.Count) then int2 := FList2[oldIndex2]; //ie what we added end; New(compareRec); compareRec^ := FLastCompareRec; FCompareList.Add(compareRec); inc(FDiffStats.adds); end; end; ckDelete : begin for i := 1 to range do begin with FLastCompareRec do begin //check if a range of deletes are following a range of adds //and convert them to modifies ... if Kind = ckAdd then begin j := FCompareList.Count -1; while (j > 0) and (PCompareRec(FCompareList[j-1]).Kind = ckAdd) do dec(j); PCompareRec(FCompareList[j]).Kind := ckModify; dec(FDiffStats.adds); inc(FDiffStats.modifies); inc(FLastCompareRec.oldIndex1); PCompareRec(FCompareList[j]).oldIndex1 := FLastCompareRec.oldIndex1; PCompareRec(FCompareList[j]).int1 := FList1[oldIndex1]; if j = FCompareList.Count-1 then FLastCompareRec.Kind := ckModify; continue; end; Kind := ckDelete; int2 := $0; inc(oldIndex1); if (oldIndex1 >= 0) and (oldIndex1 < FList1.Count) then int1 := FList1[oldIndex1]; //ie what we deleted end; New(compareRec); compareRec^ := FLastCompareRec; FCompareList.Add(compareRec); inc(FDiffStats.deletes); end; end; end; end; //------------------------------------------------------------------------------ procedure TDiff.Clear; var i: integer; begin for i := 0 to FCompareList.Count-1 do dispose(PCompareRec(FCompareList[i])); FCompareList.clear; FLastCompareRec.Kind := ckNone; FLastCompareRec.oldIndex1 := -1; FLastCompareRec.oldIndex2 := -1; FDiffStats.matches := 0; FDiffStats.adds := 0; FDiffStats.deletes :=0; FDiffStats.modifies :=0; end; //------------------------------------------------------------------------------ function TDiff.GetCompareCount: integer; begin Result := FCompareList.count; end; //------------------------------------------------------------------------------ function TDiff.GetCompare(index: integer): TCompareRec; begin Result := PCompareRec(FCompareList[index])^; end; //------------------------------------------------------------------------------ procedure TDiff.Cancel; begin FCancelled := true; end; //------------------------------------------------------------------------------ end. |
|
TDiff has two execute functions, one for character strings that looks for the differences in a string by individual characters and another for two lists of integers that are the hashes of the lines. Indeed, for use the caller must pass two lists with hashes of the lines to compare. In TDiff I had not made any changes to comply with the license that says that the changes made must be clearly indicated. With the issue of the license I believed that if we did not modify Tdiff.pas we would comply with the author's requirements. My level of English is not very high as you may have already noticed, I say it to get in touch with the author and explain clearly what we want. It would be better if someone with a better knowledge of the English language requested it. The changes made in the two files seem correct to me. |
|
Fixed typo in summary. |
|
> With the issue of the license I believed that if we did not modify Tdiff.pas we would comply with the author's requirements. No, we can easily comply by documenting the changes in the unit's comment header. He writes: "modifications to the code by other authors must be clearly documented and accompanied by the modifier's name" In fact (L)GPL has the same requirement but we break it all the time. Every project breaks it. Nowadays the same information can be found from revision control more easily. When GPL was created, revision control was not widely used and SW was copied using floppy disks. Then the requirement was justified, now it should be updated with an option for revision control. I applied the changes. New files are in JCF package. His license resembles Modified LGPL2, it surely is compatible with Mozilla / GPL which is JCF's dual license. Please test. P.S. I would like to see a diff tool resembling Kompare or Meld integrated in Lazarus. Maybe you can make such a tool when you have extra time. :) |
Date Modified | Username | Field | Change |
---|---|---|---|
2020-12-10 09:07 | OkobaPatino | New Issue | |
2020-12-10 09:08 | OkobaPatino | Note Added: 0127506 | |
2020-12-10 09:09 | OkobaPatino | Note Added: 0127507 | |
2020-12-10 21:47 | Domingo Galmés | Note Added: 0127515 | |
2020-12-10 21:51 | OkobaPatino | Note Added: 0127516 | |
2020-12-10 22:29 | Juha Manninen | Relationship added | related to 0038195 |
2020-12-10 22:32 | Domingo Galmés | Note Added: 0127519 | |
2020-12-10 22:32 | Domingo Galmés | File Added: JCF_Revert.patch | |
2021-01-06 18:34 | Domingo Galmés | Note Added: 0128128 | |
2021-01-06 18:34 | Domingo Galmés | File Added: JCF_diff_merge.patch | |
2021-01-06 18:34 | Domingo Galmés | File Added: JCF_acknowledgemnt_tdiff.patch | |
2021-01-06 22:11 | Juha Manninen | Note Added: 0128133 | |
2021-01-07 14:29 | Domingo Galmés | Note Added: 0128140 | |
2021-01-07 16:26 | Juha Manninen | Note Added: 0128143 | |
2021-01-07 16:26 | Juha Manninen | File Added: diffmerge.pas | |
2021-01-07 16:26 | Juha Manninen | File Added: Diff.pas | |
2021-01-07 18:00 | Domingo Galmés | Note Added: 0128148 | |
2021-01-07 22:23 | Bart Broersma | Summary | JCF will move the braeakpoints => JCF will move the breakpoints |
2021-01-07 22:23 | Bart Broersma | LazTarget | => - |
2021-01-07 22:23 | Bart Broersma | Note Added: 0128155 | |
2021-01-07 23:50 | Juha Manninen | Assigned To | => Juha Manninen |
2021-01-07 23:50 | Juha Manninen | Status | new => assigned |
2021-01-08 00:10 | Juha Manninen | Assigned To | Juha Manninen => |
2021-01-08 00:10 | Juha Manninen | Assigned To | => Juha Manninen |
2021-01-08 00:11 | Juha Manninen | Status | assigned => resolved |
2021-01-08 00:11 | Juha Manninen | Resolution | open => fixed |
2021-01-08 00:11 | Juha Manninen | Fixed in Revision | => r64347, r64348 |
2021-01-08 00:11 | Juha Manninen | Note Added: 0128165 | |
2021-01-08 00:15 | Juha Manninen | Note Edited: 0128165 | View Revisions |
2021-01-08 00:16 | Juha Manninen | Note Edited: 0128165 | View Revisions |
2021-02-16 15:37 | Juha Manninen | Relationship added | related to 0038125 |