{ Double Commander ------------------------------------------------------------------------- Internal diff and merge tool Copyright (C) 2010-2021 Alexander Koblov (alexx2000@mail.ru) This program 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 program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . } unit fDiffer; {$mode objfpc}{$H+} interface uses Classes, SysUtils, FileUtil, Forms, Controls, Dialogs, Menus, ComCtrls, ActnList, ExtCtrls, EditBtn, Buttons, SynEdit, uSynDiffControls, uPariterControls, uDiffOND, uFormCommands, uHotkeyManager, uOSForms, uBinaryDiffViewer, uShowForm, KASStatusBar, Graphics, StdCtrls; type { TStatusBar } TStatusBar = class(TKASStatusBar); { TfrmDiffer } TfrmDiffer = class(TAloneForm, IFormCommands) actBinaryCompare: TAction; actCopyLeftToRight: TAction; actCopyRightToLeft: TAction; actExit: TAction; actEditCut: TAction; actEditCopy: TAction; actEditDelete: TAction; actEditUndo: TAction; actEditRedo: TAction; actEditSelectAll: TAction; actEditPaste: TAction; actAbout: TAction; actAutoCompare: TAction; actLineDifferences: TAction; actSaveRightAs: TAction; actSaveLeftAs: TAction; actOpenRight: TAction; actOpenLeft: TAction; actReload: TAction; actSaveRight: TAction; actSaveLeft: TAction; actPaintBackground: TAction; actStartCompare: TAction; actFirstDifference: TAction; actIgnoreCase: TAction; actIgnoreWhiteSpace: TAction; actCancelCompare: TAction; actKeepScrolling: TAction; actPrevDifference: TAction; actLastDifference: TAction; actNextDifference: TAction; actSaveAs: TAction; actSave: TAction; ActionList: TActionList; edtFileNameLeft: TFileNameEdit; edtFileNameRight: TFileNameEdit; MainMenu: TMainMenu; miAutoCompare: TMenuItem; miDivider10: TMenuItem; miLineDifferences: TMenuItem; miEncodingRight: TMenuItem; miEncodingLeft: TMenuItem; miAbout: TMenuItem; mnuEncoding: TMenuItem; miSaveRightAs: TMenuItem; miSaveLeftAs: TMenuItem; miCopyContext: TMenuItem; miCutContext: TMenuItem; miDeleteContext: TMenuItem; miEditSelectAll: TMenuItem; miDivider9: TMenuItem; miEditDelete: TMenuItem; miEditPaste: TMenuItem; miEditCopy: TMenuItem; miEditCut: TMenuItem; miDivider8: TMenuItem; miEditRedo: TMenuItem; miEditUndo: TMenuItem; miDivider7: TMenuItem; miPasteContext: TMenuItem; miReload: TMenuItem; miDivider6: TMenuItem; miExit: TMenuItem; miSaveRight: TMenuItem; miOpenRight: TMenuItem; miOpenLeft: TMenuItem; miCopyRightToLeft: TMenuItem; miCopyLeftToRight: TMenuItem; miDivider5: TMenuItem; miPaintBackground: TMenuItem; miDivider4: TMenuItem; miBinaryCompare: TMenuItem; miKeepScrolling: TMenuItem; miDivider3: TMenuItem; miLastDiff: TMenuItem; miFirstDiff: TMenuItem; miDivider2: TMenuItem; miPrevDiff: TMenuItem; miNextDiff: TMenuItem; miDivider1: TMenuItem; miCancelCompare: TMenuItem; miSelectAllContext: TMenuItem; miSeparator1: TMenuItem; miSeparator2: TMenuItem; miStartCompare: TMenuItem; miUndoContext: TMenuItem; mnuActions: TMenuItem; miIgnoreCase: TMenuItem; miIgnoreWhiteSpace: TMenuItem; mnuOptions: TMenuItem; mnuEdit: TMenuItem; miSaveLeft: TMenuItem; mnuFile: TMenuItem; ContextMenu: TPopupMenu; pnlLeftBox: TPanel; pnlRight: TPanel; pnlLeft: TPanel; pnlRightBox: TPanel; btnLeftEncoding: TSpeedButton; btnRightEncoding: TSpeedButton; btnLeftSave: TSpeedButton; btnLeftSaveAs: TSpeedButton; btnRightSave: TSpeedButton; btnRightSaveAs: TSpeedButton; pmEncodingLeft: TPopupMenu; pmEncodingRight: TPopupMenu; Splitter: TSplitter; StatusBar: TStatusBar; tmProgress: TTimer; ToolBar: TToolBar; btnSave: TToolButton; btnSaveAs: TToolButton; Divider1: TToolButton; btnCompare: TToolButton; btnLast: TToolButton; btnNext: TToolButton; btnPrev: TToolButton; btnFirst: TToolButton; Divider2: TToolButton; Divider3: TToolButton; btnCancelCompare: TToolButton; Divider4: TToolButton; btnReload: TToolButton; btnCopyRightToLeft: TToolButton; btnCopyLeftToRight: TToolButton; Divider5: TToolButton; btnEditUndo: TToolButton; btnEditRedo: TToolButton; procedure actAboutExecute(Sender: TObject); procedure actBinaryCompareExecute(Sender: TObject); procedure actCancelCompareExecute(Sender: TObject); procedure actExecute(Sender: TObject); procedure actEditCopyExecute(Sender: TObject); procedure actEditCutExecute(Sender: TObject); procedure actEditDeleteExecute(Sender: TObject); procedure actEditPasteExecute(Sender: TObject); procedure actEditRedoExecute(Sender: TObject); procedure actEditSelectAllExecute(Sender: TObject); procedure actEditUndoExecute(Sender: TObject); procedure actIgnoreCaseExecute(Sender: TObject); procedure actLineDifferencesExecute(Sender: TObject); procedure actOpenLeftExecute(Sender: TObject); procedure actOpenRightExecute(Sender: TObject); procedure actPaintBackgroundExecute(Sender: TObject); procedure actSaveAsExecute(Sender: TObject); procedure actSaveExecute(Sender: TObject); procedure actSaveLeftAsExecute(Sender: TObject); procedure actSaveRightAsExecute(Sender: TObject); procedure actStartCompareExecute(Sender: TObject); procedure actKeepScrollingExecute(Sender: TObject); procedure btnLeftEncodingClick(Sender: TObject); procedure btnRightEncodingClick(Sender: TObject); procedure edtFileNameLeftAcceptFileName(Sender: TObject; var Value: String); procedure edtFileNameRightAcceptFileName(Sender: TObject; var Value: String); procedure FormClose(Sender: TObject; var CloseAction: TCloseAction); procedure FormCloseQuery(Sender: TObject; var CanClose: boolean); procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); procedure FormResize(Sender: TObject); procedure btnCancelClick(Sender: TObject); procedure tmProgressTimer(Sender: TObject); private BinaryDiffList: TFPList; BinaryDiffIndex: Integer; BinaryCompare: TBinaryCompare; BinaryViewerLeft, BinaryViewerRight: TBinaryDiffViewer; procedure BinaryCompareFinish; private Diff: TDiff; SynDiffEditActive: TSynDiffEdit; SynDiffEditLeft: TSynDiffEdit; SynDiffEditRight: TSynDiffEdit; SynDiffHighlighterLeft, SynDiffHighlighterRight: TSynDiffHighlighter; HashListLeft, HashListRight: array of Integer; EncodingList: TStringList; ScrollLock: LongInt; FShowIdentical: Boolean; FModal: Boolean; FCancel: Boolean; frmProgress: TForm; FWaitData: TWaitData; FElevate: TDuplicates; FCommands: TFormCommands; private procedure ShowDialog; procedure ShowIdentical; procedure ShowProgressDialog; procedure CloseProgressDialog; procedure Clear(bLeft, bRight: Boolean); procedure BuildHashList(bLeft, bRight: Boolean); procedure ChooseEncoding(SynDiffEdit: TSynDiffEdit); procedure SetColors(cAdded, cDeleted, cModified: TColor); procedure ChooseEncoding(MenuItem: TMenuItem; Encoding: String); procedure FillEncodingMenu(TheOwner: TMenuItem; MenuHandler: TNotifyEvent; GroupIndex: LongInt); procedure LoadFromFile(SynDiffEdit: TSynDiffEdit; const FileName: String); procedure SaveToFile(SynDiffEdit: TSynDiffEdit; const FileName: String); procedure OpenFileLeft(const FileName: String); procedure OpenFileRight(const FileName: String); procedure SetEncodingLeft(Sender: TObject); procedure SetEncodingRight(Sender: TObject); procedure SynDiffEditEnter(Sender: TObject); procedure ShowFirstDifference(Data: PtrInt); procedure SynDiffEditLeftStatusChange(Sender: TObject; Changes: TSynStatusChanges); procedure SynDiffEditRightStatusChange(Sender: TObject; Changes: TSynStatusChanges); property Commands: TFormCommands read FCommands implements IFormCommands; public constructor Create(TheOwner: TComponent); override; destructor Destroy; override; procedure AfterConstruction; override; published procedure cm_CopyLeftToRight(const Params: array of string); procedure cm_CopyRightToLeft(const Params: array of string); procedure cm_Exit(const Params: array of string); procedure cm_FirstDifference(const Params: array of string); procedure cm_LastDifference(const Params: array of string); procedure cm_NextDifference(const Params: array of string); procedure cm_PrevDifference(const Params: array of string); procedure cm_Reload(const Params: array of string); procedure cm_SaveLeft(const Params: array of string); procedure cm_SaveRight(const Params: array of string); end; procedure ShowDiffer(const FileNameLeft, FileNameRight: String; WaitData: TWaitData = nil; Modal: Boolean = False); implementation {$R *.lfm} uses Math, LCLType, LazFileUtils, LConvEncoding, SynEditTypes, uHash, uLng, uGlobs, uShowMsg, DCClassesUtf8, dmCommonData, uDCUtils, uConvEncoding, uAdministrator; const HotkeysCategory = 'Differ'; procedure ShowDiffer(const FileNameLeft, FileNameRight: String; WaitData: TWaitData = nil; Modal: Boolean = False); var Differ: TfrmDiffer; begin Differ := TfrmDiffer.Create(Application); with Differ do begin FModal := Modal; FWaitData := WaitData; FShowIdentical := True; edtFileNameLeft.Text:= FileNameLeft; edtFileNameRight.Text:= FileNameRight; SetColors(gDifferAddedColor, gDifferDeletedColor, gDifferModifiedColor); try if not (FileIsText(FileNameLeft) and FileIsText(FileNameRight)) then actBinaryCompare.Execute else begin OpenFileLeft(FileNameLeft); OpenFileRight(FileNameRight); actStartCompare.Execute; end; except on E: Exception do begin tmProgress.Enabled:= False; msgError(E.Message); Free; end; end; end; end; { TfrmDiffer } procedure TfrmDiffer.actStartCompareExecute(Sender: TObject); var I: Integer; LineNumberLeft, LineNumberRight: PtrInt; begin FCancel:= False; try if actBinaryCompare.Checked then begin if (BinaryViewerLeft.IsFileOpen and BinaryViewerRight.IsFileOpen) then begin actStartCompare.Enabled := False; actCancelCompare.Enabled := True; actBinaryCompare.Enabled := False; BinaryCompare:= TBinaryCompare.Create(BinaryViewerLeft.GetDataAdr, BinaryViewerRight.GetDataAdr, BinaryViewerLeft.FileSize, BinaryViewerRight.FileSize, BinaryDiffList); BinaryCompare.OnFinish:= @BinaryCompareFinish; BinaryCompare.Start; end; end else try Inc(ScrollLock); Screen.Cursor := crHourGlass; if SynDiffEditLeft.Modified then SynDiffEditLeft.Lines.RemoveFake; if SynDiffEditRight.Modified then SynDiffEditRight.Lines.RemoveFake; BuildHashList(SynDiffEditLeft.Modified, SynDiffEditRight.Modified); if (Length(HashListLeft) = 0) or (Length(HashListRight) = 0) then begin FCancel := True; Exit; end; actStartCompare.Enabled := False; actCancelCompare.Enabled := True; Diff.Execute( PInteger(@HashListLeft[0]), PInteger(@HashListRight[0]), Length(HashListLeft), Length(HashListRight) ); tmProgress.Enabled:= False; if Diff.Cancelled then Exit; SynDiffEditLeft.StartCompare; SynDiffEditRight.StartCompare; for I := 0 to Diff.Count - 1 do with Diff.Compares[I] do begin LineNumberLeft:= oldIndex1 + 1; LineNumberRight:= oldIndex2 + 1; case Kind of ckAdd: begin SynDiffEditLeft.Lines.InsertFake(I, Kind); SynDiffEditRight.Lines.SetKindAndNumber(I, Kind, LineNumberRight); end; ckDelete: begin SynDiffEditLeft.Lines.SetKindAndNumber(I, Kind, LineNumberLeft); SynDiffEditRight.Lines.InsertFake(I, Kind); end; else begin SynDiffEditLeft.Lines.SetKindAndNumber(I, Kind, LineNumberLeft); SynDiffEditRight.Lines.SetKindAndNumber(I, Kind, LineNumberRight); end; end; end; with Diff.DiffStats do begin StatusBar.Panels[0].Text := rsDiffMatches + IntToStr(matches); StatusBar.Panels[1].Text := rsDiffModifies + IntToStr(modifies); StatusBar.Panels[2].Text := rsDiffAdds + IntToStr(adds); StatusBar.Panels[3].Text := rsDiffDeletes + IntToStr(deletes); if FShowIdentical then begin CloseProgressDialog; FShowIdentical:= (modifies = 0) and (adds = 0) and (deletes = 0); if FShowIdentical then ShowIdentical else begin FShowIdentical:= False; Application.QueueAsyncCall(@ShowFirstDifference, 0); ShowDialog; end; end; end; finally SynDiffEditLeft.FinishCompare; SynDiffEditRight.FinishCompare; Screen.Cursor := crDefault; actStartCompare.Enabled := True; actCancelCompare.Enabled := False; Dec(ScrollLock); end; if actLineDifferences.Checked then begin SynDiffEditLeft.Highlighter:= SynDiffHighlighterLeft; SynDiffEditRight.Highlighter:= SynDiffHighlighterRight; end; finally if FShowIdentical and FCancel then Free; end; end; procedure TfrmDiffer.actOpenLeftExecute(Sender: TObject); begin dmComData.OpenDialog.FileName:= edtFileNameLeft.Text; dmComData.OpenDialog.Filter:= AllFilesMask; if dmComData.OpenDialog.Execute then begin edtFileNameLeft.Text:= dmComData.OpenDialog.FileName; actReload.Execute; end; end; procedure TfrmDiffer.actOpenRightExecute(Sender: TObject); begin dmComData.OpenDialog.FileName:= edtFileNameRight.Text; dmComData.OpenDialog.Filter:= AllFilesMask; if dmComData.OpenDialog.Execute then begin edtFileNameRight.Text:= dmComData.OpenDialog.FileName; actReload.Execute; end; end; procedure TfrmDiffer.actPaintBackgroundExecute(Sender: TObject); begin if actPaintBackground.Checked then begin SynDiffEditLeft.PaintStyle:= psBackground; SynDiffEditRight.PaintStyle:= psBackground; end else begin SynDiffEditLeft.PaintStyle:= psForeground; SynDiffEditRight.PaintStyle:= psForeground; end; SynDiffHighlighterLeft.UpdateColors; SynDiffHighlighterRight.UpdateColors; end; procedure TfrmDiffer.actSaveAsExecute(Sender: TObject); begin if SynDiffEditActive = SynDiffEditLeft then actSaveLeftAs.Execute else if SynDiffEditActive = SynDiffEditRight then actSaveRightAs.Execute; end; procedure TfrmDiffer.actSaveExecute(Sender: TObject); begin if SynDiffEditActive = SynDiffEditLeft then actSaveLeft.Execute else if SynDiffEditActive = SynDiffEditRight then actSaveRight.Execute; end; procedure TfrmDiffer.actSaveLeftAsExecute(Sender: TObject); begin dmComData.SaveDialog.FileName:= edtFileNameLeft.FileName; if dmComData.SaveDialog.Execute then begin PushPop(FElevate); try SaveToFile(SynDiffEditLeft, dmComData.SaveDialog.FileName); finally PushPop(FElevate); end; edtFileNameLeft.FileName:= dmComData.SaveDialog.FileName; end; end; procedure TfrmDiffer.actSaveRightAsExecute(Sender: TObject); begin dmComData.SaveDialog.FileName:= edtFileNameRight.FileName; if dmComData.SaveDialog.Execute then begin PushPop(FElevate); try SaveToFile(SynDiffEditRight, dmComData.SaveDialog.FileName); finally PushPop(FElevate); end; edtFileNameRight.FileName:= dmComData.SaveDialog.FileName; end; end; procedure TfrmDiffer.actBinaryCompareExecute(Sender: TObject); begin mnuEdit.Enabled:= not actBinaryCompare.Checked; mnuEncoding.Enabled:= not actBinaryCompare.Checked; btnLeftEncoding.Enabled:= not actBinaryCompare.Checked; btnRightEncoding.Enabled:= not actBinaryCompare.Checked; actCopyLeftToRight.Enabled:= not actBinaryCompare.Checked; actCopyRightToLeft.Enabled:= not actBinaryCompare.Checked; actEditUndo.Enabled:= not actBinaryCompare.Checked; actEditRedo.Enabled:= not actBinaryCompare.Checked; actSave.Enabled:= not actBinaryCompare.Checked; actSaveAs.Enabled:= not actBinaryCompare.Checked; actSaveLeft.Enabled:= not actBinaryCompare.Checked; actSaveLeftAs.Enabled:= not actBinaryCompare.Checked; actSaveRight.Enabled:= not actBinaryCompare.Checked; actSaveRightAs.Enabled:= not actBinaryCompare.Checked; actIgnoreCase.Enabled:= not actBinaryCompare.Checked; actIgnoreWhiteSpace.Enabled:= not actBinaryCompare.Checked; actPaintBackground.Enabled:= not actBinaryCompare.Checked; actLineDifferences.Enabled:= not actBinaryCompare.Checked; SynDiffEditLeft.Visible:= not actBinaryCompare.Checked; SynDiffEditRight.Visible:= not actBinaryCompare.Checked; BinaryViewerLeft.Visible:= actBinaryCompare.Checked; BinaryViewerRight.Visible:= actBinaryCompare.Checked; if actBinaryCompare.Checked then begin PushPop(FElevate); try BinaryDiffList.Clear; BinaryViewerLeft.FileName:= edtFileNameLeft.Text; BinaryViewerRight.FileName:= edtFileNameRight.Text; finally PushPop(FElevate); end; if FShowIdentical then begin if not BinaryViewerLeft.IsFileOpen then raise EFOpenError.Create(BinaryViewerLeft.LastError + LineEnding + edtFileNameLeft.Text); if not BinaryViewerRight.IsFileOpen then raise EFOpenError.Create(BinaryViewerRight.LastError + LineEnding + edtFileNameRight.Text); end; StatusBar.Panels[0].Text := EmptyStr; StatusBar.Panels[1].Text := EmptyStr; StatusBar.Panels[2].Text := EmptyStr; StatusBar.Panels[3].Text := EmptyStr; end else begin BinaryViewerLeft.FileName:= EmptyStr; BinaryViewerRight.FileName:= EmptyStr; OpenFileLeft(edtFileNameLeft.Text); OpenFileRight(edtFileNameRight.Text); end; actStartCompare.Execute; end; procedure TfrmDiffer.actCancelCompareExecute(Sender: TObject); begin if not actBinaryCompare.Checked then Diff.Cancel else begin if Assigned(BinaryCompare) then begin BinaryCompare.Terminate; BinaryCompare:= nil; end; end; end; procedure TfrmDiffer.actAboutExecute(Sender: TObject); begin ShowMessage('Internal Differ tool of Double Commander.' + LineEnding + LineEnding + 'It is inspired by Flavio Etrusco''s Pariter tool.' + LineEnding + 'You can find it on: http://sourceforge.net/projects/pariter' + LineEnding + 'It is based on Angus Johnson''s excellent TDiff component.' + LineEnding + 'You can find it on: http://www.users.on.net/johnson/delphi'); end; procedure TfrmDiffer.actEditCopyExecute(Sender: TObject); begin SynDiffEditActive.CopyToClipboard; end; procedure TfrmDiffer.actEditCutExecute(Sender: TObject); begin SynDiffEditActive.CutToClipboard; end; procedure TfrmDiffer.actEditDeleteExecute(Sender: TObject); begin SynDiffEditActive.ClearSelection; end; procedure TfrmDiffer.actEditPasteExecute(Sender: TObject); begin SynDiffEditActive.PasteFromClipboard; end; procedure TfrmDiffer.actEditRedoExecute(Sender: TObject); begin SynDiffEditActive.Redo; end; procedure TfrmDiffer.actEditSelectAllExecute(Sender: TObject); begin SynDiffEditActive.SelectAll; end; procedure TfrmDiffer.actEditUndoExecute(Sender: TObject); begin SynDiffEditActive.Undo; end; procedure TfrmDiffer.actExecute(Sender: TObject); var cmd: string; begin cmd := (Sender as TAction).Name; cmd := 'cm_' + Copy(cmd, 4, Length(cmd) - 3); Commands.ExecuteCommand(cmd, []); end; procedure TfrmDiffer.actIgnoreCaseExecute(Sender: TObject); begin if actAutoCompare.Checked then actStartCompare.Execute; end; procedure TfrmDiffer.actLineDifferencesExecute(Sender: TObject); begin if actLineDifferences.Checked and (Diff.Count <> 0) then begin SynDiffEditLeft.Highlighter:= SynDiffHighlighterLeft; SynDiffEditRight.Highlighter:= SynDiffHighlighterRight; end else begin SynDiffEditLeft.Highlighter:= nil; SynDiffEditRight.Highlighter:= nil; end; SynDiffEditLeft.Repaint; SynDiffEditRight.Repaint; end; procedure TfrmDiffer.actKeepScrollingExecute(Sender: TObject); begin BinaryViewerLeft.KeepScrolling:= actKeepScrolling.Checked; BinaryViewerRight.KeepScrolling:= actKeepScrolling.Checked; end; procedure TfrmDiffer.btnLeftEncodingClick(Sender: TObject); begin pmEncodingLeft.PopUp(Mouse.CursorPos.X, Mouse.CursorPos.Y); end; procedure TfrmDiffer.btnRightEncodingClick(Sender: TObject); begin pmEncodingRight.PopUp(Mouse.CursorPos.X, Mouse.CursorPos.Y); end; procedure TfrmDiffer.edtFileNameLeftAcceptFileName(Sender: TObject; var Value: String); begin OpenFileLeft(Value); if actAutoCompare.Checked then actStartCompare.Execute; end; procedure TfrmDiffer.edtFileNameRightAcceptFileName(Sender: TObject; var Value: String); begin OpenFileRight(Value); if actAutoCompare.Checked then actStartCompare.Execute; end; procedure TfrmDiffer.FormCreate(Sender: TObject); begin ScrollLock:= 0; Diff:= TDiff.Create(Self); SynDiffEditLeft:= TSynDiffEdit.Create(Self); SynDiffEditRight:= TSynDiffEdit.Create(Self); SynDiffHighlighterLeft:= TSynDiffHighlighter.Create(SynDiffEditLeft); SynDiffHighlighterRight:= TSynDiffHighlighter.Create(SynDiffEditRight); SynDiffEditLeft.Parent:= pnlLeft; SynDiffEditRight.Parent:= pnlRight; SynDiffEditLeft.Align:= alClient; SynDiffEditRight.Align:= alClient; SynDiffEditLeft.PopupMenu:= ContextMenu; SynDiffEditRight.PopupMenu:= ContextMenu; SynDiffEditLeft.ModifiedFile:= SynDiffEditRight; SynDiffEditRight.OriginalFile:= SynDiffEditLeft; SynDiffEditLeft.OnEnter:= @SynDiffEditEnter; SynDiffEditRight.OnEnter:= @SynDiffEditEnter; SynDiffEditLeft.OnStatusChange:= @SynDiffEditLeftStatusChange; SynDiffEditRight.OnStatusChange:= @SynDiffEditRightStatusChange; // Set active editor SynDiffEditActive:= SynDiffEditLeft; BinaryDiffList:= TFPList.Create; BinaryViewerLeft:= TBinaryDiffViewer.Create(Self); BinaryViewerRight:= TBinaryDiffViewer.Create(Self); BinaryViewerLeft.OnFileOpen:= @FileOpenUAC; BinaryViewerRight.OnFileOpen:= @FileOpenUAC; BinaryViewerLeft.Visible:= False; BinaryViewerRight.Visible:= False; BinaryViewerLeft.Parent:= pnlLeft; BinaryViewerRight.Parent:= pnlRight; BinaryViewerLeft.Align:= alClient; BinaryViewerRight.Align:= alClient; BinaryViewerLeft.SecondViewer:= BinaryViewerRight; BinaryViewerRight.SecondViewer:= BinaryViewerLeft; BinaryViewerLeft.Modified:= gDifferModifiedBinaryColor; BinaryViewerRight.Modified:= gDifferModifiedBinaryColor; FontOptionsToFont(gFonts[dcfEditor], SynDiffEditLeft.Font); FontOptionsToFont(gFonts[dcfEditor], SynDiffEditRight.Font); FontOptionsToFont(gFonts[dcfViewer], BinaryViewerLeft.Font); FontOptionsToFont(gFonts[dcfViewer], BinaryViewerRight.Font); // Load settings actIgnoreCase.Checked := gDifferIgnoreCase; actKeepScrolling.Checked := gDifferKeepScrolling; actLineDifferences.Checked := gDifferLineDifferences; actPaintBackground.Checked := gDifferPaintBackground; actIgnoreWhiteSpace.Checked := gDifferIgnoreWhiteSpace; // Initialize mode actKeepScrollingExecute(actKeepScrolling); actPaintBackgroundExecute(actPaintBackground); // Initialize property storage InitPropStorage(Self); // Fill encoding menu EncodingList:= TStringList.Create; GetSupportedEncodings(EncodingList); FillEncodingMenu(miEncodingLeft, @SetEncodingLeft, 1); FillEncodingMenu(miEncodingRight, @SetEncodingRight, 2); FillEncodingMenu(pmEncodingLeft.Items, @SetEncodingLeft, 1); FillEncodingMenu(pmEncodingRight.Items, @SetEncodingRight, 2); EncodingList.Free; end; procedure TfrmDiffer.FormDestroy(Sender: TObject); begin FreeAndNil(Diff); FreeAndNil(BinaryDiffList); end; procedure TfrmDiffer.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); begin if Key = VK_ESCAPE then begin Key:= 0; Close; end; end; procedure TfrmDiffer.FormResize(Sender: TObject); begin pnlLeft.Width:= (ClientWidth div 2) - (Splitter.Width div 2); end; procedure TfrmDiffer.btnCancelClick(Sender: TObject); begin FCancel:= True; if actBinaryCompare.Checked and Assigned(BinaryCompare) then BinaryCompare.Terminate else begin Diff.Cancel; end; CloseProgressDialog; end; procedure TfrmDiffer.tmProgressTimer(Sender: TObject); begin tmProgress.Enabled:= False; ShowProgressDialog; end; procedure TfrmDiffer.BinaryCompareFinish; begin BinaryCompare:= nil; if FCancel then begin if FShowIdentical then Free; Exit; end; BinaryDiffIndex:= -1; tmProgress.Enabled:= False; StatusBar.Panels[0].Text := EmptyStr; StatusBar.Panels[1].Text := rsDiffModifies + IntToStr(BinaryDiffList.Count); StatusBar.Panels[2].Text := EmptyStr; StatusBar.Panels[3].Text := EmptyStr; actStartCompare.Enabled := True; actCancelCompare.Enabled := False; actBinaryCompare.Enabled := True; if FShowIdentical then begin CloseProgressDialog; FShowIdentical:= (BinaryDiffList.Count = 0); if FShowIdentical then ShowIdentical else begin FShowIdentical:= False; Application.QueueAsyncCall(@ShowFirstDifference, 0); ShowDialog; end; end; end; procedure TfrmDiffer.ShowDialog; begin if FModal then ShowModal else if (FWaitData = nil) then ShowOnTop else FWaitData.ShowOnTop(Self); end; procedure TfrmDiffer.ShowIdentical; var Message: String; begin Message:= rsDiffFilesIdentical + LineEnding + LineEnding; Message+= edtFileNameLeft.Text + LineEnding + edtFileNameRight.Text; if MessageDlg(rsToolDiffer, Message, mtWarning, [mbIgnore, mbCancel], 0, mbIgnore) = mrCancel then Close else begin FShowIdentical:= False; ShowDialog; end; end; procedure TfrmDiffer.ShowProgressDialog; var lblPrompt : TLabel; btnCancel : TBitBtn; pbProgress: TProgressBar; begin frmProgress := TModalDialog.CreateNew(nil, 0); with frmProgress do begin BorderStyle := bsDialog; Position := poOwnerFormCenter; AutoSize := True; Height := 120; ChildSizing.TopBottomSpacing := 8; ChildSizing.LeftRightSpacing := 8; Caption := Self.Caption; lblPrompt := TLabel.Create(frmProgress); with lblPrompt do begin Parent := frmProgress; Caption := rsDiffComparing; Top := 6; Left := 6; end; pbProgress:= TProgressBar.Create(frmProgress); with pbProgress do begin Parent := frmProgress; Style:= pbstMarquee; Left := 6; AnchorToNeighbour(akTop, 6, lblPrompt); Constraints.MinWidth := Math.Max(280, Screen.Width div 4); end; btnCancel := TBitBtn.Create(frmProgress); with btnCancel do begin AutoSize := True; Parent := frmProgress; Kind := bkCancel; Cancel := True; OnClick:= @btnCancelClick; Anchors := [akTop, akRight]; AnchorToNeighbour(akTop, 18, pbProgress); AnchorSide[akRight].Control := pbProgress; AnchorSide[akRight].Side := asrCenter; end; if FModal then ShowModal else if (FWaitData = nil) then ShowOnTop else FWaitData.ShowOnTop(frmProgress); end; end; procedure TfrmDiffer.CloseProgressDialog; begin if Assigned(frmProgress) then begin frmProgress.Close; FreeAndNil(frmProgress); end; end; procedure TfrmDiffer.FormClose(Sender: TObject; var CloseAction: TCloseAction); begin CloseAction:= caFree; // Save settings gDifferIgnoreCase := actIgnoreCase.Checked; gDifferKeepScrolling := actKeepScrolling.Checked; gDifferLineDifferences := actLineDifferences.Checked; gDifferPaintBackground := actPaintBackground.Checked; gDifferIgnoreWhiteSpace := actIgnoreWhiteSpace.Checked; end; procedure TfrmDiffer.FormCloseQuery(Sender: TObject; var CanClose: boolean); var Result: TMyMsgResult; begin if SynDiffEditLeft.Modified then begin Result:= msgYesNoCancel(Format(rsMsgFileChangedSave, [edtFileNameLeft.FileName])); CanClose:= Result <> mmrCancel; if Result = mmrYes then actSaveLeft.Execute else if Result = mmrCancel then Exit; end; if SynDiffEditRight.Modified then begin Result:= msgYesNoCancel(Format(rsMsgFileChangedSave, [edtFileNameRight.FileName])); CanClose:= Result <> mmrCancel; if Result = mmrYes then actSaveRight.Execute else if Result = mmrCancel then Exit; end; end; procedure TfrmDiffer.Clear(bLeft, bRight: Boolean); begin if bLeft then begin SynDiffEditLeft.Lines.Clear; SetLength(HashListLeft, 0); end; if bRight then begin SynDiffEditRight.Lines.Clear; SetLength(HashListRight, 0); end; Diff.Clear; StatusBar.Panels[0].Text := EmptyStr; StatusBar.Panels[1].Text := EmptyStr; StatusBar.Panels[2].Text := EmptyStr; StatusBar.Panels[3].Text := EmptyStr; actStartCompare.Enabled := True; end; procedure TfrmDiffer.cm_CopyLeftToRight(const Params: array of string); var I, iStart, iFinish: Integer; begin I := SynDiffEditLeft.CaretY - 1; iStart:= SynDiffEditLeft.DiffBegin(I); iFinish:= SynDiffEditLeft.DiffEnd(I); if SynDiffEditLeft.Lines.Kind[iStart] <> ckAdd then begin for I:= iStart to iFinish do begin SynDiffEditRight.Lines[I]:= SynDiffEditLeft.Lines[I]; if SynDiffEditLeft.Lines.Kind[I] = ckDelete then begin SynDiffEditLeft.Lines.Kind[I]:= ckNone; SynDiffEditRight.Lines.SetKindAndNumber(I, ckNone, 0); end; end; end else begin for I:= iStart to iFinish do begin if SynDiffEditLeft.Lines[iStart] <> EmptyStr then begin SynDiffEditRight.Lines[iStart]:= SynDiffEditLeft.Lines[iStart]; Inc(iStart); end else begin SynDiffEditLeft.Lines.Delete(iStart); SynDiffEditRight.Lines.Delete(iStart); end; end; end; SynDiffEditLeft.Renumber; SynDiffEditRight.Renumber; end; procedure TfrmDiffer.cm_CopyRightToLeft(const Params: array of string); var I, iStart, iFinish: Integer; begin I := SynDiffEditRight.CaretY - 1; iStart:= SynDiffEditRight.DiffBegin(I); iFinish:= SynDiffEditRight.DiffEnd(I); if SynDiffEditLeft.Lines.Kind[iStart] <> ckDelete then begin for I:= iStart to iFinish do begin SynDiffEditLeft.Lines[I]:= SynDiffEditRight.Lines[I]; if SynDiffEditRight.Lines.Kind[I] = ckAdd then begin SynDiffEditRight.Lines.Kind[I]:= ckNone; SynDiffEditLeft.Lines.SetKindAndNumber(I, ckNone, 0); end; end; end else begin for I:= iStart to iFinish do begin if SynDiffEditRight.Lines[iStart] <> EmptyStr then begin SynDiffEditLeft.Lines[iStart]:= SynDiffEditRight.Lines[iStart]; Inc(iStart); end else begin SynDiffEditLeft.Lines.Delete(iStart); SynDiffEditRight.Lines.Delete(iStart); end; end; end; SynDiffEditLeft.Renumber; SynDiffEditRight.Renumber; end; procedure TfrmDiffer.cm_Exit(const Params: array of string); begin Close; end; procedure TfrmDiffer.cm_FirstDifference(const Params: array of string); var Line: Integer; Kind: TChangeKind; begin if actBinaryCompare.Checked then begin if BinaryDiffList.Count > 0 then begin BinaryDiffIndex:= 0; BinaryViewerLeft.Position:= PtrInt(BinaryDiffList[BinaryDiffIndex]); if not actKeepScrolling.Checked then BinaryViewerRight.Position:= PtrInt(BinaryDiffList[BinaryDiffIndex]); end; end else begin // Start at first line Line := 0; if Line = SynDiffEditLeft.Lines.Count then Exit; // Skip unmodified lines Kind := ckNone; while (Line < SynDiffEditLeft.Lines.Count - 1) and (SynDiffEditLeft.Lines.Kind[Line] = Kind) do Inc(Line); Inc(Line); SynDiffEditLeft.CaretY := Line; SynDiffEditLeft.TopLine := Line; SynDiffEditRight.CaretY := Line; if not actKeepScrolling.Checked then begin SynDiffEditRight.TopLine := Line; end; end; end; procedure TfrmDiffer.cm_LastDifference(const Params: array of string); var Line: Integer; Kind: TChangeKind; begin if actBinaryCompare.Checked then begin if BinaryDiffList.Count > 0 then begin BinaryDiffIndex:= BinaryDiffList.Count - 1; BinaryViewerLeft.Position:= PtrInt(BinaryDiffList[BinaryDiffIndex]); if not actKeepScrolling.Checked then BinaryViewerRight.Position:= PtrInt(BinaryDiffList[BinaryDiffIndex]); end; end else begin Line := SynDiffEditLeft.Lines.Count - 1; if Line = 0 then Exit; // Skip unmodified lines Kind := ckNone; while (Line > 0) and (SynDiffEditLeft.Lines.Kind[Line] = Kind) do Dec(Line); // Find top line of previous difference Kind:= SynDiffEditLeft.Lines.Kind[Line]; while (Line > 0) and (SynDiffEditLeft.Lines.Kind[Line] = Kind) do Dec(Line); if (Line <> 0) then Inc(Line, 2); SynDiffEditLeft.CaretY := Line; SynDiffEditLeft.TopLine := Line; SynDiffEditRight.CaretY := Line; if not actKeepScrolling.Checked then begin SynDiffEditRight.TopLine := Line; end; end; end; procedure TfrmDiffer.cm_NextDifference(const Params: array of string); var Line: Integer; Kind: TChangeKind; begin if actBinaryCompare.Checked then begin if BinaryDiffIndex < BinaryDiffList.Count - 1 then begin BinaryDiffIndex:= BinaryDiffIndex + 1; BinaryViewerLeft.Position:= PtrInt(BinaryDiffList[BinaryDiffIndex]); if not actKeepScrolling.Checked then BinaryViewerRight.Position:= PtrInt(BinaryDiffList[BinaryDiffIndex]); end; end else begin Line := SynDiffEditLeft.CaretY - 1; if Line = SynDiffEditLeft.Lines.Count - 1 then Exit; // Skip lines with current difference type Kind := SynDiffEditLeft.Lines.Kind[Line]; while (Line < SynDiffEditLeft.Lines.Count - 1) and (SynDiffEditLeft.Lines.Kind[Line] = Kind) do Inc(Line); if SynDiffEditLeft.Lines.Kind[Line] = ckNone then begin // Skip unmodified lines Kind := ckNone; while (Line < SynDiffEditLeft.Lines.Count - 1) and (SynDiffEditLeft.Lines.Kind[Line] = Kind) do Inc(Line); end; Inc(Line); SynDiffEditLeft.CaretY := Line; SynDiffEditLeft.TopLine := Line; SynDiffEditRight.CaretY := Line; if not actKeepScrolling.Checked then begin SynDiffEditRight.TopLine := Line; end; end; end; procedure TfrmDiffer.cm_PrevDifference(const Params: array of string); var Line: Integer; Kind: TChangeKind; begin if actBinaryCompare.Checked then begin if BinaryDiffIndex > 0 then begin BinaryDiffIndex:= BinaryDiffIndex - 1; BinaryViewerLeft.Position:= PtrInt(BinaryDiffList[BinaryDiffIndex]); if not actKeepScrolling.Checked then BinaryViewerRight.Position:= PtrInt(BinaryDiffList[BinaryDiffIndex]); end; end else begin Line := SynDiffEditLeft.CaretY - 1; if Line = 0 then Exit; // Skip lines with current difference type Kind := SynDiffEditLeft.Lines.Kind[Line]; while (Line > 0) and (SynDiffEditLeft.Lines.Kind[Line] = Kind) do Dec(Line); if SynDiffEditLeft.Lines.Kind[Line] = ckNone then begin // Skip unmodified lines Kind := ckNone; while (Line > 0) and (SynDiffEditLeft.Lines.Kind[Line] = Kind) do Dec(Line); end; // Find top line of previous difference Kind:= SynDiffEditLeft.Lines.Kind[Line]; while (Line > 0) and (SynDiffEditLeft.Lines.Kind[Line] = Kind) do Dec(Line); if (Line <> 0) then Inc(Line, 2); SynDiffEditLeft.CaretY := Line; SynDiffEditLeft.TopLine := Line; SynDiffEditRight.CaretY := Line; if not actKeepScrolling.Checked then begin SynDiffEditRight.TopLine := Line; end; end; end; procedure TfrmDiffer.cm_Reload(const Params: array of string); begin OpenFileLeft(edtFileNameLeft.FileName); OpenFileRight(edtFileNameRight.FileName); if actAutoCompare.Checked then actStartCompare.Execute; end; procedure TfrmDiffer.cm_SaveLeft(const Params: array of string); begin PushPop(FElevate); try SaveToFile(SynDiffEditLeft, edtFileNameLeft.FileName); finally PushPop(FElevate); end; end; procedure TfrmDiffer.cm_SaveRight(const Params: array of string); begin PushPop(FElevate); try SaveToFile(SynDiffEditRight, edtFileNameRight.FileName); finally PushPop(FElevate); end; end; constructor TfrmDiffer.Create(TheOwner: TComponent); var HMForm: THMForm; begin inherited Create(TheOwner); FCommands := TFormCommands.Create(Self, actionList); HMForm := HotMan.Register(Self, HotkeysCategory); HMForm.RegisterActionList(actionList); end; destructor TfrmDiffer.Destroy; begin BinaryViewerLeft.SecondViewer:= nil; BinaryViewerRight.SecondViewer:= nil; HotMan.UnRegister(Self); inherited Destroy; if Assigned(FWaitData) then FWaitData.Done; end; procedure TfrmDiffer.AfterConstruction; begin inherited AfterConstruction; ToolBar.ImagesWidth:= gToolIconsSize; ToolBar.SetButtonSize(gToolIconsSize + ScaleX(6, 96), gToolIconsSize + ScaleY(6, 96)); end; procedure TfrmDiffer.BuildHashList(bLeft, bRight: Boolean); var I: Integer; begin if bLeft then begin SetLength(HashListLeft, SynDiffEditLeft.Lines.Count); for I := 0 to SynDiffEditLeft.Lines.Count - 1 do HashListLeft[I]:= Integer(HashString(SynDiffEditLeft.Lines[I], actIgnoreCase.Checked, actIgnoreWhiteSpace.Checked)); end; if bRight then begin SetLength(HashListRight, SynDiffEditRight.Lines.Count); for I := 0 to SynDiffEditRight.Lines.Count - 1 do HashListRight[I]:= Integer(HashString(SynDiffEditRight.Lines[I], actIgnoreCase.Checked, actIgnoreWhiteSpace.Checked)); end; actStartCompare.Enabled := (Length(HashListLeft) > 0) and (Length(HashListRight) > 0); actCopyLeftToRight.Enabled := actStartCompare.Enabled; actCopyRightToLeft.Enabled := actStartCompare.Enabled; end; procedure TfrmDiffer.SetColors(cAdded, cDeleted, cModified: TColor); begin with SynDiffEditLeft do begin Colors.Added:= cAdded; Colors.Deleted:= cDeleted; Colors.Modified:= cModified; end; with SynDiffEditRight do begin Colors.Added:= cAdded; Colors.Deleted:= cDeleted; Colors.Modified:= cModified; end; end; procedure TfrmDiffer.ChooseEncoding(SynDiffEdit: TSynDiffEdit); begin if SynDiffEdit = SynDiffEditLeft then begin ChooseEncoding(miEncodingLeft, SynDiffEdit.Encoding); ChooseEncoding(pmEncodingLeft.Items, SynDiffEdit.Encoding); end else begin ChooseEncoding(miEncodingRight, SynDiffEdit.Encoding); ChooseEncoding(pmEncodingRight.Items, SynDiffEdit.Encoding); end; end; procedure TfrmDiffer.ChooseEncoding(MenuItem: TMenuItem; Encoding: String); var I: Integer; begin Encoding:= NormalizeEncoding(Encoding); for I:= 0 to MenuItem.Count - 1 do if SameText(NormalizeEncoding(MenuItem.Items[I].Caption), Encoding) then MenuItem.Items[I].Checked:= True; end; procedure TfrmDiffer.FillEncodingMenu(TheOwner: TMenuItem; MenuHandler: TNotifyEvent; GroupIndex: LongInt); var I: Integer; mi: TMenuItem; begin for I:= 0 to EncodingList.Count - 1 do begin mi:= TMenuItem.Create(TheOwner); mi.Caption:= EncodingList[I]; mi.RadioItem:= True; mi.GroupIndex:= GroupIndex; mi.OnClick:= MenuHandler; TheOwner.Add(mi); end; end; procedure TfrmDiffer.LoadFromFile(SynDiffEdit: TSynDiffEdit; const FileName: String); var AText: String; fsFileStream: TFileStreamUAC; begin try fsFileStream:= TFileStreamUAC.Create(FileName, fmOpenRead or fmShareDenyNone); try SetLength(AText, fsFileStream.Size); fsFileStream.Read(Pointer(AText)^, Length(AText)); if Length(SynDiffEdit.Encoding) = 0 then begin SynDiffEdit.Encoding:= DetectEncoding(AText); ChooseEncoding(SynDiffEdit); end; with SynDiffEdit do begin if (Encoding = EncodingUTF16LE) or (Encoding = EncodingUTF16BE) then begin AText:= Copy(AText, 3, MaxInt); // Skip BOM end; AText:= ConvertEncoding(AText, Encoding, EncodingUTF8); end; SynDiffEdit.Lines.Text:= AText; // Determine line break style SynDiffEdit.Lines.TextLineBreakStyle := GuessLineBreakStyle(AText); finally FreeAndNil(fsFileStream); end; except on E: Exception do begin E.Message:= E.Message + LineEnding + FileName; if FShowIdentical then raise; msgError(E.Message); end; end; end; procedure TfrmDiffer.SaveToFile(SynDiffEdit: TSynDiffEdit; const FileName: String); var AText: String; AMode: LongWord; begin AText := EmptyStr; if (SynDiffEdit.Encoding = EncodingUTF16LE) then AText := UTF16LEBOM else if (SynDiffEdit.Encoding = EncodingUTF16BE) then begin AText := UTF16BEBOM end; with TStringListEx.Create do try Assign(SynDiffEdit.Lines); // remove fake lines RemoveFake; // restore encoding AText+= ConvertEncoding(Text, EncodingUTF8, SynDiffEdit.Encoding); finally Free; end; // save to file try if not FileExistsUAC(FileName) then AMode:= fmCreate else begin AMode:= fmOpenWrite or fmShareDenyWrite; end; with TFileStreamUAC.Create(FileName, AMode) do try WriteBuffer(Pointer(AText)^, Length(AText)); if (AMode <> fmCreate) then Size:= Position; finally Free; end; SynDiffEdit.Modified:= False; // needed for the undo stack except on E: Exception do msgError(rsMsgErrSaveFile + ' ' + FileName + LineEnding + E.Message); end; end; procedure TfrmDiffer.OpenFileLeft(const FileName: String); begin PushPop(FElevate); try if not FileExistsUAC(FileName) then Exit; if actBinaryCompare.Checked then begin BinaryDiffList.Clear; BinaryViewerLeft.FileName:= FileName end else begin Clear(True, False); LoadFromFile(SynDiffEditLeft, FileName); BuildHashList(True, False); SynDiffEditLeft.Repaint; end; finally PushPop(FElevate); end; end; procedure TfrmDiffer.OpenFileRight(const FileName: String); begin PushPop(FElevate); try if not FileExistsUAC(FileName) then Exit; if actBinaryCompare.Checked then begin BinaryDiffList.Clear; BinaryViewerRight.FileName:= FileName end else begin Clear(False, True); LoadFromFile(SynDiffEditRight, FileName); BuildHashList(False, True); SynDiffEditRight.Repaint; end; finally PushPop(FElevate); end; end; procedure TfrmDiffer.SetEncodingLeft(Sender: TObject); begin SynDiffEditLeft.Encoding:= (Sender as TMenuItem).Caption; ChooseEncoding(miEncodingLeft, SynDiffEditLeft.Encoding); ChooseEncoding(pmEncodingLeft.Items, SynDiffEditLeft.Encoding); actReload.Execute; end; procedure TfrmDiffer.SetEncodingRight(Sender: TObject); begin SynDiffEditRight.Encoding:= (Sender as TMenuItem).Caption; ChooseEncoding(miEncodingRight, SynDiffEditRight.Encoding); ChooseEncoding(pmEncodingRight.Items, SynDiffEditRight.Encoding); actReload.Execute; end; procedure TfrmDiffer.SynDiffEditEnter(Sender: TObject); begin SynDiffEditActive:= (Sender as TSynDiffEdit); end; procedure TfrmDiffer.ShowFirstDifference(Data: PtrInt); begin cm_FirstDifference([]); end; procedure TfrmDiffer.SynDiffEditLeftStatusChange(Sender: TObject; Changes: TSynStatusChanges); begin if (actKeepScrolling.Checked) and (ScrollLock = 0) and ((scTopLine in Changes) or (scLeftChar in Changes)) then try Inc(ScrollLock); while (SynDiffEditRight.PaintLock <> 0) do Sleep(1); SynDiffEditRight.TopLine:= SynDiffEditLeft.TopLine; SynDiffEditRight.LeftChar:= SynDiffEditLeft.LeftChar; finally Dec(ScrollLock); end; end; procedure TfrmDiffer.SynDiffEditRightStatusChange(Sender: TObject; Changes: TSynStatusChanges); begin if (actKeepScrolling.Checked) and (ScrollLock = 0) and ((scTopLine in Changes) or (scLeftChar in Changes)) then try Inc(ScrollLock); while (SynDiffEditLeft.PaintLock <> 0) do Sleep(1); SynDiffEditLeft.TopLine:= SynDiffEditRight.TopLine; SynDiffEditLeft.LeftChar:= SynDiffEditRight.LeftChar; finally Dec(ScrollLock); end; end; initialization TFormCommands.RegisterCommandsForm(TfrmDiffer, HotkeysCategory, @rsHotkeyCategoryDiffer); end.