Index: src/fdiffer.pas
===================================================================
--- src/fdiffer.pas	(revision 7907)
+++ src/fdiffer.pas	(working copy)
@@ -29,7 +29,7 @@
   Classes, SysUtils, FileUtil, Forms, Controls, Dialogs, Menus, ComCtrls,
   ActnList, ExtCtrls, EditBtn, Buttons, SynEdit, uSynDiffControls,
   uPariterControls, uDiffOND, uFormCommands, uHotkeyManager, uOSForms,
-  uBinaryDiffViewer;
+  uBinaryDiffViewer, uShowForm;
 
 type
 
@@ -214,6 +214,7 @@
     EncodingList: TStringList;
     ScrollLock: LongInt;
     FShowIdentical: Boolean;
+    FWaitData: TWaitData;
     FCommands: TFormCommands;
     procedure ShowIdentical;
     procedure Clear(bLeft, bRight: Boolean);
@@ -248,7 +249,7 @@
     procedure cm_SaveRight(const Params: array of string);
   end; 
 
-procedure ShowDiffer(const FileNameLeft, FileNameRight: String);
+procedure ShowDiffer(const FileNameLeft, FileNameRight: String; WaitData: TWaitData = nil);
 
 implementation
 
@@ -261,10 +262,11 @@
 const
   HotkeysCategory = 'Differ';
 
-procedure ShowDiffer(const FileNameLeft, FileNameRight: String);
+procedure ShowDiffer(const FileNameLeft, FileNameRight: String; WaitData: TWaitData = nil);
 begin
   with TfrmDiffer.Create(Application) do
   begin
+    FWaitData := WaitData;
     edtFileNameLeft.Text:= FileNameLeft;
     edtFileNameRight.Text:= FileNameRight;
     FShowIdentical:= actAutoCompare.Checked;
@@ -984,6 +986,7 @@
   BinaryViewerRight.SecondViewer:= nil;
   HotMan.UnRegister(Self);
   inherited Destroy;
+  if Assigned(FWaitData) then FWaitData.Done;
 end;
 
 procedure TfrmDiffer.BuildHashList(bLeft, bRight: Boolean);
Index: src/feditor.pas
===================================================================
--- src/feditor.pas	(revision 7907)
+++ src/feditor.pas	(working copy)
@@ -161,7 +161,7 @@
     sEncodingIn,
     sEncodingOut,
     sOriginalText: String;
-    FWaitData: TEditorWaitData;
+    FWaitData: TWaitData;
     FCommands: TFormCommands;
 
     property Commands: TFormCommands read FCommands implements IFormCommands;
@@ -222,8 +222,7 @@
      procedure cm_EditRplc(const {%H-}Params:array of string);
   end;
 
-  procedure ShowEditor(WaitData: TEditorWaitData);
-  function ShowEditor(const sFileName: String): TfrmEditor;
+  procedure ShowEditor(const sFileName: String; WaitData: TWaitData = nil);
 
 implementation
 
@@ -234,29 +233,24 @@
   uLng, uShowMsg, fEditSearch, uGlobs, fOptions, DCClassesUtf8,
   uOSUtils, uConvEncoding, fOptionsToolsEditor, uDCUtils, uClipboard;
 
-function ShowEditor(const sFileName: String): TfrmEditor;
+procedure ShowEditor(const sFileName: String; WaitData: TWaitData = nil);
+var
+  Editor: TfrmEditor;
 begin
-  Result := TfrmEditor.Create(Application);
+  Editor := TfrmEditor.Create(Application);
+  Editor.FWaitData := WaitData;
 
   if sFileName = '' then
-    Result.cm_FileNew([''])
+    Editor.cm_FileNew([''])
   else
   begin
-    if not Result.OpenFile(sFileName) then
+    if not Editor.OpenFile(sFileName) then
       Exit;
   end;
 
-  Result.ShowOnTop;
+  Editor.ShowOnTop;
 end;
 
-procedure ShowEditor(WaitData: TEditorWaitData);
-var
-  Editor: TfrmEditor;
-begin
-  Editor:= ShowEditor(WaitData.FileName);
-  Editor.FWaitData:= WaitData
-end;
-
 procedure TfrmEditor.FormCreate(Sender: TObject);
 var
   i:Integer;
@@ -536,7 +530,7 @@
 begin
   HotMan.UnRegister(Self);
   inherited Destroy;
-  if Assigned(FWaitData) then EditDone(FWaitData);
+  if Assigned(FWaitData) then FWaitData.Done;
 end;
 
 
Index: src/ffileexecuteyourself.pas
===================================================================
--- src/ffileexecuteyourself.pas	(revision 7907)
+++ src/ffileexecuteyourself.pas	(working copy)
@@ -46,13 +46,13 @@
     procedure FormCreate(Sender: TObject);
   private
     FFileSource: IFileSource;
-    FWaitData: TEditorWaitData;
+    FWaitData: TWaitData;
   public
     constructor Create(TheOwner: TComponent; aFileSource: IFileSource; const FileName, FromPath: String); reintroduce;
     destructor Destroy; override;
   end; 
 
-  procedure ShowFileEditExternal(aWaitData: TEditorWaitData);
+  procedure ShowFileEditExternal(const FileName, FromPath: string; aWaitData: TWaitData);
   function ShowFileExecuteYourSelf(aFileView: TFileView; aFile: TFile; bWithAll: Boolean): Boolean;
 
 implementation
@@ -62,13 +62,10 @@
 uses
   LCLProc, uTempFileSystemFileSource, uFileSourceOperation, uShellExecute, DCOSUtils;
 
-procedure ShowFileEditExternal(aWaitData: TEditorWaitData);
-var
-  APath: String;
+procedure ShowFileEditExternal(const FileName, FromPath: string; aWaitData: TWaitData);
 begin
-  APath:= aWaitData.TargetFileSource.CurrentAddress + aWaitData.TargetPath;
   // Create wait window
-  with TfrmFileExecuteYourSelf.Create(Application, nil, ExtractFileName(aWaitData.FileName), APath) do
+  with TfrmFileExecuteYourSelf.Create(Application, nil, FileName, FromPath) do
   begin
     FWaitData:= aWaitData;
     // Show wait window
@@ -155,7 +152,7 @@
   // Delete the temporary file source and all files inside.
   FFileSource:= nil;
   inherited Destroy;
-  if Assigned(FWaitData) then EditDone(FWaitData);
+  if Assigned(FWaitData) then FWaitData.Done;
 end;
 
 end.
Index: src/umaincommands.pas
===================================================================
--- src/umaincommands.pas	(revision 7907)
+++ src/umaincommands.pas	(working copy)
@@ -505,31 +505,13 @@
 procedure TMainCommands.OnEditCopyOutStateChanged(Operation: TFileSourceOperation;
                                                   State: TFileSourceOperationState);
 var
-  aFile: TFile;
   WaitData: TEditorWaitData;
-  aFileSource: ITempFileSystemFileSource;
-  aCopyOutOperation: TFileSourceCopyOperation;
 begin
   if (State = fsosStopped) and (Operation.Result = fsorFinished) then
   begin
-    aCopyOutOperation := Operation as TFileSourceCopyOperation;
-    aFileSource := aCopyOutOperation.TargetFileSource as ITempFileSystemFileSource;
-
     try
-      aFile := aCopyOutOperation.SourceFiles[0];
-
-      WaitData:= TEditorWaitData.Create;
-
+      WaitData := TEditorWaitData.Create(Operation as TFileSourceCopyOperation);
       try
-        WaitData.TargetPath:= aCopyOutOperation.SourceFiles.Path;
-
-        ChangeFileListRoot(aFileSource.FileSystemRoot, aCopyOutOperation.SourceFiles);
-
-        WaitData.FileName:= aFile.FullPath;
-        WaitData.SourceFileSource:= aFileSource;
-        WaitData.FileTime:= mbFileAge(WaitData.FileName);
-        WaitData.TargetFileSource:= aCopyOutOperation.FileSource as IFileSource;
-
         ShowEditorByGlob(WaitData);
       except
         WaitData.Free;
@@ -1737,9 +1719,6 @@
   ActiveFile: TFile = nil;
   AllFiles: TFiles = nil;
   SelectedFiles: TFiles = nil;
-  TempFiles: TFiles = nil;
-  TempFileSource: ITempFileSystemFileSource = nil;
-  Operation: TFileSourceOperation;
   aFileSource: IFileSource;
   sCmd: string = '';
   sParams: string = '';
@@ -1767,44 +1746,8 @@
     // Default to using the file source directly.
     aFileSource := ActiveFrame.FileSource;
 
-    // If files are links to local files
-    if (fspLinksToLocalFiles in ActiveFrame.FileSource.Properties) then
-      begin
-        for I := 0 to SelectedFiles.Count - 1 do
-          begin
-            aFile := SelectedFiles[I];
-            ActiveFrame.FileSource.GetLocalName(aFile);
-          end;
-      end
-    // If files not directly accessible copy them to temp file source.
-    else if not (fspDirectAccess in ActiveFrame.FileSource.Properties) then
-    begin
-      if not (fsoCopyOut in ActiveFrame.FileSource.GetOperationsTypes) then
-      begin
-        msgWarning(rsMsgErrNotSupported);
-        Exit;
-      end;
-
-      TempFiles := SelectedFiles.Clone;
-
-      TempFileSource := TTempFileSystemFileSource.GetFileSource;
-
-      Operation := ActiveFrame.FileSource.CreateCopyOutOperation(
-                       TempFileSource,
-                       TempFiles,
-                       TempFileSource.FileSystemRoot);
-
-      if Assigned(Operation) then
-      begin
-        Operation.AddStateChangedListener([fsosStopped], @OnCopyOutStateChanged);
-        OperationsManager.AddOperation(Operation);
-      end
-      else
-      begin
-        msgWarning(rsMsgErrNotSupported);
-      end;
+    if PrepareData(ActiveFrame.FileSource, SelectedFiles, @OnCopyOutStateChanged) <> pdrSynchronous then
       Exit;
-    end;
 
     try
       aFile := SelectedFiles[0];
@@ -1875,7 +1818,6 @@
     FreeAndNil(sl);
     FreeAndNil(AllFiles);
     FreeAndNil(SelectedFiles);
-    FreeAndNil(TempFiles);
     FreeAndNil(ActiveFile);
   end;
 end;
@@ -2026,10 +1968,7 @@
 var
   i: Integer;
   aFile: TFile;
-  TempFiles: TFiles;
   SelectedFiles: TFiles = nil;
-  Operation: TFileSourceOperation;
-  TempFileSource: ITempFileSystemFileSource = nil;
   sCmd: string = '';
   sParams: string = '';
   sStartPath: string = '';
@@ -2037,44 +1976,9 @@
   with frmMain do
   try
     SelectedFiles := ActiveFrame.CloneSelectedOrActiveFiles;
-    // If files are links to local files
-    if (fspLinksToLocalFiles in ActiveFrame.FileSource.Properties) then
-      begin
-        for I := 0 to SelectedFiles.Count - 1 do
-          begin
-            aFile := SelectedFiles[I];
-            ActiveFrame.FileSource.GetLocalName(aFile);
-          end;
-      end
-    // If files not directly accessible copy them to temp file source.
-    else if not (fspDirectAccess in ActiveFrame.FileSource.Properties) then
-    begin
-      if not (fsoCopyOut in ActiveFrame.FileSource.GetOperationsTypes) then
-      begin
-        msgWarning(rsMsgErrNotSupported);
-        Exit;
-      end;
 
-      TempFiles := SelectedFiles.Clone;
-
-      TempFileSource := TTempFileSystemFileSource.GetFileSource;
-
-      Operation := ActiveFrame.FileSource.CreateCopyOutOperation(
-                       TempFileSource,
-                       TempFiles,
-                       TempFileSource.FileSystemRoot);
-
-      if Assigned(Operation) then
-      begin
-        Operation.AddStateChangedListener([fsosStopped], @OnEditCopyOutStateChanged);
-        OperationsManager.AddOperation(Operation);
-      end
-      else
-      begin
-        msgWarning(rsMsgErrNotSupported);
-      end;
+    if PrepareData(ActiveFrame.FileSource, SelectedFiles, @OnEditCopyOutStateChanged) <> pdrSynchronous then
       Exit;
-    end;
 
     try
       for i := 0 to SelectedFiles.Count - 1 do
@@ -2655,15 +2559,18 @@
 
 procedure TMainCommands.cm_CompareContents(const Params: array of string);
 var
-  FilesToCompare: TStringList = nil;
-  DirsToCompare: TStringList = nil;
+  FilesNumber: Integer = 0;
+  DirsNumber: Integer = 0;
 
-  procedure AddItem(const aFile: TFile);
+  procedure CountFiles(const Files: TFiles);
+  var I: Integer;
   begin
-    if not aFile.IsDirectory then
-      FilesToCompare.Add(aFile.FullPath)
-    else
-      DirsToCompare.Add(aFile.FullPath);
+    if Assigned(Files) then
+      for I := 0 to Files.Count - 1 do
+        if Files[I].IsDirectory then
+          Inc(DirsNumber)
+        else
+          Inc(FilesNumber);
   end;
 
 var
@@ -2671,31 +2578,19 @@
   Param: String;
   ActiveSelectedFiles: TFiles = nil;
   NotActiveSelectedFiles: TFiles = nil;
+  FirstFileSource: IFileSource = nil;
+  FirstFileSourceFiles: TFiles = nil;
+  SecondFileSource: IFileSource = nil;
+  SecondFileSourceFiles: TFiles = nil;
 begin
   with frmMain do
   begin
-    // For now works only for file source with direct access.
-    // Later use temporary file system for other file sources.
-
-    try
-      FilesToCompare := TStringList.Create;
-      DirsToCompare := TStringList.Create;
       Param := GetDefaultParam(Params);
 
       if Param = 'dir' then
-      begin
-        DirsToCompare.Add(FrameLeft.CurrentPath);
-        DirsToCompare.Add(FrameRight.CurrentPath);
-      end
+        ShowDifferByGlob(FrameLeft.CurrentPath, FrameRight.CurrentPath)
       else
       begin
-        // For now works only for file source with direct access.
-        if not (fspDirectAccess in ActiveFrame.FileSource.Properties) then
-        begin
-          msgWarning(rsMsgNotImplemented);
-          Exit;
-        end;
-
         try
           ActiveSelectedFiles := ActiveFrame.CloneSelectedOrActiveFiles;
 
@@ -2719,31 +2614,31 @@
 
             if NotActiveSelectedFiles.Count = 1 then
             begin
-              // For now works only for file source with direct access.
-              if not (fspDirectAccess in NotActiveFrame.FileSource.Properties) then
-              begin
-                msgWarning(rsMsgNotImplemented);
-                Exit;
-              end;
 
               { compare single selected files in both panels }
 
               case gResultingFramePositionAfterCompare of
                 rfpacActiveOnLeft:
-                  begin;
-                    AddItem(ActiveSelectedFiles[0]);
-                    AddItem(NotActiveSelectedFiles[0]);
+                  begin
+                    FirstFileSource := ActiveFrame.FileSource;
+                    FirstFileSourceFiles := ActiveSelectedFiles;
+                    SecondFileSource := NotActiveFrame.FileSource;
+                    SecondFileSourceFiles := NotActiveSelectedFiles;
                   end;
                 rfpacLeftOnLeft:
                   begin
                     if ActiveFrame = FrameLeft then
                     begin
-                      AddItem(ActiveSelectedFiles[0]);
-                      AddItem(NotActiveSelectedFiles[0]);
+                      FirstFileSource := ActiveFrame.FileSource;
+                      FirstFileSourceFiles := ActiveSelectedFiles;
+                      SecondFileSource := NotActiveFrame.FileSource;
+                      SecondFileSourceFiles := NotActiveSelectedFiles;
                     end
                     else begin
-                      AddItem(NotActiveSelectedFiles[0]);
-                      AddItem(ActiveSelectedFiles[0]);
+                      FirstFileSource := NotActiveFrame.FileSource;
+                      FirstFileSourceFiles := NotActiveSelectedFiles;
+                      SecondFileSource := ActiveFrame.FileSource;
+                      SecondFileSourceFiles := ActiveSelectedFiles;
                     end;
                   end;
                end;
@@ -2760,45 +2655,39 @@
           begin
             { compare all selected files in active frame }
 
-            for I := 0 to ActiveSelectedFiles.Count - 1 do
-              AddItem(ActiveSelectedFiles[I]);
+            FirstFileSource := ActiveFrame.FileSource;
+            FirstFileSourceFiles := ActiveSelectedFiles;
           end;
 
+          CountFiles(FirstFileSourceFiles);
+          CountFiles(SecondFileSourceFiles);
+
+          if ((FilesNumber > 0) and (DirsNumber > 0))
+          or ((FilesNumber = 1) or (DirsNumber = 1)) then
+            // Either files or directories must be selected and more than one.
+            MsgWarning(rsMsgInvalidSelection)
+          else if (FilesNumber = 0) and (DirsNumber = 0) then
+            MsgWarning(rsMsgNoFilesSelected)
+          else if (FilesNumber > 2) and not gExternalTools[etDiffer].Enabled then
+            MsgWarning(rsMsgTooManyFilesSelected)
+          else if (DirsNumber > 0) and not gExternalTools[etDiffer].Enabled then
+            MsgWarning(rsMsgNotImplemented)
+          else
+          begin
+            if not Assigned(SecondFileSource) then
+              PrepareToolData(FirstFileSource, FirstFileSourceFiles,
+                              @ShowDifferByGlobList)
+            else
+              PrepareToolData(FirstFileSource, FirstFileSourceFiles,
+                              SecondFileSource, SecondFileSourceFiles,
+                              @ShowDifferByGlobList);
+          end;
+
         finally
           FreeAndNil(ActiveSelectedFiles);
           FreeAndNil(NotActiveSelectedFiles);
         end;
       end;
-
-      if ((FilesToCompare.Count > 0) and (DirsToCompare.Count > 0))
-      or ((FilesToCompare.Count = 1) or (DirsToCompare.Count = 1)) then
-      begin
-         // Either files or directories must be selected and more than one.
-         MsgWarning(rsMsgInvalidSelection);
-      end
-      else if FilesToCompare.Count > 0 then
-      begin
-        if gExternalTools[etDiffer].Enabled then
-          RunExtDiffer(FilesToCompare)
-        else if FilesToCompare.Count = 2 then
-          ShowDiffer(FilesToCompare.Strings[0], FilesToCompare.Strings[1])
-        else
-          MsgWarning(rsMsgTooManyFilesSelected);
-      end
-      else if DirsToCompare.Count > 0 then
-      begin
-        if gExternalTools[etDiffer].Enabled then
-          RunExtDiffer(DirsToCompare)
-        else
-          MsgWarning(rsMsgNotImplemented);
-      end
-      else
-        msgWarning(rsMsgNoFilesSelected);
-
-    finally
-      FreeAndNil(FilesToCompare);
-      FreeAndNil(DirsToCompare);
-    end;
   end;
 end;
 
Index: src/ushowform.pas
===================================================================
--- src/ushowform.pas	(revision 7907)
+++ src/ushowform.pas	(working copy)
@@ -19,27 +19,57 @@
 interface
 
 uses
-  Classes, DCBasicTypes, uFileSource, uFileSourceOperation;
+  Classes, DCBasicTypes, uFileSource, uFileSourceOperation, uFile,
+  uFileSourceCopyOperation;
 
 type
 
+  { TWaitData }
+
+  TWaitData = class
+  public
+    procedure ShowWaitForm; virtual; abstract;
+    procedure Done; virtual; abstract;
+  end;
+
   { TEditorWaitData }
 
-  TEditorWaitData = class
+  TEditorWaitData = class(TWaitData)
   public
-    FileName: String;
+    Files: TFiles;
+    function GetFileList: TStringList;
+  protected
+    FileTimes: array of TFileTime;
     TargetPath: String;
-    FileTime: TFileTime;
     SourceFileSource: IFileSource;
     TargetFileSource: IFileSource;
+    function GetRelativeFileName(const FullPath: string): string;
+    function GetRelativeFileNames: string;
+    function GetFromPath: string;
   public
+    constructor Create(aCopyOutOperation: TFileSourceCopyOperation);
     destructor Destroy; override;
+    procedure ShowWaitForm; override;
+    procedure Done; override;
   protected
     procedure OnCopyInStateChanged(Operation: TFileSourceOperation;
                                    State: TFileSourceOperationState);
   end;
 
-procedure EditDone(WaitData: TEditorWaitData);
+  TToolDataPreparedProc = procedure(const FileList: TStringList; WaitData: TWaitData);
+
+  TPrepareDataResult = (pdrFailed, pdrSynchronous, pdrAsynchronous);
+
+function PrepareData(FileSource: IFileSource; var SelectedFiles: TFiles;
+                     FunctionToCall: TFileSourceOperationStateChangedNotify): TPrepareDataResult;
+
+procedure PrepareToolData(FileSource: IFileSource; var SelectedFiles: TFiles;
+                          FunctionToCall: TToolDataPreparedProc); overload;
+
+procedure PrepareToolData(FileSource1: IFileSource; var SelectedFiles1: TFiles;
+                          FileSource2: IFileSource; var SelectedFiles2: TFiles;
+                          FunctionToCall: TToolDataPreparedProc); overload;
+
 procedure RunExtDiffer(CompareList: TStringList);
 
 procedure ShowEditorByGlob(const sFileName: String);
@@ -46,6 +76,7 @@
 procedure ShowEditorByGlob(WaitData: TEditorWaitData); overload;
 
 procedure ShowDifferByGlob(const LeftName, RightName: String);
+procedure ShowDifferByGlobList(const CompareList: TStringList; WaitData: TWaitData);
 
 procedure ShowViewerByGlob(const sFileName: String);
 procedure ShowViewerByGlobList(const FilesToView: TStringList;
@@ -57,12 +88,24 @@
   SysUtils, Process, DCProcessUtf8, Dialogs, LCLIntf,
   uShellExecute, uGlobs, uOSUtils, fEditor, fViewer, uDCUtils,
   uTempFileSystemFileSource, uLng, fDiffer, uDebug, DCOSUtils, uShowMsg,
-  uFile, uFileSourceCopyOperation, uFileSystemFileSource,
+  DCStrUtils, uFileSourceProperty,
   uFileSourceOperationOptions, uOperationsManager, uFileSourceOperationTypes,
   uMultiArchiveFileSource, fFileExecuteYourSelf;
 
 type
 
+  { TWaitDataDouble }
+
+  TWaitDataDouble = class(TWaitData)
+  private
+    FWaitData1, FWaitData2: TEditorWaitData;
+  public
+    constructor Create(WaitData1: TEditorWaitData; WaitData2: TEditorWaitData);
+    procedure ShowWaitForm; override;
+    procedure Done; override;
+    destructor Destroy; override;
+  end;
+
   { TViewerWaitThread }
 
   TViewerWaitThread = class(TThread)
@@ -76,11 +119,13 @@
     destructor Destroy; override;
   end;
 
-  { TEditorWaitThread }
+  { TExtToolWaitThread }
 
-  TEditorWaitThread = class(TThread)
+  TExtToolWaitThread = class(TThread)
   private
-    FWaitData: TEditorWaitData;
+    FExternalTool: TExternalTool;
+    FFileList: TStringList;
+    FWaitData: TWaitData;
   private
     procedure RunEditDone;
     procedure ShowWaitForm;
@@ -87,7 +132,10 @@
   protected
     procedure Execute; override;
   public
-    constructor Create(WaitData: TEditorWaitData);
+    constructor Create(ExternalTool: TExternalTool;
+                       const FileList: TStringList;
+                       WaitData: TWaitData);
+    destructor Destroy; override;
   end;
 
 procedure RunExtTool(const ExtTool: TExternalToolOptions; sFileName: String);
@@ -147,11 +195,21 @@
 end;
 
 procedure ShowEditorByGlob(WaitData: TEditorWaitData);
+var
+  FileList: TStringList;
 begin
   if gExternalTools[etEditor].Enabled then
-    with TEditorWaitThread.Create(WaitData) do Start
+  begin
+    FileList := TStringList.Create;
+    try
+      FileList.Add(WaitData.Files[0].FullPath);
+      with TExtToolWaitThread.Create(etEditor, FileList, WaitData) do Start;
+    finally
+      FileList.Free
+    end;
+  end
   else begin
-    ShowEditor(WaitData);
+    ShowEditor(WaitData.Files[0].FullPath, WaitData);
   end;
 end;
 
@@ -201,6 +259,19 @@
     ShowDiffer(LeftName, RightName);
 end;
 
+procedure ShowDifferByGlobList(const CompareList: TStringList; WaitData: TWaitData);
+begin
+  if gExternalTools[etDiffer].Enabled then
+  begin
+    if Assigned(WaitData) then
+      with TExtToolWaitThread.Create(etDiffer, CompareList, WaitData) do Start
+    else
+      RunExtDiffer(CompareList);
+  end
+  else
+    ShowDiffer(CompareList[0], CompareList[1], WaitData);
+end;
+
 procedure ShowViewerByGlobList(const FilesToView : TStringList;
                                const aFileSource: IFileSource);
 var
@@ -227,51 +298,147 @@
     ShowViewer(FilesToView, aFileSource);
 end;
 
-procedure EditDone(WaitData: TEditorWaitData);
+procedure TEditorWaitData.Done;
 var
-  Files: TFiles;
+  I: Integer;
   Operation: TFileSourceCopyOperation;
+  DoNotFreeYet: Boolean = False;
 begin
-  with WaitData do
   try
-    // File was modified
-    if mbFileAge(FileName) <> FileTime then
+    for I := Files.Count - 1 downto 0 do
+      if (mbFileAge(Files[I].FullPath) = FileTimes[I]) or
+         not msgYesNo(Format(rsMsgCopyBackward, [GetRelativeFileName(Files[I].FullPath)]) + LineEnding + LineEnding + GetFromPath) then
+        Files.Delete(I);
+
+    // Files were modified
+    if Files.Count > 0 then
     begin
-      if not msgYesNo(Format(rsMsgCopyBackward, [ExtractFileName(FileName)])) then Exit;
       if (fsoCopyIn in TargetFileSource.GetOperationsTypes) and
          (not (TargetFileSource is TMultiArchiveFileSource)) then
       begin
-        Files:= TFiles.Create(SourceFileSource.GetRootDir);
-        Files.Add(TFileSystemFileSource.CreateFileFromFile(FileName));
         Operation:= TargetFileSource.CreateCopyInOperation(SourceFileSource, Files, TargetPath) as TFileSourceCopyOperation;
-        // Copy file back
+        // Copy files back
         if Assigned(Operation) then
         begin
           Operation.AddStateChangedListener([fsosStopped], @OnCopyInStateChanged);
           Operation.FileExistsOption:= fsoofeOverwrite;
           OperationsManager.AddOperation(Operation);
-          WaitData:= nil; // Will be free in operation
+          DoNotFreeYet:= True; // Will be free in operation
         end;
       end
-      else if msgYesNo(rsMsgCouldNotCopyBackward + LineEnding + FileName) then
+      else if msgYesNo(rsMsgCouldNotCopyBackward + LineEnding + GetRelativeFileNames) then
       begin
         (SourceFileSource as ITempFileSystemFileSource).DeleteOnDestroy:= False;
       end;
     end;
   finally
-    WaitData.Free;
+    if not DoNotFreeYet then
+      Free;
   end;
 end;
 
+{ TWaitDataDouble }
+
+constructor TWaitDataDouble.Create(WaitData1: TEditorWaitData; WaitData2: TEditorWaitData);
+begin
+  FWaitData1 := WaitData1;
+  FWaitData2 := WaitData2;
+end;
+
+procedure TWaitDataDouble.ShowWaitForm;
+begin
+  try
+    FWaitData1.ShowWaitForm;
+  finally
+    FWaitData2.ShowWaitForm;
+  end;
+end;
+
+procedure TWaitDataDouble.Done;
+begin
+  try
+    if Assigned(FWaitData1) then
+      FWaitData1.Done;
+  finally
+    FWaitData1 := nil;
+    try
+      if Assigned(FWaitData2) then
+        FWaitData2.Done;
+    finally
+      FWaitData2 := nil;
+      Free;
+    end;
+  end;
+end;
+
+destructor TWaitDataDouble.Destroy;
+begin
+  inherited Destroy;
+  if Assigned(FWaitData1) then
+    FWaitData1.Free;
+  if Assigned(FWaitData2) then
+    FWaitData2.Free;
+end;
+
 { TEditorWaitData }
 
+constructor TEditorWaitData.Create(aCopyOutOperation: TFileSourceCopyOperation);
+var
+  I: Integer;
+  aFileSource: ITempFileSystemFileSource;
+begin
+  aFileSource := aCopyOutOperation.TargetFileSource as ITempFileSystemFileSource;
+  TargetPath := aCopyOutOperation.SourceFiles.Path;
+  Files := aCopyOutOperation.SourceFiles.Clone;
+  ChangeFileListRoot(aFileSource.FileSystemRoot, Files);
+  SetLength(FileTimes, Files.Count);
+  for I := 0 to Files.Count - 1 do
+    FileTimes[I] := mbFileAge(Files[I].FullPath);
+  SourceFileSource := aFileSource;
+  TargetFileSource := aCopyOutOperation.FileSource as IFileSource;
+end;
+
 destructor TEditorWaitData.Destroy;
 begin
   inherited Destroy;
+  Files.Free;
   SourceFileSource:= nil;
   TargetFileSource:= nil;
 end;
 
+function TEditorWaitData.GetRelativeFileName(const FullPath: string): string;
+begin
+  Result := ExtractDirLevel(IncludeTrailingPathDelimiter(Files.Path), FullPath);
+end;
+
+function TEditorWaitData.GetRelativeFileNames: string;
+var
+  I: Integer;
+begin
+  Result := GetRelativeFileName(Files[0].FullPath);
+  for I := 1 to Files.Count - 1 do
+    Result := Result + ', ' + GetRelativeFileName(Files[I].FullPath);
+end;
+
+function TEditorWaitData.GetFromPath: string;
+begin
+  Result := TargetFileSource.CurrentAddress + TargetPath;
+end;
+
+procedure TEditorWaitData.ShowWaitForm;
+begin
+  ShowFileEditExternal(GetRelativeFileNames, GetFromPath, Self);
+end;
+
+function TEditorWaitData.GetFileList: TStringList;
+var
+  I: Integer;
+begin
+  Result := TStringList.Create;
+  for I := 0 to Files.Count - 1 do
+    Result.Add(Files[I].FullPath);
+end;
+
 procedure TEditorWaitData.OnCopyInStateChanged(Operation: TFileSourceOperation;
                                                State: TFileSourceOperationState);
 var
@@ -296,27 +463,29 @@
   end;
 end;
 
-{ TEditorWaitThread }
+{ TExtToolWaitThread }
 
-procedure TEditorWaitThread.RunEditDone;
+procedure TExtToolWaitThread.RunEditDone;
 begin
-  EditDone(FWaitData);
+  FWaitData.Done;
 end;
 
-procedure TEditorWaitThread.ShowWaitForm;
+procedure TExtToolWaitThread.ShowWaitForm;
 begin
-  ShowFileEditExternal(FWaitData);
+  FWaitData.ShowWaitForm;
 end;
 
-procedure TEditorWaitThread.Execute;
+procedure TExtToolWaitThread.Execute;
 var
+  I: Integer;
   StartTime: QWord;
   Process : TProcessUTF8;
   sCmd, sSecureEmptyStr: String;
 begin
+  try
   Process := TProcessUTF8.Create(nil);
-
-  with gExternalTools[etEditor] do
+  try
+  with gExternalTools[FExternalTool] do
   begin
     sCmd := ReplaceEnvVars(Path);
     // TProcess arguments must be enclosed with double quotes and not escaped.
@@ -325,7 +494,8 @@
       sCmd := QuoteStr(sCmd);
       if Parameters <> EmptyStr then
         sCmd := sCmd + ' ' + Parameters;
-      sCmd := sCmd + ' ' + QuoteStr(FWaitData.FileName);
+      for I := 0 to FFileList.Count - 1 do
+        sCmd := sCmd + ' ' + QuoteStr(FFileList[I]);
       sSecureEmptyStr := EmptyStr; // Let's play safe and don't let EmptyStr being passed as "VAR" parameter of "FormatTerminal"
       FormatTerminal(sCmd, sSecureEmptyStr, False);
     end
@@ -334,7 +504,8 @@
       sCmd := '"' + sCmd + '"';
       if Parameters <> EmptyStr then
         sCmd := sCmd + ' ' + Parameters;
-      sCmd := sCmd + ' "' + FWaitData.FileName + '"';
+      for I := 0 to FFileList.Count - 1 do
+        sCmd := sCmd + ' "' + FFileList[I] + '"';
     end;
   end;
 
@@ -342,7 +513,6 @@
   Process.Options := [poWaitOnExit];
   StartTime:= GetTickCount64;
   Process.Execute;
-  Process.Free;
 
   // If an editor closes within gEditWaitTime amount of milliseconds,
   // assume that it's a multiple document editor and show dialog where
@@ -354,17 +524,38 @@
   else begin
     Synchronize(@RunEditDone);
   end;
+
+  finally
+    Process.Free;
+  end;
+  except
+    FWaitData.Free;
+  end;
 end;
 
-constructor TEditorWaitThread.Create(WaitData: TEditorWaitData);
+constructor TExtToolWaitThread.Create(ExternalTool: TExternalTool;
+                                      const FileList: TStringList;
+                                      WaitData: TWaitData);
 begin
   inherited Create(True);
 
   FreeOnTerminate := True;
 
+  FExternalTool := ExternalTool;
+
+  FFileList := TStringList.Create;
+  // Make a copy of list elements.
+  FFileList.Assign(FileList);
+
   FWaitData := WaitData;
 end;
 
+destructor TExtToolWaitThread.Destroy;
+begin
+  FFileList.Free;
+  inherited Destroy;
+end;
+
 { TViewerWaitThread }
 
 constructor TViewerWaitThread.Create(const FilesToView: TStringList; const aFileSource: IFileSource);
@@ -425,4 +616,306 @@
   Process.Free;
 end;
 
+{ PrepareData }
+
+function PrepareData(FileSource: IFileSource; var SelectedFiles: TFiles;
+                     FunctionToCall: TFileSourceOperationStateChangedNotify): TPrepareDataResult;
+var
+  aFile: TFile;
+  I: Integer;
+  TempFiles: TFiles = nil;
+  TempFileSource: ITempFileSystemFileSource = nil;
+  Operation: TFileSourceOperation;
+begin
+  // If files are links to local files
+  if (fspLinksToLocalFiles in FileSource.Properties) then
+    begin
+      for I := 0 to SelectedFiles.Count - 1 do
+        begin
+          aFile := SelectedFiles[I];
+          FileSource.GetLocalName(aFile);
+        end;
+    end
+  // If files not directly accessible copy them to temp file source.
+  else if not (fspDirectAccess in FileSource.Properties) then
+  begin
+    if not (fsoCopyOut in FileSource.GetOperationsTypes) then
+    begin
+      msgWarning(rsMsgErrNotSupported);
+      Exit(pdrFailed);
+    end;
+
+    TempFileSource := TTempFileSystemFileSource.GetFileSource;
+
+    TempFiles := SelectedFiles.Clone;
+    try
+      Operation := FileSource.CreateCopyOutOperation(
+                       TempFileSource,
+                       TempFiles,
+                       TempFileSource.FileSystemRoot);
+    finally
+      TempFiles.Free;
+    end;
+
+    if not Assigned(Operation) then
+    begin
+      msgWarning(rsMsgErrNotSupported);
+      Exit(pdrFailed);
+    end;
+
+    Operation.AddStateChangedListener([fsosStopped], FunctionToCall);
+
+    OperationsManager.AddOperation(Operation);
+
+    Exit(pdrAsynchronous);
+  end;
+  Exit(pdrSynchronous);
+end;
+
+{ TToolDataPreparator }
+
+type
+  TToolDataPreparator = class
+   protected
+    FFunc: TToolDataPreparedProc;
+    FCallOnFail: Boolean;
+    procedure OnCopyOutStateChanged(Operation: TFileSourceOperation;
+                                    State: TFileSourceOperationState);
+   public
+    constructor Create(FunctionToCall: TToolDataPreparedProc; CallOnFail: Boolean = False);
+    procedure Prepare(FileSource: IFileSource; var SelectedFiles: TFiles);
+  end;
+
+constructor TToolDataPreparator.Create(FunctionToCall: TToolDataPreparedProc; CallOnFail: Boolean = False);
+begin
+  FFunc := FunctionToCall;
+  FCallOnFail := CallOnFail;
+end;
+
+procedure TToolDataPreparator.Prepare(FileSource: IFileSource; var SelectedFiles: TFiles);
+var
+  I: Integer;
+  FileList: TStringList;
+begin
+  case PrepareData(FileSource, SelectedFiles, @OnCopyOutStateChanged) of
+  pdrSynchronous:
+    try
+      FileList := TStringList.Create;
+      for I := 0 to SelectedFiles.Count - 1 do
+        FileList.Add(SelectedFiles[i].FullPath);
+      FFunc(FileList, nil);
+    finally
+      Free;
+    end;
+  pdrFailed:
+    try
+      if FCallOnFail then
+        FFunc(nil, nil);
+    finally
+      Free;
+    end;
+  end;
+end;
+
+procedure TToolDataPreparator.OnCopyOutStateChanged(
+  Operation: TFileSourceOperation; State: TFileSourceOperationState);
+var WaitData: TEditorWaitData;
+begin
+  if (State <> fsosStopped) then
+    Exit;
+  try
+    if Operation.Result = fsorFinished then
+    begin
+      WaitData := TEditorWaitData.Create(Operation as TFileSourceCopyOperation);
+      FFunc(WaitData.GetFileList, WaitData);
+    end
+    else
+    begin
+      if FCallOnFail then
+        FFunc(nil, nil);
+    end;
+  finally
+    Free;
+  end;
+end;
+
+{ TToolDataPreparator2 }
+
+type
+  TToolDataPreparator2 = class
+   protected
+    FFunc: TToolDataPreparedProc;
+    FCallOnFail: Boolean;
+    FFailed: Boolean;
+    FFileList1: TStringList;
+    FFileList2: TStringList;
+    FPrepared1: Boolean;
+    FPrepared2: Boolean;
+    FWaitData1: TEditorWaitData;
+    FWaitData2: TEditorWaitData;
+    procedure OnCopyOutStateChanged1(Operation: TFileSourceOperation;
+                                     State: TFileSourceOperationState);
+    procedure OnCopyOutStateChanged2(Operation: TFileSourceOperation;
+                                     State: TFileSourceOperationState);
+    procedure TryFinish;
+   public
+    constructor Create(FunctionToCall: TToolDataPreparedProc; CallOnFail: Boolean = False);
+    procedure Prepare(FileSource1: IFileSource; var SelectedFiles1: TFiles;
+                      FileSource2: IFileSource; var SelectedFiles2: TFiles);
+    destructor Destroy; override;
+  end;
+
+constructor TToolDataPreparator2.Create(FunctionToCall: TToolDataPreparedProc; CallOnFail: Boolean = False);
+begin
+  FFunc := FunctionToCall;
+  FCallOnFail := CallOnFail;
+end;
+
+procedure TToolDataPreparator2.Prepare(FileSource1: IFileSource; var SelectedFiles1: TFiles;
+                                       FileSource2: IFileSource; var SelectedFiles2: TFiles);
+var
+  I: Integer;
+begin
+  case PrepareData(FileSource1, SelectedFiles1, @OnCopyOutStateChanged1) of
+  pdrSynchronous:
+    begin
+      FFileList1 := TStringList.Create;
+      for I := 0 to SelectedFiles1.Count - 1 do
+        FFileList1.Add(SelectedFiles1[I].FullPath);
+      FPrepared1 := True;
+    end;
+  pdrFailed:
+    begin
+      try
+        if FCallOnFail then
+          FFunc(nil, nil);
+      finally
+        Free;
+      end;
+      Exit;
+    end;
+  end;
+
+  case PrepareData(FileSource2, SelectedFiles2, @OnCopyOutStateChanged2) of
+  pdrSynchronous:
+    begin
+      FFileList2 := TStringList.Create;
+      for I := 0 to SelectedFiles2.Count - 1 do
+        FFileList2.Add(SelectedFiles2[I].FullPath);
+      FPrepared2 := True;
+    end;
+  pdrFailed:
+    begin
+      FPrepared2 := True;
+      FFailed := True;
+    end;
+  end;
+
+  TryFinish;
+end;
+
+procedure TToolDataPreparator2.OnCopyOutStateChanged1(
+  Operation: TFileSourceOperation; State: TFileSourceOperationState);
+begin
+  if (State <> fsosStopped) then
+    Exit;
+  FPrepared1 := True;
+  if not FFailed then
+  begin
+    if Operation.Result = fsorFinished then
+    begin
+      FWaitData1 := TEditorWaitData.Create(Operation as TFileSourceCopyOperation);
+      FFileList1 := FWaitData1.GetFileList;
+    end
+    else
+    begin
+      FFailed := True;
+//      if not FPrepared2 and Assigned(FOperation2) then
+//        FOperation2.Stop();
+    end;
+  end;
+  TryFinish;
+end;
+
+procedure TToolDataPreparator2.OnCopyOutStateChanged2(
+  Operation: TFileSourceOperation; State: TFileSourceOperationState);
+begin
+  if (State <> fsosStopped) then
+    Exit;
+  FPrepared2 := True;
+  if not FFailed then
+  begin
+    if Operation.Result = fsorFinished then
+    begin
+      FWaitData2 := TEditorWaitData.Create(Operation as TFileSourceCopyOperation);
+      FFileList2 := FWaitData2.GetFileList;
+    end
+    else
+    begin
+      FFailed := True;
+//      if not FPrepared1 and Assigned(FOperation1) then
+//        FOperation1.Stop();
+    end;
+  end;
+  TryFinish;
+end;
+
+procedure TToolDataPreparator2.TryFinish;
+var
+  s: string;
+  WaitData: TWaitDataDouble;
+begin
+  if FPrepared1 and FPrepared2 then
+  try
+    if FFailed then
+    begin
+      if FCallOnFail then
+        FFunc(nil, nil);
+      Exit;
+    end;
+    if Assigned(FFileList2) then
+      for s in FFileList2 do
+        FFileList1.Append(s);
+    if Assigned(FWaitData1) or Assigned(FWaitData2) then
+    begin
+      WaitData := TWaitDataDouble.Create(FWaitData1, FWaitData2);
+      FWaitData1 := nil;
+      FWaitData2 := nil;
+      FFunc(FFileList1, WaitData);
+    end
+    else
+      FFunc(FFileList1, nil);
+  finally
+    Free;
+  end;
+end;
+
+destructor TToolDataPreparator2.Destroy;
+begin
+  inherited Destroy;
+  if Assigned(FFileList1) then
+     FFileList1.Free;
+  if Assigned(FFileList2) then
+     FFileList2.Free;
+  if Assigned(FWaitData1) then
+     FWaitData1.Free;
+  if Assigned(FWaitData2) then
+     FWaitData2.Free;
+end;
+
+procedure PrepareToolData(FileSource: IFileSource; var SelectedFiles: TFiles;
+                          FunctionToCall: TToolDataPreparedProc);
+begin
+  with TToolDataPreparator.Create(FunctionToCall) do
+    Prepare(FileSource, SelectedFiles);
+end;
+
+procedure PrepareToolData(FileSource1: IFileSource; var SelectedFiles1: TFiles;
+                          FileSource2: IFileSource; var SelectedFiles2: TFiles;
+                          FunctionToCall: TToolDataPreparedProc);
+begin
+  with TToolDataPreparator2.Create(FunctionToCall) do
+    Prepare(FileSource1, SelectedFiles1, FileSource2, SelectedFiles2);
+end;
+
 end.
