فهرست منبع

Bug 1893: Basic implementation of local tabs

Source commit: fc01f97bd46c7581fcd9c9ebd5f45c345e50a0d6
Martin Prikryl 4 سال پیش
والد
کامیت
7c0121b5d9

+ 10 - 0
source/core/RemoteFiles.cpp

@@ -144,6 +144,16 @@ UnicodeString __fastcall ExtractFileName(const UnicodeString & Path, bool Unix)
   }
 }
 //---------------------------------------------------------------------------
+UnicodeString ExtractShortName(const UnicodeString & Path, bool Unix)
+{
+  UnicodeString Result = ExtractFileName(Path, Unix);
+  if (Result.IsEmpty())
+  {
+    Result = Path;
+  }
+  return Result;
+}
+//---------------------------------------------------------------------------
 bool __fastcall ExtractCommonPath(TStrings * Files, UnicodeString & Path)
 {
   DebugAssert(Files->Count > 0);

+ 1 - 0
source/core/RemoteFiles.h

@@ -547,6 +547,7 @@ UnicodeString __fastcall UnixCombinePaths(const UnicodeString & Path1, const Uni
 UnicodeString __fastcall UnixExtractFileDir(const UnicodeString Path);
 UnicodeString __fastcall UnixExtractFilePath(const UnicodeString Path);
 UnicodeString __fastcall UnixExtractFileName(const UnicodeString Path);
+UnicodeString ExtractShortName(const UnicodeString & Path, bool Unix);
 UnicodeString __fastcall UnixExtractFileExt(const UnicodeString Path);
 Boolean __fastcall UnixSamePath(const UnicodeString Path1, const UnicodeString Path2);
 bool __fastcall UnixIsChildPath(UnicodeString Parent, UnicodeString Child);

+ 32 - 4
source/core/SessionData.cpp

@@ -200,6 +200,7 @@ void __fastcall TSessionData::DefaultSettings()
 
   // FS common
   LocalDirectory = L"";
+  OtherLocalDirectory = L"";
   RemoteDirectory = L"";
   SynchronizeBrowsing = false;
   UpdateDirectories = true;
@@ -318,6 +319,7 @@ void __fastcall TSessionData::NonPersistant()
   PROPERTY(FSProtocol); \
   PROPERTY(Ftps); \
   PROPERTY(LocalDirectory); \
+  PROPERTY(OtherLocalDirectory); \
   PROPERTY(RemoteDirectory); \
   PROPERTY(Color); \
   PROPERTY(SynchronizeBrowsing); \
@@ -516,6 +518,7 @@ void __fastcall TSessionData::CopyDirectoriesStateData(TSessionData * SourceData
 {
   RemoteDirectory = SourceData->RemoteDirectory;
   LocalDirectory = SourceData->LocalDirectory;
+  OtherLocalDirectory = SourceData->OtherLocalDirectory;
   SynchronizeBrowsing = SourceData->SynchronizeBrowsing;
 }
 //---------------------------------------------------------------------
@@ -524,6 +527,7 @@ bool __fastcall TSessionData::HasStateData()
   return
     !RemoteDirectory.IsEmpty() ||
     !LocalDirectory.IsEmpty() ||
+    !OtherLocalDirectory.IsEmpty() ||
     (Color != 0);
 }
 //---------------------------------------------------------------------
@@ -689,6 +693,7 @@ void __fastcall TSessionData::DoLoad(THierarchicalStorage * Storage, bool PuttyI
 
   FSProtocol = (TFSProtocol)Storage->ReadInteger(L"FSProtocol", FSProtocol);
   LocalDirectory = Storage->ReadString(L"LocalDirectory", LocalDirectory);
+  OtherLocalDirectory = Storage->ReadString(L"OtherLocalDirectory", OtherLocalDirectory);
   RemoteDirectory = Storage->ReadString(L"RemoteDirectory", RemoteDirectory);
   SynchronizeBrowsing = Storage->ReadBool(L"SynchronizeBrowsing", SynchronizeBrowsing);
   UpdateDirectories = Storage->ReadBool(L"UpdateDirectories", UpdateDirectories);
@@ -1056,6 +1061,7 @@ void __fastcall TSessionData::DoSave(THierarchicalStorage * Storage,
     WRITE_DATA(String, PublicKeyFile);
     WRITE_DATA(Integer, FSProtocol);
     WRITE_DATA(String, LocalDirectory);
+    WRITE_DATA(String, OtherLocalDirectory);
     WRITE_DATA(String, RemoteDirectory);
     WRITE_DATA(Bool, SynchronizeBrowsing);
     WRITE_DATA(Bool, UpdateDirectories);
@@ -2379,6 +2385,16 @@ bool __fastcall TSessionData::GetCanLogin()
 {
   return !FHostName.IsEmpty();
 }
+//---------------------------------------------------------------------
+bool __fastcall TSessionData::GetIsLocalBrowser()
+{
+  return !LocalDirectory.IsEmpty() && !OtherLocalDirectory.IsEmpty();
+}
+//---------------------------------------------------------------------
+bool __fastcall TSessionData::GetCanOpen()
+{
+  return CanLogin || IsLocalBrowser;
+}
 //---------------------------------------------------------------------------
 UnicodeString __fastcall TSessionData::GetSessionKey()
 {
@@ -2977,7 +2993,14 @@ void __fastcall TSessionData::SetRekeyTime(unsigned int value)
 //---------------------------------------------------------------------
 UnicodeString __fastcall TSessionData::GetDefaultSessionName()
 {
-  if (!HostName.IsEmpty() && !UserName.IsEmpty())
+  if (IsLocalBrowser)
+  {
+    // See also TScpCommanderForm::GetLocalBrowserSessionTitle
+    UnicodeString Path1 = ExtractShortName(LocalDirectory, false);
+    UnicodeString Path2 = ExtractShortName(OtherLocalDirectory, false);
+    return FORMAT(L"%s - %s", (Path1, Path2));
+  }
+  else if (!HostName.IsEmpty() && !UserName.IsEmpty())
   {
     // If we ever choose to include port number,
     // we have to escape IPv6 literals in HostName
@@ -3657,6 +3680,11 @@ void __fastcall TSessionData::SetLocalDirectory(UnicodeString value)
   SET_SESSION_PROPERTY(LocalDirectory);
 }
 //---------------------------------------------------------------------
+void __fastcall TSessionData::SetOtherLocalDirectory(const UnicodeString & value)
+{
+  SET_SESSION_PROPERTY(OtherLocalDirectory);
+}
+//---------------------------------------------------------------------
 UnicodeString __fastcall TSessionData::GetLocalDirectoryExpanded()
 {
   return ExpandFileName(::ExpandEnvironmentVariables(LocalDirectory));
@@ -5016,7 +5044,7 @@ TSessionData * __fastcall TStoredSessionList::CheckIsInFolderOrWorkspaceAndResol
   {
     Data = ResolveWorkspaceData(Data);
 
-    if ((Data != NULL) && Data->CanLogin &&
+    if ((Data != NULL) && Data->CanOpen &&
         DebugAlwaysTrue(Data->Link.IsEmpty()))
     {
       return Data;
@@ -5207,10 +5235,10 @@ TSessionData * __fastcall TStoredSessionList::SaveWorkspaceData(TSessionData * D
   return Result.release();
 }
 //---------------------------------------------------------------------
-bool __fastcall TStoredSessionList::CanLogin(TSessionData * Data)
+bool __fastcall TStoredSessionList::CanOpen(TSessionData * Data)
 {
   Data = ResolveWorkspaceData(Data);
-  return (Data != NULL) && Data->CanLogin;
+  return (Data != NULL) && Data->CanOpen;
 }
 //---------------------------------------------------------------------
 UnicodeString GetExpandedLogFileName(UnicodeString LogFileName, TDateTime Started, TSessionData * SessionData)

+ 8 - 1
source/core/SessionData.h

@@ -139,6 +139,7 @@ private:
   bool FModified;
   UnicodeString FLocalDirectory;
   UnicodeString FRemoteDirectory;
+  UnicodeString FOtherLocalDirectory;
   bool FLockInHome;
   bool FSpecial;
   bool FSynchronizeBrowsing;
@@ -281,6 +282,8 @@ private:
 
   void __fastcall SetPuttyProtocol(UnicodeString value);
   bool __fastcall GetCanLogin();
+  bool __fastcall GetCanOpen();
+  bool __fastcall GetIsLocalBrowser();
   void __fastcall SetPingIntervalDT(TDateTime value);
   TDateTime __fastcall GetPingIntervalDT();
   TDateTime __fastcall GetFtpPingIntervalDT();
@@ -293,6 +296,7 @@ private:
   void __fastcall SetFSProtocol(TFSProtocol value);
   UnicodeString __fastcall GetFSProtocolStr();
   void __fastcall SetLocalDirectory(UnicodeString value);
+  void __fastcall SetOtherLocalDirectory(const UnicodeString & value);
   UnicodeString __fastcall GetLocalDirectoryExpanded();
   void __fastcall SetRemoteDirectory(UnicodeString value);
   void __fastcall SetSynchronizeBrowsing(bool value);
@@ -562,6 +566,8 @@ public:
   __property UnicodeString FSProtocolStr  = { read=GetFSProtocolStr };
   __property bool Modified  = { read=FModified, write=FModified };
   __property bool CanLogin  = { read=GetCanLogin };
+  __property bool CanOpen = { read=GetCanOpen };
+  __property bool IsLocalBrowser = { read=GetIsLocalBrowser };
   __property bool ClearAliases = { read = FClearAliases, write = SetClearAliases };
   __property TDateTime PingIntervalDT = { read = GetPingIntervalDT, write = SetPingIntervalDT };
   __property TDateTime TimeDifference = { read = FTimeDifference, write = SetTimeDifference };
@@ -571,6 +577,7 @@ public:
   __property UnicodeString DefaultSessionName  = { read=GetDefaultSessionName };
   __property UnicodeString LocalDirectory  = { read=FLocalDirectory, write=SetLocalDirectory };
   __property UnicodeString LocalDirectoryExpanded = { read = GetLocalDirectoryExpanded };
+  __property UnicodeString OtherLocalDirectory = { read=FOtherLocalDirectory, write=SetOtherLocalDirectory };
   __property UnicodeString RemoteDirectory  = { read=FRemoteDirectory, write=SetRemoteDirectory };
   __property bool SynchronizeBrowsing = { read=FSynchronizeBrowsing, write=SetSynchronizeBrowsing };
   __property bool UpdateDirectories = { read=FUpdateDirectories, write=SetUpdateDirectories };
@@ -721,7 +728,7 @@ public:
   TSessionData * __fastcall ParseUrl(UnicodeString Url, TOptions * Options, bool & DefaultsOnly,
     UnicodeString * FileName = NULL, bool * ProtocolDefined = NULL, UnicodeString * MaskedUrl = NULL, int Flags = 0);
   bool __fastcall IsUrl(UnicodeString Url);
-  bool __fastcall CanLogin(TSessionData * Data);
+  bool __fastcall CanOpen(TSessionData * Data);
   void __fastcall GetFolderOrWorkspace(const UnicodeString & Name, TList * List);
   TStrings * __fastcall GetFolderOrWorkspaceList(const UnicodeString & Name);
   TStrings * __fastcall GetWorkspaces();

+ 116 - 74
source/forms/CustomScpExplorer.cpp

@@ -185,6 +185,7 @@ __fastcall TCustomScpExplorerForm::TCustomScpExplorerForm(TComponent* Owner):
   FDragDropOperation = false;
   memset(&FHistoryMenu, 0, sizeof(FHistoryMenu));
   FAllowTransferPresetAutoSelect = true;
+  FSessionChanging = false;
   FCopyParamDefault = L"";
   FSynchronizeController = NULL;
   FPendingQueueActionItem = NULL;
@@ -544,7 +545,7 @@ void __fastcall TCustomScpExplorerForm::SetManagedSession(TManagedTerminal * val
 {
   if (FManagedSession != value)
   {
-    TerminalChanging();
+    SessionChanging();
     DoSetManagedSession(value, false);
   }
 }
@@ -553,21 +554,20 @@ void __fastcall TCustomScpExplorerForm::DoSetManagedSession(TManagedTerminal * v
 {
   DebugAssert(!Replace || ((value != NULL) && !value->LocalBrowser));
   FManagedSession = value;
-  bool PrevAllowTransferPresetAutoSelect = FAllowTransferPresetAutoSelect;
-  FAllowTransferPresetAutoSelect = false;
-  try
-  {
-    TerminalChanged(Replace);
-  }
-  __finally
   {
-    FAllowTransferPresetAutoSelect = PrevAllowTransferPresetAutoSelect;
+    TValueRestorer<bool> AllowTransferPresetAutoSelectRestorer(FAllowTransferPresetAutoSelect);
+    FAllowTransferPresetAutoSelect = false;
+    TAutoFlag SessionChangingFlag(FSessionChanging);
+
+    SessionChanged(Replace);
   }
 
   if (Terminal != NULL)
   {
     TransferPresetAutoSelect();
   }
+  // Update app and tab titles, prevented above by FSessionChanging.
+  UpdateControls();
 }
 //---------------------------------------------------------------------------
 void __fastcall TCustomScpExplorerForm::ReplaceTerminal(TManagedTerminal * value)
@@ -575,21 +575,24 @@ void __fastcall TCustomScpExplorerForm::ReplaceTerminal(TManagedTerminal * value
   DoSetManagedSession(value, true);
 }
 //---------------------------------------------------------------------------
-void __fastcall TCustomScpExplorerForm::TerminalChanging()
+void __fastcall TCustomScpExplorerForm::SessionChanging()
 {
   if (ManagedSession != NULL)
   {
-    UpdateTerminal(ManagedSession);
+    UpdateSession(ManagedSession);
   }
 }
 //---------------------------------------------------------------------------
-void __fastcall TCustomScpExplorerForm::TerminalChanged(bool Replaced)
+void __fastcall TCustomScpExplorerForm::SessionChanged(bool Replaced)
 {
   if (Terminal != NULL)
   {
     UpdateSessionColor((TColor)Terminal->StateData->Color);
   }
-  SessionListChanged();
+  if (!TTerminalManager::Instance()->Updating)
+  {
+    SessionListChanged();
+  }
 
   if (Terminal != NULL)
   {
@@ -609,14 +612,7 @@ void __fastcall TCustomScpExplorerForm::TerminalChanged(bool Replaced)
 
     if (WinConfiguration->PreservePanelState)
     {
-      if (Terminal->RemoteExplorerState != NULL)
-      {
-        DirView(osRemote)->RestoreState(Terminal->RemoteExplorerState);
-      }
-      else
-      {
-        DirView(osRemote)->ClearState();
-      }
+      DirView(osRemote)->RestoreState(Terminal->RemoteExplorerState);
     }
   }
   else
@@ -4078,7 +4074,7 @@ bool __fastcall TCustomScpExplorerForm::RemoteTransferDialog(TManagedTerminal *&
   DebugAssert(Terminal != NULL);
   DebugAssert(!IsLocalBrowserMode());
   // update Terminal->StateData->RemoteDirectory
-  UpdateTerminal(Terminal);
+  UpdateSession(Terminal);
 
   if (Session == NULL)
   {
@@ -6111,6 +6107,7 @@ void __fastcall TCustomScpExplorerForm::ExploreLocalDirectory(TOperationSide)
 //---------------------------------------------------------------------------
 TSessionData * __fastcall TCustomScpExplorerForm::CloneCurrentSessionData()
 {
+  DebugAssert(!IsLocalBrowserMode()); // untested
   std::unique_ptr<TSessionData> SessionData(new TSessionData(L""));
   SessionData->Assign(Terminal->SessionData);
   UpdateSessionData(SessionData.get());
@@ -6145,10 +6142,10 @@ TObjectList * __fastcall TCustomScpExplorerForm::DoCollectWorkspace()
   TTerminalManager * Manager = TTerminalManager::Instance();
   std::unique_ptr<TObjectList> DataList(new TObjectList());
 
-  if (DebugAlwaysTrue(Terminal != NULL))
+  if (DebugAlwaysTrue(ManagedSession != NULL))
   {
-    // Update (Managed)Terminal->StateData
-    UpdateTerminal(Terminal);
+    // Update ManagedSession->StateData
+    UpdateSession(ManagedSession);
   }
   Manager->SaveWorkspace(DataList.get());
 
@@ -6270,38 +6267,43 @@ bool __fastcall TCustomScpExplorerForm::SaveWorkspace(bool EnableAutoSave)
   return Result;
 }
 //---------------------------------------------------------------------------
-void __fastcall TCustomScpExplorerForm::UpdateTerminal(TManagedTerminal * Terminal)
+void __fastcall TCustomScpExplorerForm::UpdateSession(TManagedTerminal * Session)
 {
-  DebugAssert(!IsLocalBrowserMode());
-  SAFE_DESTROY(Terminal->RemoteExplorerState);
-
-  if (WinConfiguration->PreservePanelState)
+  DebugAssert(IsLocalBrowserMode() == Session->LocalBrowser);
+  DebugAssert(Session == ManagedSession);
+  if (!IsLocalBrowserMode())
   {
-    Terminal->RemoteExplorerState = RemoteDirView->SaveState();
-  }
+    SAFE_DESTROY(Session->RemoteExplorerState);
 
-  UpdateSessionData(Terminal->StateData);
+    if (WinConfiguration->PreservePanelState)
+    {
+      Session->RemoteExplorerState = RemoteDirView->SaveState();
+    }
+  }
+  UpdateSessionData(Session->StateData);
 }
 //---------------------------------------------------------------------------
 void __fastcall TCustomScpExplorerForm::UpdateSessionData(TSessionData * Data)
 {
   // Keep in sync with TSessionData::CopyStateData
 
-  DebugAssert(!IsLocalBrowserMode());
   DebugAssert(Data != NULL);
 
   // This is inconsistent with how color (for example) is handled.
-  Data->Name = Terminal->SessionData->Name;
+  Data->Name = ManagedSession->SessionData->Name;
 
-  // cannot use RemoteDirView->Path, because it is empty if connection
-  // was already closed
-  // also only peek, we may not be connected at all atm
-  // so make sure we do not try retrieving current directory from the server
-  // (particularly with FTP)
-  UnicodeString ACurrentDirectory = Terminal->PeekCurrentDirectory();
-  if (!ACurrentDirectory.IsEmpty())
+  if (!IsLocalBrowserMode())
   {
-    Data->RemoteDirectory = ACurrentDirectory;
+    // cannot use RemoteDirView->Path, because it is empty if connection
+    // was already closed
+    // also only peek, we may not be connected at all atm
+    // so make sure we do not try retrieving current directory from the server
+    // (particularly with FTP)
+    UnicodeString ACurrentDirectory = Terminal->PeekCurrentDirectory();
+    if (!ACurrentDirectory.IsEmpty())
+    {
+      Data->RemoteDirectory = ACurrentDirectory;
+    }
   }
   Data->Color = SessionColor;
 }
@@ -6726,6 +6728,11 @@ void __fastcall TCustomScpExplorerForm::FileTerminalRemoved(const UnicodeString
   }
 }
 //---------------------------------------------------------------------------
+TManagedTerminal * TCustomScpExplorerForm::GetReplacementForLastSession()
+{
+  return NULL;
+}
+//---------------------------------------------------------------------------
 void __fastcall TCustomScpExplorerForm::LastTerminalClosed()
 {
   UpdateControls();
@@ -6746,7 +6753,7 @@ void __fastcall TCustomScpExplorerForm::NeedSession(bool Startup)
       if (ShowLogin)
       {
         bool ReloadSessions = !Startup;
-        TTerminalManager::Instance()->NewSession(L"", ReloadSessions, this);
+        TTerminalManager::Instance()->NewSession(L"", ReloadSessions, this, true);
       }
     }
     __finally
@@ -6755,6 +6762,7 @@ void __fastcall TCustomScpExplorerForm::NeedSession(bool Startup)
       // (so there was no chance for the user to open any session yet)
       if ((ShowLogin || !Startup) &&
           !WinConfiguration->KeepOpenWhenNoSession &&
+          // this rules out the implicit Commander's local-local browser
           ((Terminal == NULL) || (!Terminal->Active && !Terminal->Permanent)))
       {
         TerminateApplication();
@@ -6771,8 +6779,10 @@ void __fastcall TCustomScpExplorerForm::NeedSession(bool Startup)
 //---------------------------------------------------------------------------
 void __fastcall TCustomScpExplorerForm::SessionListChanged()
 {
-  TStrings * SessionList = TTerminalManager::Instance()->SessionList;
-  int ActiveSessionIndex = TTerminalManager::Instance()->ActiveSessionIndex;
+  TTerminalManager * Manager = TTerminalManager::Instance();
+  DebugAssert(!Manager->Updating);
+  TStrings * SessionList = Manager->SessionList;
+  int ActiveSessionIndex = Manager->ActiveSessionIndex;
 
   Configuration->Usage->SetMax(L"MaxOpenedSessions", SessionList->Count);
 
@@ -6813,11 +6823,10 @@ void __fastcall TCustomScpExplorerForm::SessionListChanged()
         TabSheet->ImageIndex = FNewSessionTabImageIndex;
         TabSheet->Tag = 0; // not really needed
         TabSheet->Shadowed = false;
+        TabSheet->ShowCloseButton = false;
         // We know that we are at the last page, otherwise we could not call this (it assumes that new session tab is the last one)
         UpdateNewSessionTab();
       }
-
-      TabSheet->ShowCloseButton = IsSessionTab;
     }
   }
   __finally
@@ -6845,28 +6854,53 @@ TManagedTerminal * __fastcall TCustomScpExplorerForm::GetSessionTabSession(TTabS
   return reinterpret_cast<TManagedTerminal *>(TabSheet->Tag);
 }
 //---------------------------------------------------------------------------
+UnicodeString TCustomScpExplorerForm::GetLocalBrowserSessionTitle(TManagedTerminal *)
+{
+  DebugFail();
+  return UnicodeString();
+}
+//---------------------------------------------------------------------------
 void __fastcall TCustomScpExplorerForm::UpdateSessionTab(TTabSheet * TabSheet)
 {
   if (DebugAlwaysTrue(TabSheet != NULL))
   {
-    TManagedTerminal * ATerminal = GetSessionTabSession(TabSheet);
-    if (DebugAlwaysTrue(ATerminal != NULL))
+    TManagedTerminal * ASession = GetSessionTabSession(TabSheet);
+    if (DebugAlwaysTrue(ASession != NULL))
     {
-      TColor Color = (ATerminal == Terminal) ? FSessionColor : ATerminal->StateData->Color;
-      TabSheet->ImageIndex = AddSessionColor(Color);
+      TColor Color = (ASession == Terminal) ? FSessionColor : ASession->StateData->Color;
+      if (ASession->LocalBrowser)
+      {
+        TabSheet->ImageIndex = FLocalBrowserTabImageIndex;
+      }
+      else
+      {
+        TabSheet->ImageIndex = AddSessionColor(Color);
+      }
 
-      UnicodeString TabCaption = TTerminalManager::Instance()->GetTerminalTitle(ATerminal, true);
-      TabSheet->Caption = SessionsPageControl->FormatCaptionWithCloseButton(TabCaption);
+      TTerminalManager * Manager = TTerminalManager::Instance();
+      UnicodeString TabCaption = Manager->GetSessionTitle(ASession, true);
 
       TThemeTabSheet * ThemeTabSheet = dynamic_cast<TThemeTabSheet *>(TabSheet);
       if (DebugAlwaysTrue(ThemeTabSheet != NULL))
       {
-        ThemeTabSheet->Shadowed = !ATerminal->Active;
+        ThemeTabSheet->Shadowed = !ASession->Active && !ASession->LocalBrowser;
+        ThemeTabSheet->ShowCloseButton = CanCloseSession(ASession);
+        if (ThemeTabSheet->ShowCloseButton)
+        {
+          TabCaption = SessionsPageControl->FormatCaptionWithCloseButton(TabCaption);
+        }
       }
+
+      TabSheet->Caption = TabCaption;
     }
   }
 }
 //---------------------------------------------------------------------------
+bool TCustomScpExplorerForm::CanCloseSession(TManagedTerminal * Session)
+{
+  return !Session->LocalBrowser || (TTerminalManager::Instance()->Count > 1);
+}
+//---------------------------------------------------------------------------
 bool __fastcall TCustomScpExplorerForm::SessionTabSwitched()
 {
   DebugAssert(SessionsPageControl->ActivePage != NULL);
@@ -7097,6 +7131,7 @@ void __fastcall TCustomScpExplorerForm::SysResizing(unsigned int /*Cmd*/)
 //---------------------------------------------------------------------------
 void __fastcall TCustomScpExplorerForm::DoShow()
 {
+  // Called from TCustomScpExplorerForm::CMShowingChanged
   // only now are the controls resized finally, so the size constraints
   // will not conflict with possibly very small window size
   RestoreFormParams();
@@ -7136,9 +7171,10 @@ void __fastcall TCustomScpExplorerForm::DoShow()
     Configuration->Usage->Inc(L"OpenedWorkspacesAuto");
   }
 
-  if (Terminal == NULL)
+  // Before main window displays
+  if (ManagedSession == NULL)
   {
-    StartingDisconnected();
+    StartingWithoutSession();
   }
 
   UpdatePixelsPerInchMainWindowCounter();
@@ -7151,7 +7187,7 @@ void __fastcall TCustomScpExplorerForm::UpdatePixelsPerInchMainWindowCounter()
   Configuration->Usage->Set(L"PixelsPerInchMainWindow", PixelsPerInch);
 }
 //---------------------------------------------------------------------------
-void __fastcall TCustomScpExplorerForm::StartingDisconnected()
+void __fastcall TCustomScpExplorerForm::StartingWithoutSession()
 {
   SessionListChanged();
   InitStatusBar();
@@ -7189,8 +7225,9 @@ void __fastcall TCustomScpExplorerForm::PopupTrayBalloon(TTerminal * Terminal,
       Title = LoadResourceString(Captions[Type]);
       if (Terminal != NULL)
       {
+        TManagedTerminal * Session = DebugNotNull(dynamic_cast<TManagedTerminal *>(Terminal));
         Title = FORMAT(L"%s - %s",
-          (TTerminalManager::Instance()->GetTerminalTitle(Terminal, true), Title));
+          (TTerminalManager::Instance()->GetSessionTitle(Session, true), Title));
       }
     }
 
@@ -7281,10 +7318,7 @@ void __fastcall TCustomScpExplorerForm::ShowExtendedException(
 //---------------------------------------------------------------------------
 void __fastcall TCustomScpExplorerForm::SessionReady()
 {
-  if (Terminal->Active)
-  {
-    InitStatusBar();
-  }
+  InitStatusBar();
   // cannot rely on active page being page for active terminal,
   // as it can happen that active page is the "new session" page
   // (e.g. when reconnecting active terminal, while login dialog
@@ -7324,8 +7358,9 @@ void __fastcall TCustomScpExplorerForm::Notify(TTerminal * Terminal,
     UnicodeString NoteMessage(UnformatMessage(Message));
     if (Terminal != NULL)
     {
+      TManagedTerminal * Session = DebugNotNull(dynamic_cast<TManagedTerminal *>(Terminal));
       NoteMessage = FORMAT(L"%s: %s",
-        (TTerminalManager::Instance()->GetTerminalTitle(Terminal, true), NoteMessage));
+        (TTerminalManager::Instance()->GetSessionTitle(Session, true), NoteMessage));
     }
 
     if (WinConfiguration->BalloonNotifications)
@@ -8697,7 +8732,7 @@ UnicodeString __fastcall TCustomScpExplorerForm::PathForCaption()
     switch (WinConfiguration->PathInCaption)
     {
       case picShort:
-        Result = TTerminalManager::Instance()->GetTerminalShortPath(Terminal);
+        Result = ExtractShortName(Terminal->CurrentDirectory, true);
         break;
 
       case picFull:
@@ -8723,16 +8758,19 @@ TColor __fastcall TCustomScpExplorerForm::DisabledPanelColor()
 //---------------------------------------------------------------------------
 void __fastcall TCustomScpExplorerForm::UpdateControls()
 {
-  TTerminalManager::Instance()->UpdateAppTitle();
+  if (!FSessionChanging)
+  {
+    TTerminalManager::Instance()->UpdateAppTitle();
+  }
   // WORAKRDOUND: Disabling list view when it is not showing yet does not set its
   // background to gray on Windows 7 (works on Windows 10).
   // See also EnableControl
   if (Showing)
   {
-    if (Terminal != NULL)
+    if (ManagedSession != NULL)
     {
       // Update path when it changes
-      if ((SessionsPageControl->ActivePage != NULL) && (GetSessionTabSession(SessionsPageControl->ActivePage) == Terminal))
+      if ((SessionsPageControl->ActivePage != NULL) && (GetSessionTabSession(SessionsPageControl->ActivePage) == ManagedSession))
       {
         UpdateSessionTab(SessionsPageControl->ActivePage);
       }
@@ -9260,6 +9298,9 @@ void __fastcall TCustomScpExplorerForm::WMClose(TMessage & Message)
 //---------------------------------------------------------------------------
 void __fastcall TCustomScpExplorerForm::CMShowingChanged(TMessage & Message)
 {
+  // Ignoring implicit local-local browser of Commander, opened from DoShow
+  bool NoSession = (ManagedSession == NULL);
+  // This calls TCustomScpExplorerForm::DoShow
   TForm::Dispatch(&Message);
 
   // Now the window is visible (TForm::Dispatch is what usually takes long, like when loading a "local" network directory)
@@ -9269,7 +9310,7 @@ void __fastcall TCustomScpExplorerForm::CMShowingChanged(TMessage & Message)
     InterfaceStarted();
   }
 
-  if (Showing && (Terminal == NULL))
+  if (Showing && NoSession)
   {
     // When we are starting minimized (i.e. from an installer),
     // postpone showing Login dialog until we get restored.
@@ -10022,13 +10063,13 @@ void __fastcall TCustomScpExplorerForm::SessionsDDDragOver(
   }
   else
   {
-    TTerminal * TargetTerminal = GetSessionTabSession(SessionsPageControl->Pages[Index]);
-    // do not allow dropping on the "+" tab
-    if (TargetTerminal == NULL)
+    TManagedTerminal * TargetSession = GetSessionTabSession(SessionsPageControl->Pages[Index]);
+    // do not allow dropping on the "+" tab or on local-local tabs
+    if ((TargetSession == NULL) || TargetSession->LocalBrowser)
     {
       Effect = DROPEFFECT_NONE;
     }
-    else if ((TargetTerminal != Terminal) && (Effect == DROPEFFECT_MOVE))
+    else if ((TargetSession != Terminal) && (Effect == DROPEFFECT_MOVE))
     {
       Effect = DROPEFFECT_COPY;
     }
@@ -10075,7 +10116,7 @@ void __fastcall TCustomScpExplorerForm::FormClose(TObject * /*Sender*/, TCloseAc
   FShowing = false;
 
   // Do not save empty workspace
-  if (WinConfiguration->AutoSaveWorkspace && (Terminal != NULL))
+  if (WinConfiguration->AutoSaveWorkspace && (ManagedSession != NULL))
   {
     std::unique_ptr<TObjectList> DataList(DoCollectWorkspace());
     UnicodeString Name = WorkspaceName();
@@ -10140,6 +10181,7 @@ void __fastcall TCustomScpExplorerForm::AddFixedSessionImages()
   FNewSessionTabImageIndex = AddFixedSessionImage(NonVisualDataModule->NewSessionAction->ImageIndex);
   FSessionTabImageIndex = AddFixedSessionImage(SiteImageIndex);
   FSessionColorMaskImageIndex = AddFixedSessionImage(SiteColorMaskImageIndex);
+  FLocalBrowserTabImageIndex = AddFixedSessionImage(LocalBrowserImageIndex);
 }
 //---------------------------------------------------------------------------
 void __fastcall TCustomScpExplorerForm::CollectItemsWithTextDisplayMode(TWinControl * Control)

+ 10 - 5
source/forms/CustomScpExplorer.h

@@ -269,6 +269,7 @@ private:
   int FNewSessionTabImageIndex;
   int FSessionTabImageIndex;
   int FSessionColorMaskImageIndex;
+  int FLocalBrowserTabImageIndex;
   ::TTrayIcon * FTrayIcon;
   TCustomCommandType FLastCustomCommand;
   TFileMasks FDirViewMatchMask;
@@ -360,6 +361,7 @@ protected:
   TQueueItemProxy * FPendingQueueActionItem;
   TTBXPopupMenu * FHistoryMenu[2][2];
   bool FAllowTransferPresetAutoSelect;
+  bool FSessionChanging;
   TStrings * FNotes;
   TTimer * FNoteTimer;
   TDateTime FNoteShown;
@@ -422,8 +424,8 @@ protected:
   void __fastcall LocalCustomCommand(TStrings * FileList,
     const TCustomCommandType & ACommand, TStrings * ALocalFileList,
     const TCustomCommandData & Data, const UnicodeString & CommandCommand);
-  virtual void __fastcall TerminalChanging();
-  virtual void __fastcall TerminalChanged(bool Replaced);
+  virtual void __fastcall SessionChanging();
+  virtual void __fastcall SessionChanged(bool Replaced);
   virtual void __fastcall QueueChanged();
   void __fastcall InitStatusBar();
   void __fastcall UpdateStatusBar();
@@ -660,8 +662,8 @@ protected:
     const UnicodeString FileName, TEditedFileData * Data, TObject * Token,
     void * Arg);
   void __fastcall AnyInternalEditorModified(TObject * Sender, bool & Modified);
-  virtual void __fastcall StartingDisconnected();
-  void __fastcall NeedSession(bool Startup);
+  virtual void __fastcall StartingWithoutSession();
+  virtual void __fastcall NeedSession(bool Startup);
   bool __fastcall DraggingAllFilesFromDirView(TOperationSide Side, TStrings * FileList);
   bool __fastcall SelectedAllFilesInDirView(TCustomDirView * DView);
   TSessionData * __fastcall SessionDataForCode();
@@ -754,6 +756,7 @@ public:
   bool __fastcall HasActiveTerminal();
   bool __fastcall HasManagedSession();
   virtual bool IsLocalBrowserMode();
+  bool CanCloseSession(TManagedTerminal * Session);
 
   void __fastcall NewSession(const UnicodeString & SessionUrl = L"");
   void __fastcall DuplicateSession();
@@ -776,7 +779,7 @@ public:
   virtual void __fastcall CompareDirectories();
   void __fastcall ExecuteCurrentFile();
   virtual void __fastcall OpenConsole(UnicodeString Command = L"");
-  virtual void __fastcall UpdateTerminal(TManagedTerminal * Terminal);
+  virtual void __fastcall UpdateSession(TManagedTerminal * Terminal);
   virtual void __fastcall SynchronizeDirectories();
   virtual void __fastcall FullSynchronizeDirectories() = 0;
   virtual void __fastcall ExploreLocalDirectory(TOperationSide Side);
@@ -797,6 +800,7 @@ public:
   void __fastcall ToggleQueueEnabled();
   UnicodeString __fastcall GetQueueProgressTitle();
   void __fastcall LastTerminalClosed();
+  virtual TManagedTerminal * GetReplacementForLastSession();
   void __fastcall TerminalRemoved(TObject * Sender);
   void __fastcall TerminalDisconnected();
   void __fastcall TerminalConnecting();
@@ -859,6 +863,7 @@ public:
   virtual void __fastcall BrowseFile();
   void __fastcall CloseApp();
   virtual bool IsSideLocalBrowser(TOperationSide Side);
+  virtual UnicodeString GetLocalBrowserSessionTitle(TManagedTerminal * Session);
 
   __property bool ComponentVisible[Byte Component] = { read = GetComponentVisible, write = SetComponentVisible };
   __property bool EnableFocusedOperation[TOperationSide Side] = { read = GetEnableFocusedOperation, index = 0 };

+ 1 - 0
source/forms/Glyphs.h

@@ -28,5 +28,6 @@ private:
 extern PACKAGE TGlyphsModule * GlyphsModule;
 const int SiteImageIndex = 103;
 const int SiteColorMaskImageIndex = 104;
+const int LocalBrowserImageIndex = 29;
 //---------------------------------------------------------------------------
 #endif

+ 14 - 13
source/forms/Login.cpp

@@ -766,7 +766,7 @@ void __fastcall TLoginDialog::SessionTreeDblClick(TObject * /*Sender*/)
   // as that may pop-up modal box.
   if (Node == SessionTree->Selected)
   {
-    // EnsureNotEditing must be before CanLogin, as CanLogin checks for FEditing
+    // EnsureNotEditing must be before CanOpen, as CanOpen checks for FEditing
     if (EnsureNotEditing())
     {
       if (IsCloneToNewSiteDefault())
@@ -776,7 +776,7 @@ void __fastcall TLoginDialog::SessionTreeDblClick(TObject * /*Sender*/)
       // this can hardly be false
       // (after editing and clone tests above)
       // (except for empty folders, but those do not pass a condition below)
-      else if (CanLogin())
+      else if (CanOpen())
       {
         if (IsSessionNode(Node) || IsWorkspaceNode(Node))
         {
@@ -1176,11 +1176,12 @@ void __fastcall TLoginDialog::ActionListUpdate(TBasicAction * BasicAction,
   }
   else if (Action == LoginAction)
   {
-    LoginAction->Enabled = CanLogin();
+    LoginAction->Enabled = CanOpen();
   }
   else if (Action == PuttyAction)
   {
-    Action->Enabled = (NewSiteSelected || SiteSelected) && CanLogin();
+    TSessionData * Data = GetSessionData();
+    Action->Enabled = (NewSiteSelected || SiteSelected) && CanOpen() && !Data->IsLocalBrowser;
   }
   else if (Action == SaveSessionAction)
   {
@@ -1251,14 +1252,14 @@ void __fastcall TLoginDialog::ActionListUpdate(TBasicAction * BasicAction,
 //---------------------------------------------------------------------------
 bool __fastcall TLoginDialog::IsCloneToNewSiteDefault()
 {
-  return !FEditing && !FRenaming && IsSiteNode(SessionTree->Selected) && !StoredSessions->CanLogin(GetSessionData());
+  return !FEditing && !FRenaming && IsSiteNode(SessionTree->Selected) && !StoredSessions->CanOpen(GetSessionData());
 }
 //---------------------------------------------------------------------------
-bool __fastcall TLoginDialog::CanLogin()
+bool __fastcall TLoginDialog::CanOpen()
 {
   TSessionData * Data = GetSessionData();
   return
-    ((Data != NULL) && StoredSessions->CanLogin(Data) && !FEditing) ||
+    ((Data != NULL) && StoredSessions->CanOpen(Data) && !FEditing) ||
     (IsFolderOrWorkspaceNode(SessionTree->Selected) && HasNodeAnySession(SessionTree->Selected, true));
 }
 //---------------------------------------------------------------------------
@@ -1309,8 +1310,8 @@ bool __fastcall TLoginDialog::Execute(TList * DataList)
   // Not calling LoadState here.
   // It's redundant and does not work anyway, see comment in the method.
   int AResult = ShowModal();
-  // When CanLogin is false, the DefaultResult() will fail finding a default button.
-  bool Result = CanLogin() && (AResult == DefaultResult());
+  // When CanOpen is false, the DefaultResult() will fail finding a default button.
+  bool Result = CanOpen() && (AResult == DefaultResult());
   SaveState();
   if (Result)
   {
@@ -1761,7 +1762,7 @@ void __fastcall TLoginDialog::SendToHookActionExecute(TObject * /*Sender*/)
   }
 }
 //---------------------------------------------------------------------------
-bool __fastcall TLoginDialog::HasNodeAnySession(TTreeNode * Node, bool NeedCanLogin)
+bool __fastcall TLoginDialog::HasNodeAnySession(TTreeNode * Node, bool NeedCanOpen)
 {
   bool Result = false;
   TTreeNode * ANode = Node->GetNext();
@@ -1769,7 +1770,7 @@ bool __fastcall TLoginDialog::HasNodeAnySession(TTreeNode * Node, bool NeedCanLo
   {
     Result =
       IsSessionNode(ANode) &&
-      (!NeedCanLogin || StoredSessions->CanLogin(GetNodeSession(ANode)));
+      (!NeedCanOpen || StoredSessions->CanOpen(GetNodeSession(ANode)));
     ANode = ANode->GetNext();
   }
   return Result;
@@ -1825,8 +1826,8 @@ void __fastcall TLoginDialog::FormCloseQuery(TObject * /*Sender*/,
   // CanClose test is now probably redundant,
   // once we have a fallback to LoginButton in DefaultResult
   CanClose = EnsureNotEditing();
-  // When CanLogin is false, the DefaultResult() will fail finding a default button
-  if (CanClose && CanLogin() && (ModalResult == DefaultResult()))
+  // When CanOpen is false, the DefaultResult() will fail finding a default button
+  if (CanClose && CanOpen() && (ModalResult == DefaultResult()))
   {
     SaveDataList(FDataList);
   }

+ 2 - 2
source/forms/Login.h

@@ -353,7 +353,7 @@ private:
   void __fastcall MasterPasswordRecrypt(TObject * Sender);
   void __fastcall LoadOpenedStoredSessionFolders(
     TTreeNode * Node, TStrings * OpenedStoredSessionFolders);
-  bool __fastcall HasNodeAnySession(TTreeNode * Node, bool NeedCanLogin = false);
+  bool __fastcall HasNodeAnySession(TTreeNode * Node, bool NeedCanOpen = false);
   void __fastcall SaveDataList(TList * DataList);
   inline bool __fastcall IsFolderNode(TTreeNode * Node);
   inline bool __fastcall IsWorkspaceNode(TTreeNode * Node);
@@ -381,7 +381,7 @@ private:
   TSessionData * __fastcall GetEditingSessionData();
   void __fastcall SaveAsSession(bool ForceDialog);
   void __fastcall InvalidateSessionData();
-  bool __fastcall CanLogin();
+  bool __fastcall CanOpen();
   bool __fastcall IsCloneToNewSiteDefault();
   bool __fastcall IsDefaultResult(TModalResult Result);
   void __fastcall UpdateNodeImage(TTreeNode * Node);

+ 3 - 3
source/forms/NonVisual.cpp

@@ -414,9 +414,9 @@ void __fastcall TNonVisualDataModule::ExplorerActionsUpdate(
   // SESSION
   UPD(NewSessionAction, true)
   UPD(SiteManagerAction, true)
-  UPD(DuplicateTabAction, HasManagedSession)
-  UPD(RenameTabAction, HasManagedSession)
-  UPD(CloseTabAction, HasManagedSession)
+  UPD(DuplicateTabAction, HasTerminal)
+  UPD(RenameTabAction, HasTerminal)
+  UPD(CloseTabAction, HasManagedSession && ScpExplorer->CanCloseSession(ScpExplorer->ManagedSession))
   UPDEX1(DisconnectSessionAction, HasTerminal, DisconnectSessionAction->Visible = (ScpExplorer->Terminal == NULL) || !ScpExplorer->Terminal->Disconnected)
   UPDEX1(ReconnectSessionAction, (ScpExplorer->Terminal != NULL) && ScpExplorer->Terminal->Disconnected, ReconnectSessionAction->Visible = ReconnectSessionAction->Enabled)
   UPD(SavedSessionsAction2, true)

+ 136 - 65
source/forms/ScpCommander.cpp

@@ -257,29 +257,37 @@ void __fastcall TScpCommanderForm::StoreParams()
   }
 }
 //---------------------------------------------------------------------------
-void __fastcall TScpCommanderForm::UpdateTerminal(TManagedTerminal * Terminal)
+void __fastcall TScpCommanderForm::UpdateSession(TManagedTerminal * Session)
 {
-  DebugAssert(!IsLocalBrowserMode());
-  TCustomScpExplorerForm::UpdateTerminal(Terminal);
+  TCustomScpExplorerForm::UpdateSession(Session);
 
   DebugAssert(LocalDirView != NULL);
 
-  SAFE_DESTROY(Terminal->LocalExplorerState);
+  SAFE_DESTROY(Session->LocalExplorerState);
+  SAFE_DESTROY(Session->OtherLocalExplorerState);
 
   if (WinConfiguration->PreservePanelState)
   {
-    Terminal->LocalExplorerState = LocalDirView->SaveState();
+    Session->LocalExplorerState = LocalDirView->SaveState();
+
+    if (IsLocalBrowserMode())
+    {
+      Session->OtherLocalExplorerState = OtherLocalDirView->SaveState();
+    }
   }
 }
 //---------------------------------------------------------------------------
 void __fastcall TScpCommanderForm::UpdateSessionData(TSessionData * Data)
 {
-  DebugAssert(!IsLocalBrowserMode());
   // Keep in sync with TSessionData::CopyStateData
   TCustomScpExplorerForm::UpdateSessionData(Data);
 
   DebugAssert(LocalDirView);
   Data->LocalDirectory = LocalDirView->PathName;
+  if (IsLocalBrowserMode())
+  {
+    Data->OtherLocalDirectory = OtherLocalDirView->PathName;
+  }
   Data->SynchronizeBrowsing = NonVisualDataModule->SynchronizeBrowsingAction2->Checked;
 }
 //---------------------------------------------------------------------------
@@ -392,6 +400,33 @@ void __fastcall TScpCommanderForm::DoShow()
   }
 
   TCustomScpExplorerForm::DoShow();
+
+  // Need to do do this before the main window really displays.
+  // Also the NoSession test in TCustomScpExplorerForm::CMShowingChanged rely on this happening here.
+  if (ManagedSession == NULL)
+  {
+    TTerminalManager * Manager = TTerminalManager::Instance();
+    Manager->ActiveSession = Manager->NewLocalBrowser();
+  }
+}
+//---------------------------------------------------------------------------
+void __fastcall TScpCommanderForm::NeedSession(bool Startup)
+{
+  if (ManagedSession == NULL)
+  {
+    // in the LastTerminalClosed, new session should have already been created by GetReplacementForLastSession
+    DebugAssert(Startup);
+
+    TTerminalManager * Manager = TTerminalManager::Instance();
+    Manager->ActiveSession = Manager->NewLocalBrowser();
+  }
+
+  TCustomScpExplorerForm::NeedSession(Startup);
+}
+//---------------------------------------------------------------------------
+TManagedTerminal * TScpCommanderForm::GetReplacementForLastSession()
+{
+  return TTerminalManager::Instance()->NewLocalBrowser();
 }
 //---------------------------------------------------------------------------
 Boolean __fastcall TScpCommanderForm::AllowedAction(TAction * Action, TActionAllowed Allowed)
@@ -536,75 +571,89 @@ void __fastcall TScpCommanderForm::BatchEnd(void * Storage)
   delete Storage;
 }
 //---------------------------------------------------------------------------
-void __fastcall TScpCommanderForm::StartingDisconnected()
+void __fastcall TScpCommanderForm::StartingWithoutSession()
 {
-  TCustomScpExplorerForm::StartingDisconnected();
+  TCustomScpExplorerForm::StartingWithoutSession();
 
   LocalDefaultDirectory();
 }
 //---------------------------------------------------------------------------
-void __fastcall TScpCommanderForm::TerminalChanged(bool Replaced)
+void TScpCommanderForm::RestoreSessionLocalDirView(TDirView * ALocalDirView, const UnicodeString & LocalDirectory)
 {
-  NonVisualDataModule->SynchronizeBrowsingAction2->Checked = false;
+  // we will load completely different directory, so particularly
+  // do not attempt to select previously selected directory
+  ALocalDirView->ContinueSession(false);
 
-  TCustomScpExplorerForm::TerminalChanged(Replaced);
+  // reset home directory
+  ALocalDirView->HomeDirectory = L"";
 
-  if (Terminal)
+  if (!LocalDirectory.IsEmpty() &&
+      (FFirstTerminal || !WinConfiguration->ScpCommander.PreserveLocalDirectory || ManagedSession->LocalBrowser))
   {
-    // we will load completelly different directory, so particularly
-    // do not attempt to select previously selected directory
-    LocalDirView->ContinueSession(false);
-
-    // reset home directory
-    LocalDirView->HomeDirectory = L"";
-
-    if (FFirstTerminal || !WinConfiguration->ScpCommander.PreserveLocalDirectory)
+    try
     {
-      UnicodeString LocalDirectory = Terminal->StateData->LocalDirectory;
-
-      if (!LocalDirectory.IsEmpty())
+      ALocalDirView->Path = LocalDirectory;
+    }
+    catch(Exception & E)
+    {
+      if (!ManagedSession->SessionData->UpdateDirectories)
       {
-        try
-        {
-          LocalDirView->Path = LocalDirectory;
-        }
-        catch(Exception & E)
-        {
-          if (!Terminal->SessionData->UpdateDirectories)
-          {
-            Terminal->ShowExtendedException(&E);
-          }
-        }
+        ManagedSession->ShowExtendedException(&E);
       }
     }
+  }
+}
+//---------------------------------------------------------------------------
+void __fastcall TScpCommanderForm::SessionChanged(bool Replaced)
+{
+  NonVisualDataModule->SynchronizeBrowsingAction2->Checked = false;
+
+  TCustomScpExplorerForm::SessionChanged(Replaced);
+
+  if (ManagedSession != NULL)
+  {
+    RestoreSessionLocalDirView(LocalDirView, ManagedSession->StateData->LocalDirectory);
+
+    if (ManagedSession->LocalBrowser)
+    {
+      RestoreSessionLocalDirView(OtherLocalDirView, ManagedSession->StateData->OtherLocalDirectory);
+    }
+
     FFirstTerminal = false;
 
-    // Happens when opening a connection from a command-line (StartingDisconnected was not called),
+    // Happens when opening a connection from a command-line (StartingWithoutSession was not called),
     // which does not have a local directory set yet.
+    // Or when starting with vanila local browser.
     if (LocalDirView->Path.IsEmpty())
     {
       LocalDefaultDirectory();
     }
 
     if (WinConfiguration->DefaultDirIsHome &&
-        !Terminal->SessionData->UpdateDirectories)
+        !ManagedSession->SessionData->UpdateDirectories &&
+        DebugAlwaysTrue(!ManagedSession->LocalBrowser))
     {
-      LocalDirView->HomeDirectory = Terminal->SessionData->LocalDirectoryExpanded;
+      LocalDirView->HomeDirectory = ManagedSession->SessionData->LocalDirectoryExpanded;
     }
 
     if (WinConfiguration->PreservePanelState &&
-        !WinConfiguration->ScpCommander.PreserveLocalDirectory)
+        (!WinConfiguration->ScpCommander.PreserveLocalDirectory || ManagedSession->LocalBrowser))
     {
-      if (Terminal->LocalExplorerState != NULL)
-      {
-        LocalDirView->RestoreState(Terminal->LocalExplorerState);
-      }
-      else
+      LocalDirView->RestoreState(ManagedSession->LocalExplorerState);
+
+      if (ManagedSession->LocalBrowser)
       {
-        LocalDirView->ClearState();
+        OtherLocalDirView->RestoreState(ManagedSession->OtherLocalExplorerState);
       }
+      NonVisualDataModule->SynchronizeBrowsingAction2->Checked = ManagedSession->StateData->SynchronizeBrowsing;
+    }
 
-      NonVisualDataModule->SynchronizeBrowsingAction2->Checked = Terminal->StateData->SynchronizeBrowsing;
+    if (ManagedSession->LocalBrowser)
+    {
+      // Particularly, when switching from a remote to a local-local session,
+      // the other local dir view path may not change at all,
+      // so the path combo box would retain the remote path.
+      UpdateRemotePathComboBox(false);
     }
   }
 }
@@ -655,10 +704,10 @@ void __fastcall TScpCommanderForm::LocalDefaultDirectory()
 void __fastcall TScpCommanderForm::ConfigurationChanged()
 {
   TCustomScpExplorerForm::ConfigurationChanged();
-  if (WinConfiguration->DefaultDirIsHome && Terminal &&
-      !Terminal->SessionData->UpdateDirectories)
+  if (WinConfiguration->DefaultDirIsHome && (ManagedSession != NULL) &&
+      !ManagedSession->SessionData->UpdateDirectories)
   {
-    LocalDirView->HomeDirectory = Terminal->SessionData->LocalDirectoryExpanded;
+    LocalDirView->HomeDirectory = ManagedSession->SessionData->LocalDirectoryExpanded;
   }
   else
   {
@@ -923,6 +972,16 @@ void __fastcall TScpCommanderForm::UpdateControls()
   bool DirViewWasFocused = DirView(osOther)->Focused();
   bool DriveViewWasFocused = DriveView(osOther)->Focused();
 
+  // When implicitly connecting a not-yet-connected session by switching to its tab opened with workspace,
+  // Terminal property is still NULL, but ActiveTerminal is already set.
+  // Had we used Terminal for the test, the window would switch to local-local mode temporarily while session is opening.
+  bool HasTerminal = (TTerminalManager::Instance()->ActiveTerminal != NULL);
+  // Set asap, so that TCustomScpExplorerForm already knows if we are in local-local mode.
+  RemoteDirView->Visible = HasTerminal;
+  RemoteDriveView->Visible = HasTerminal;
+  OtherLocalDirView->Visible = !HasTerminal;
+  OtherLocalDriveView->Visible = !HasTerminal;
+
   TCustomScpExplorerForm::UpdateControls();
 
   UnicodeString SplitterLongHint = Splitter->Hint;
@@ -1008,14 +1067,6 @@ void __fastcall TScpCommanderForm::UpdateControls()
   // otherwise it shines too much on the toolbar.
   RemoteNewSubmenuItem->Enabled = DirViewEnabled(osRemote);
 
-  // When implicitly connecting a not-yet-connected session by switching to its tab opened with workspace,
-  // Terminal property is still NULL, but ActiveTerminal is already set.
-  // Had we used Terminal for the test, the window would switch to local-local mode temporarily while session is opening.
-  bool HasTerminal = (TTerminalManager::Instance()->ActiveTerminal != NULL);
-  RemoteDirView->Visible = HasTerminal;
-  RemoteDriveView->Visible = HasTerminal;
-  OtherLocalDirView->Visible = !HasTerminal;
-  OtherLocalDriveView->Visible = !HasTerminal;
   bool LocalOnLeft = (Panel(true) == LocalPanel);
   LocalMenuButton->Caption = LoadStr(HasTerminal ? LOCAL_MENU_CAPTION : (LocalOnLeft ? LEFT_MENU_CAPTION : RIGHT_MENU_CAPTION));
   RemoteMenuButton->Caption = LoadStr(HasTerminal ? REMOTE_MENU_CAPTION : (LocalOnLeft ? RIGHT_MENU_CAPTION : LEFT_MENU_CAPTION));
@@ -2045,18 +2096,12 @@ UnicodeString __fastcall TScpCommanderForm::PathForCaption()
   {
     // make sure the path corresponds to the terminal in title
     // (there's a mismatch, while opening a new session)
-    if (Terminal == TTerminalManager::Instance()->ActiveTerminal)
+    if (ManagedSession == TTerminalManager::Instance()->ActiveSession)
     {
       switch (WinConfiguration->PathInCaption)
       {
         case picShort:
-          {
-            Result = ExtractFileName(GetCurrentLocalBrowser()->PathName);
-            if (Result.IsEmpty())
-            {
-              Result = GetCurrentLocalBrowser()->PathName;
-            }
-          }
+          Result = ExtractShortName(GetCurrentLocalBrowser()->PathName, false);
           break;
 
         case picFull:
@@ -2595,7 +2640,8 @@ void __fastcall TScpCommanderForm::OtherLocalDirViewPathChange(TCustomDirView *
   // should happen only when called from TScpCommanderForm::DoShow while starting connected
   if (IsLocalBrowserMode())
   {
-    DoLocalDirViewPathChange(Sender, RemotePathComboBox);
+    DebugAssert(Sender == OtherLocalDirView);
+    UpdateRemotePathComboBox(!FSessionChanging);
   }
 }
 //---------------------------------------------------------------------------
@@ -2731,3 +2777,28 @@ void TScpCommanderForm::LocalLocalCopy(
     }
   }
 }
+//---------------------------------------------------------------------------
+UnicodeString TScpCommanderForm::GetLocalBrowserSessionTitle(TManagedTerminal * ASession)
+{
+  DebugAssert(ASession->LocalBrowser);
+  UnicodeString Path1;
+  UnicodeString Path2;
+  TTerminalManager * Manager = TTerminalManager::Instance();
+  if ((ASession == ManagedSession) &&
+      // prevent tab title flicker, when switching to local-local tab, as the path changes in individual local panels
+      !FSessionChanging)
+  {
+    Path1 = LocalDirView->PathName;
+    Path2 = OtherLocalDirView->PathName;
+  }
+  else
+  {
+    Path1 = ASession->StateData->LocalDirectory;
+    Path2 = ASession->StateData->OtherLocalDirectory;
+  }
+  // See also TSessionData::GetDefaultSessionName()
+  Path1 = Manager->GetPathForSessionTabName(ExtractShortName(Path1, false));
+  Path2 = Manager->GetPathForSessionTabName(ExtractShortName(Path2, false));
+  UnicodeString Result = FORMAT(L"%s - %s", (Path1, Path2));
+  return Result;
+}

+ 7 - 3
source/forms/ScpCommander.h

@@ -560,7 +560,7 @@ protected:
     TScpCommanderPanelConfiguration & PanelConfiguration);
   virtual void __fastcall RestoreParams();
   virtual void __fastcall FixControlsPlacement();
-  virtual void __fastcall TerminalChanged(bool Replaced);
+  virtual void __fastcall SessionChanged(bool Replaced);
   virtual void __fastcall ConfigurationChanged();
   virtual bool __fastcall GetHasDirView(TOperationSide Side);
   virtual TCustomDirView * GetCurrentLocalBrowser();
@@ -610,7 +610,7 @@ protected:
   virtual bool __fastcall UpdateToolbarDisplayMode();
   virtual void __fastcall QueueLabelUpdateStatus();
   virtual UnicodeString __fastcall DefaultDownloadTargetDirectory();
-  virtual void __fastcall StartingDisconnected();
+  virtual void __fastcall StartingWithoutSession();
   virtual void __fastcall UpdateImages();
   virtual void __fastcall FileColorsChanged();
   virtual void __fastcall ThemeChanged();
@@ -624,6 +624,8 @@ protected:
   virtual void __fastcall DoRemotePathComboBoxItemClick(TObject * Sender);
   virtual void __fastcall UpdateRemotePathComboBox(bool TextOnly);
   void __fastcall SetToolbar2ItemAction(TTBXItem * Item, TBasicAction * Action);
+  virtual void __fastcall NeedSession(bool Startup);
+  void RestoreSessionLocalDirView(TDirView * ALocalDirView, const UnicodeString & LocalDirectory);
 
 public:
   __fastcall TScpCommanderForm(TComponent* Owner);
@@ -633,7 +635,7 @@ public:
   virtual bool __fastcall AllowedAction(TAction * Action, TActionAllowed Allowed);
   virtual void __fastcall ChangePath(TOperationSide Side);
   virtual void __fastcall CompareDirectories();
-  virtual void __fastcall UpdateTerminal(TManagedTerminal * Terminal);
+  virtual void __fastcall UpdateSession(TManagedTerminal * Terminal);
   virtual void __fastcall SynchronizeDirectories();
   virtual void __fastcall FullSynchronizeDirectories();
   virtual void __fastcall StoreParams();
@@ -653,6 +655,8 @@ public:
   virtual bool IsLocalBrowserMode();
   virtual void LocalLocalCopy(
     ::TFileOperation Operation, TOperationSide Side, bool OnFocused, bool NoConfirmation, bool DragDrop, unsigned int Flags);
+  virtual UnicodeString GetLocalBrowserSessionTitle(TManagedTerminal * Session);
+  virtual TManagedTerminal * GetReplacementForLastSession();
 
   __property double LeftPanelWidth = { read = GetLeftPanelWidth, write = SetLeftPanelWidth };
 };

+ 30 - 29
source/packages/filemng/CustomDirView.pas

@@ -378,7 +378,6 @@ type
     function PasteFromClipBoard(TargetPath: string = ''): Boolean; virtual; abstract;
     function SaveState: TObject;
     procedure RestoreState(AState: TObject);
-    procedure ClearState;
     procedure DisplayContextMenu(Where: TPoint); virtual; abstract;
     procedure DisplayContextMenuInSitu;
     procedure UpdateStatusBar;
@@ -3269,40 +3268,42 @@ var
   DirColProperties: TCustomDirViewColProperties;
   ListItem: TListItem;
 begin
-  Assert(AState is TDirViewState);
-  State := AState as TDirViewState;
-  Assert(Assigned(State));
-
-  if Assigned(State.HistoryPaths) then
-    FHistoryPaths.Assign(State.HistoryPaths);
-  FBackCount := State.BackCount;
-  DoHistoryChange;
-  if State.SortStr <> '' then
-  begin
-    // TCustomDirViewColProperties should not be here
-    DirColProperties := ColProperties as TCustomDirViewColProperties;
-    Assert(Assigned(DirColProperties));
-    DirColProperties.SortStr := State.SortStr;
-  end;
-  Mask := State.Mask;
-  if State.FocusedItem <> '' then
+  if Assigned(AState) then
   begin
-    ListItem := FindFileItem(State.FocusedItem);
-    if Assigned(ListItem) then
+    Assert(AState is TDirViewState);
+    State := AState as TDirViewState;
+    Assert(Assigned(State));
+
+    if Assigned(State.HistoryPaths) then
+      FHistoryPaths.Assign(State.HistoryPaths);
+    FBackCount := State.BackCount;
+    DoHistoryChange;
+    if State.SortStr <> '' then
     begin
-      ItemFocused := ListItem;
-      ListItem.MakeVisible(False);
+      // TCustomDirViewColProperties should not be here
+      DirColProperties := ColProperties as TCustomDirViewColProperties;
+      Assert(Assigned(DirColProperties));
+      DirColProperties.SortStr := State.SortStr;
+    end;
+    Mask := State.Mask;
+    if State.FocusedItem <> '' then
+    begin
+      ListItem := FindFileItem(State.FocusedItem);
+      if Assigned(ListItem) then
+      begin
+        ItemFocused := ListItem;
+        ListItem.MakeVisible(False);
+      end;
     end;
+  end
+    else
+  begin
+    FHistoryPaths.Clear;
+    FBackCount := 0;
+    DoHistoryChange;
   end;
 end;
 
-procedure TCustomDirView.ClearState;
-begin
-  FHistoryPaths.Clear;
-  FBackCount := 0;
-  DoHistoryChange;
-end;
-
 procedure TCustomDirView.SetMask(Value: string);
 begin
   if Mask <> Value then

+ 127 - 68
source/windows/TerminalManager.cpp

@@ -30,7 +30,7 @@ TTerminalManager * TTerminalManager::FInstance = NULL;
 __fastcall TManagedTerminal::TManagedTerminal(TSessionData * SessionData,
   TConfiguration * Configuration) :
   TTerminal(SessionData, Configuration),
-  LocalBrowser(false), LocalExplorerState(NULL), RemoteExplorerState(NULL),
+  LocalBrowser(false), LocalExplorerState(NULL), RemoteExplorerState(NULL), OtherLocalExplorerState(NULL),
   ReopenStart(0), DirectoryLoaded(Now()), TerminalThread(NULL), Disconnected(false), DisconnectedTemporarily(false)
 {
   StateData = new TSessionData(L"");
@@ -42,6 +42,7 @@ __fastcall TManagedTerminal::~TManagedTerminal()
 {
   delete StateData;
   delete LocalExplorerState;
+  delete OtherLocalExplorerState;
   delete RemoteExplorerState;
 }
 //---------------------------------------------------------------------------
@@ -77,6 +78,7 @@ __fastcall TTerminalManager::TTerminalManager() :
   FChangeSection.reset(new TCriticalSection());
   FPendingConfigurationChange = 0;
   FKeepAuthenticateForm = false;
+  FUpdating = 0;
 
   FApplicationsEvents.reset(new TApplicationEvents(Application));
   FApplicationsEvents->OnException = ApplicationException;
@@ -145,7 +147,9 @@ TTerminalQueue * __fastcall TTerminalManager::NewQueue(TTerminal * Terminal)
 //---------------------------------------------------------------------------
 TManagedTerminal * __fastcall TTerminalManager::CreateManagedTerminal(TSessionData * Data)
 {
-  return new TManagedTerminal(Data, Configuration);
+  TManagedTerminal * Result = new TManagedTerminal(Data, Configuration);
+  Result->LocalBrowser = Data->IsLocalBrowser;
+  return Result;
 }
 //---------------------------------------------------------------------------
 TTerminal * __fastcall TTerminalManager::CreateTerminal(TSessionData * Data)
@@ -201,6 +205,14 @@ TTerminal * __fastcall TTerminalManager::NewTerminal(TSessionData * Data)
   return Terminal;
 }
 //---------------------------------------------------------------------------
+TManagedTerminal * __fastcall TTerminalManager::NewLocalBrowser()
+{
+  std::unique_ptr<TSessionData> SessionData(new TSessionData(UnicodeString()));
+  TManagedTerminal * Terminal = NewManagedTerminal(SessionData.get());
+  Terminal->LocalBrowser = true;
+  return Terminal;
+}
+//---------------------------------------------------------------------------
 TManagedTerminal * __fastcall TTerminalManager::NewManagedTerminal(TSessionData * Data)
 {
   return DebugNotNull(dynamic_cast<TManagedTerminal *>(NewTerminal(Data)));
@@ -234,8 +246,8 @@ void __fastcall TTerminalManager::FreeActiveTerminal()
 {
   if (FTerminalPendingAction == tpNull)
   {
-    DebugAssert(ActiveTerminal);
-    FreeTerminal(ActiveTerminal);
+    DebugAssert(ActiveSession != NULL);
+    FreeTerminal(ActiveSession);
   }
   else
   {
@@ -388,7 +400,7 @@ bool __fastcall TTerminalManager::ConnectActiveTerminalImpl(bool Reopen)
       if (ScpExplorer)
       {
         DebugAssert(ActiveTerminal->Status == ssOpened);
-        TerminalReady();
+        SessionReady();
       }
 
       WinConfiguration->ClearTemporaryLoginData();
@@ -511,7 +523,7 @@ void __fastcall TTerminalManager::DisconnectActiveTerminal()
   ActiveTerminal->Disconnected = true;
   if (ScpExplorer != NULL)
   {
-    TerminalReady(); // in case it was never connected
+    SessionReady(); // in case it was never connected
     ScpExplorer->TerminalDisconnected();
   }
   // disconnecting duplidate session removes need to distinguish the only connected session with short path
@@ -526,7 +538,7 @@ void __fastcall TTerminalManager::ReconnectActiveTerminal()
   {
     if (ScpExplorer->Terminal == ActiveTerminal)
     {
-      ScpExplorer->UpdateTerminal(ActiveTerminal);
+      ScpExplorer->UpdateSession(ActiveTerminal);
     }
   }
 
@@ -568,9 +580,10 @@ void __fastcall TTerminalManager::FreeTerminal(TTerminal * Terminal)
 {
   try
   {
+    TManagedTerminal * ManagedSession = DebugNotNull(dynamic_cast<TManagedTerminal *>(Terminal));
     // we want the Login dialog to open on auto-workspace name,
     // as set in TCustomScpExplorerForm::FormClose
-    if (!FDestroying || !WinConfiguration->AutoSaveWorkspace)
+    if ((!FDestroying || !WinConfiguration->AutoSaveWorkspace) && !ManagedSession->LocalBrowser)
     {
       if (StoredSessions->FindSame(Terminal->SessionData) != NULL)
       {
@@ -598,22 +611,38 @@ void __fastcall TTerminalManager::FreeTerminal(TTerminal * Terminal)
     FQueues->Delete(Index);
     FTerminationMessages->Delete(Index);
 
-    if (ActiveTerminal && (Terminal == ActiveTerminal))
+    if ((ActiveSession != NULL) && (Terminal == ActiveSession))
     {
-      if ((Count > 0) && !FDestroying)
+      TManagedTerminal * NewActiveTerminal;
+      bool LastTerminalClosed = false;
+
+      if (FDestroying)
       {
-        TManagedTerminal * NewActiveTerminal = Sessions[Index < Count ? Index : Index - 1];
-        if (!NewActiveTerminal->Active && !NewActiveTerminal->Disconnected)
-        {
-          NewActiveTerminal->Disconnected = true;
-          NewActiveTerminal->DisconnectedTemporarily = true;
-        }
-        ActiveSession = NewActiveTerminal;
+        NewActiveTerminal = NULL;
       }
       else
       {
-        ActiveSession = NULL;
+        if (Count > 0)
+        {
+          NewActiveTerminal = Sessions[Index < Count ? Index : Index - 1];
+          if (!NewActiveTerminal->Active && !NewActiveTerminal->Disconnected)
+          {
+            NewActiveTerminal->Disconnected = true;
+            NewActiveTerminal->DisconnectedTemporarily = true;
+          }
+        }
+        else
+        {
+          NewActiveTerminal = NULL;
+          LastTerminalClosed = true;
+          if (ScpExplorer != NULL)
+          {
+            TAutoNestingCounter UpdatingCounter(FUpdating); // prevent tab flicker
+            NewActiveTerminal = ScpExplorer->GetReplacementForLastSession();
+          }
+        }
       }
+      DoSetActiveSession(NewActiveTerminal, false, LastTerminalClosed);
     }
     else
     {
@@ -661,31 +690,29 @@ TManagedTerminal * TTerminalManager::GetActiveTerminal()
 //---------------------------------------------------------------------------
 void __fastcall TTerminalManager::SetActiveSession(TManagedTerminal * value)
 {
-  DoSetActiveSession(value, false);
+  DoSetActiveSession(value, false, false);
 }
 //---------------------------------------------------------------------------
 void __fastcall TTerminalManager::SetActiveTerminalWithAutoReconnect(TManagedTerminal * value)
 {
-  DoSetActiveSession(value, true);
+  DoSetActiveSession(value, true, false);
 }
 //---------------------------------------------------------------------------
-void __fastcall TTerminalManager::DoSetActiveSession(TManagedTerminal * value, bool AutoReconnect)
+void __fastcall TTerminalManager::DoSetActiveSession(TManagedTerminal * value, bool AutoReconnect, bool LastTerminalClosed)
 {
   if (ActiveSession != value)
   {
-    // here used to be call to TCustomScpExporer::UpdateSessionData (now UpdateTerminal)
-    // but it seems to be duplicate to call from TCustomScpExporer::TerminalChanging
+    // here used to be call to TCustomScpExporer::UpdateSessionData (now UpdateSession)
+    // but it seems to be duplicate to call from TCustomScpExporer::SessionChanging
 
     TManagedTerminal * PActiveSession = ActiveSession;
     FActiveSession = value;
-    // moved from else block of next if (ActiveTerminal) statement
-    // so ScpExplorer can update its caption
-    UpdateAppTitle();
     if (ScpExplorer)
     {
-      if ((ActiveSession != NULL) && ((ActiveSession->Status == ssOpened) || ActiveSession->Disconnected))
+      if ((ActiveSession != NULL) &&
+          ((ActiveSession->Status == ssOpened) || ActiveSession->Disconnected || ActiveSession->LocalBrowser))
       {
-        TerminalReady();
+        SessionReady();
       }
       else
       {
@@ -693,6 +720,7 @@ void __fastcall TTerminalManager::DoSetActiveSession(TManagedTerminal * value, b
         ScpExplorer->Queue = NULL;
       }
     }
+    UpdateAppTitle();
 
     if (PActiveSession != NULL)
     {
@@ -734,17 +762,26 @@ void __fastcall TTerminalManager::DoSetActiveSession(TManagedTerminal * value, b
           }
         }
       }
+
+      // LastTerminalClosed is true only for a replacement local session,
+      // and it should never happen that it fails to be activated
+      if (LastTerminalClosed && DebugAlwaysFalse(value != ActiveSession))
+      {
+        LastTerminalClosed = false; // just in case
+      }
     }
     else
     {
-      if (FScpExplorer != NULL)
-      {
-        FScpExplorer->LastTerminalClosed();
-      }
+      LastTerminalClosed = true;
+    }
+
+    if (LastTerminalClosed && !Updating && (ScpExplorer != NULL))
+    {
+      ScpExplorer->LastTerminalClosed();
     }
 
-    if ((ActiveSession != NULL) && !ActiveSession->Active &&
-        !ActiveSession->Disconnected)
+    if ((ActiveSession != NULL) &&
+        !ActiveSession->Active && !ActiveSession->Disconnected && !ActiveSession->LocalBrowser)
     {
       ConnectActiveTerminal();
     }
@@ -770,7 +807,7 @@ bool __fastcall TTerminalManager::ShouldDisplayQueueStatusOnAppTitle()
 //---------------------------------------------------------------------------
 UnicodeString __fastcall TTerminalManager::FormatFormCaptionWithSession(TCustomForm * Form, const UnicodeString & Caption)
 {
-  return FormatFormCaption(Form, Caption, GetActiveTerminalTitle(false));
+  return FormatFormCaption(Form, Caption, GetActiveSessionAppTitle());
 }
 //---------------------------------------------------------------------------
 UnicodeString __fastcall TTerminalManager::GetAppProgressTitle()
@@ -806,7 +843,7 @@ void __fastcall TTerminalManager::UpdateAppTitle()
       MainForm->Perform(WM_MANAGES_CAPTION, 0, 0);
     }
 
-    UnicodeString NewTitle = FormatMainFormCaption(GetActiveTerminalTitle(false));
+    UnicodeString NewTitle = FormatMainFormCaption(GetActiveSessionAppTitle());
 
     UnicodeString ProgressTitle = GetAppProgressTitle();
     if (!ProgressTitle.IsEmpty())
@@ -1366,7 +1403,7 @@ void __fastcall TTerminalManager::ConfigurationChange(TObject * /*Sender*/)
   }
 }
 //---------------------------------------------------------------------------
-void __fastcall TTerminalManager::TerminalReady()
+void __fastcall TTerminalManager::SessionReady()
 {
   ScpExplorer->ManagedSession = ActiveSession;
   ScpExplorer->Queue = ActiveQueue;
@@ -1379,7 +1416,7 @@ TStrings * __fastcall TTerminalManager::GetSessionList()
   for (int i = 0; i < Count; i++)
   {
     TManagedTerminal * Terminal = Sessions[i];
-    UnicodeString Name = GetTerminalTitle(Terminal, true);
+    UnicodeString Name = GetSessionTitle(Terminal, true);
     FSessionList->AddObject(Name, Terminal);
   }
   return FSessionList;
@@ -1395,58 +1432,67 @@ void __fastcall TTerminalManager::SetActiveSessionIndex(int value)
   ActiveSession = Sessions[value];
 }
 //---------------------------------------------------------------------------
-UnicodeString __fastcall TTerminalManager::GetTerminalShortPath(TTerminal * Terminal)
+UnicodeString TTerminalManager::GetPathForSessionTabName(const UnicodeString & Path)
 {
-  UnicodeString Result = UnixExtractFileName(Terminal->CurrentDirectory);
-  if (Result.IsEmpty())
+  UnicodeString Result = Path;
+  const int MaxPathLen = 16;
+  if ((WinConfiguration->SessionTabNameFormat == stnfShortPathTrunc) &&
+      (Result.Length() > MaxPathLen))
   {
-    Result = Terminal->CurrentDirectory;
+    Result = Result.SubString(1, MaxPathLen - 2) + Ellipsis;
   }
   return Result;
 }
 //---------------------------------------------------------------------------
-UnicodeString __fastcall TTerminalManager::GetTerminalTitle(TTerminal * Terminal, bool Unique)
+UnicodeString __fastcall TTerminalManager::GetSessionTitle(TManagedTerminal * Session, bool Unique)
 {
-  UnicodeString Result = Terminal->SessionData->SessionName;
-  if (Unique &&
-      (WinConfiguration->SessionTabNameFormat != stnfNone))
+  UnicodeString Result;
+  if (!Session->LocalBrowser)
   {
-    int Index = IndexOf(Terminal);
-    // not for background transfer sessions and disconnected sessions
-    if ((Index >= 0) && Terminal->Active)
+    Result = Session->SessionData->SessionName;
+    if (Unique &&
+        (WinConfiguration->SessionTabNameFormat != stnfNone))
     {
-      for (int Index2 = 0; Index2 < Count; Index2++)
+      int Index = IndexOf(Session);
+      // not for background transfer sessions and disconnected sessions
+      if ((Index >= 0) && Session->Active)
       {
-        UnicodeString Name = Sessions[Index2]->SessionData->SessionName;
-        if ((Sessions[Index2] != Terminal) &&
-            Sessions[Index2]->Active &&
-            SameText(Name, Result))
+        for (int Index2 = 0; Index2 < Count; Index2++)
         {
-          UnicodeString Path = GetTerminalShortPath(Terminal);
-          if (!Path.IsEmpty())
+          UnicodeString Name = Sessions[Index2]->SessionData->SessionName;
+          if ((Sessions[Index2] != Session) &&
+              Sessions[Index2]->Active &&
+              SameText(Name, Result))
           {
-            const int MaxPathLen = 16;
-            if ((WinConfiguration->SessionTabNameFormat == stnfShortPathTrunc) &&
-                (Path.Length() > MaxPathLen))
+            UnicodeString Path = ExtractShortName(Session->CurrentDirectory, true);
+            if (!Path.IsEmpty())
             {
-              Path = Path.SubString(1, MaxPathLen - 2) + Ellipsis;
+              Path = GetPathForSessionTabName(Path);
+              Result = FORMAT(L"%s (%s)", (Result, Path));
             }
-            Result = FORMAT(L"%s (%s)", (Result, Path));
+            break;
           }
-          break;
         }
       }
     }
   }
+  else
+  {
+    // should happen only when closing
+    if (ScpExplorer != NULL)
+    {
+      Result = ScpExplorer->GetLocalBrowserSessionTitle(Session);
+    }
+  }
   return Result;
 }
 //---------------------------------------------------------------------------
-UnicodeString __fastcall TTerminalManager::GetActiveTerminalTitle(bool Unique)
+UnicodeString __fastcall TTerminalManager::GetActiveSessionAppTitle()
 {
   UnicodeString Result;
-  if (ActiveTerminal != NULL)
+  if ((ActiveSession != NULL) && !ActiveSession->LocalBrowser)
   {
-    Result = GetTerminalTitle(ActiveTerminal, Unique);
+    Result = GetSessionTitle(ActiveSession, false);
   }
   return Result;
 }
@@ -1454,7 +1500,7 @@ UnicodeString __fastcall TTerminalManager::GetActiveTerminalTitle(bool Unique)
 TTerminalQueue * __fastcall TTerminalManager::GetActiveQueue()
 {
   TTerminalQueue * Result = NULL;
-  if (ActiveTerminal != NULL)
+  if (ActiveSession != NULL)
   {
     Result = reinterpret_cast<TTerminalQueue *>(FQueues->Items[ActiveSessionIndex]);
   }
@@ -1521,7 +1567,8 @@ void __fastcall TTerminalManager::OpenInPutty()
   }
 }
 //---------------------------------------------------------------------------
-void __fastcall TTerminalManager::NewSession(const UnicodeString & SessionUrl, bool ReloadSessions, TForm * LinkedForm)
+void __fastcall TTerminalManager::NewSession(
+  const UnicodeString & SessionUrl, bool ReloadSessions, TForm * LinkedForm, bool ReplaceExisting)
 {
   if (ReloadSessions)
   {
@@ -1550,6 +1597,13 @@ void __fastcall TTerminalManager::NewSession(const UnicodeString & SessionUrl, b
 
     if (DataList->Count > 0)
     {
+      if (ReplaceExisting)
+      {
+        // Tested for only the implicit Commanders' local browser
+        DebugAssert((Count == 0) || ((Count == 1) && Sessions[0]->LocalBrowser));
+        TAutoNestingCounter UpdatingCounter(FUpdating); // prevent tab flicker
+        FreeAll();
+      }
       TManagedTerminal * ANewTerminal = NewTerminals(DataList.get());
       bool AdHoc = (DataList->Count == 1) && (StoredSessions->FindSame(reinterpret_cast<TSessionData *>(DataList->Items[0])) == NULL);
       bool CanRetry = SessionUrl.IsEmpty() && AdHoc;
@@ -1725,7 +1779,7 @@ void __fastcall TTerminalManager::Move(TTerminal * Source, TTerminal * Target)
 //---------------------------------------------------------------------------
 void __fastcall TTerminalManager::DoSessionListChanged()
 {
-  if (FScpExplorer != NULL)
+  if ((FScpExplorer != NULL) && !Updating)
   {
     FScpExplorer->SessionListChanged();
   }
@@ -1998,3 +2052,8 @@ bool __fastcall TTerminalManager::UploadPublicKey(
 
   return Result;
 }
+//---------------------------------------------------------------------------
+bool TTerminalManager::IsUpdating()
+{
+  return (FUpdating > 0);
+}

+ 12 - 6
source/windows/TerminalManager.h

@@ -25,6 +25,7 @@ public:
   TSessionData * StateData;
   TObject * LocalExplorerState;
   TObject * RemoteExplorerState;
+  TObject * OtherLocalExplorerState;
   TDateTime ReopenStart;
   TDateTime DirectoryLoaded;
   TTerminalThread * TerminalThread;
@@ -48,6 +49,7 @@ public:
   __fastcall ~TTerminalManager();
 
   TManagedTerminal * __fastcall NewManagedTerminal(TSessionData * Data);
+  TManagedTerminal * __fastcall NewLocalBrowser();
   TManagedTerminal * __fastcall NewTerminals(TList * DataList);
   virtual void __fastcall FreeTerminal(TTerminal * Terminal);
   void __fastcall Move(TTerminal * Source, TTerminal * Target);
@@ -61,11 +63,11 @@ public:
   void __fastcall UpdateAppTitle();
   bool __fastcall CanOpenInPutty();
   void __fastcall OpenInPutty();
-  void __fastcall NewSession(const UnicodeString & SessionUrl, bool ReloadSessions = true, TForm * LinkedForm = NULL);
+  void __fastcall NewSession(
+    const UnicodeString & SessionUrl, bool ReloadSessions = true, TForm * LinkedForm = NULL, bool ReplaceExisting = false);
   void __fastcall Idle(bool SkipCurrentTerminal);
-  UnicodeString __fastcall GetTerminalShortPath(TTerminal * Terminal);
-  UnicodeString __fastcall GetTerminalTitle(TTerminal * Terminal, bool Unique);
-  UnicodeString __fastcall GetActiveTerminalTitle(bool Unique);
+  UnicodeString __fastcall GetSessionTitle(TManagedTerminal * Terminal, bool Unique);
+  UnicodeString __fastcall GetActiveSessionAppTitle();
   UnicodeString __fastcall GetAppProgressTitle();
   UnicodeString __fastcall FormatFormCaptionWithSession(TCustomForm * Form, const UnicodeString & Caption);
   void __fastcall HandleException(Exception * E);
@@ -75,6 +77,7 @@ public:
   TTerminal * __fastcall FindActiveTerminalForSite(TSessionData * Data);
   TTerminalQueue * __fastcall FindQueueForTerminal(TTerminal * Terminal);
   bool __fastcall UploadPublicKey(TTerminal * Terminal, TSessionData * Data, UnicodeString & FileName);
+  UnicodeString GetPathForSessionTabName(const UnicodeString & Result);
 
   __property TCustomScpExplorerForm * ScpExplorer = { read = FScpExplorer, write = SetScpExplorer };
   __property TManagedTerminal * ActiveSession = { read = FActiveSession, write = SetActiveSession };
@@ -84,6 +87,7 @@ public:
   __property TStrings * SessionList = { read = GetSessionList };
   __property TTerminal * LocalTerminal = { read = FLocalTerminal };
   __property TManagedTerminal * Sessions[int Index]  = { read = GetSession };
+  __property bool Updating = { read = IsUpdating };
 
 protected:
   virtual TTerminal * __fastcall CreateTerminal(TSessionData * Data);
@@ -116,12 +120,13 @@ private:
   bool FAuthenticationCancelled;
   std::unique_ptr<TApplicationEvents> FApplicationsEvents;
   bool FKeepAuthenticateForm;
+  int FUpdating;
 
   bool __fastcall ConnectActiveTerminalImpl(bool Reopen);
   bool __fastcall ConnectActiveTerminal();
   TTerminalQueue * __fastcall NewQueue(TTerminal * Terminal);
   void __fastcall SetScpExplorer(TCustomScpExplorerForm * value);
-  void __fastcall DoSetActiveSession(TManagedTerminal * value, bool AutoReconnect);
+  void __fastcall DoSetActiveSession(TManagedTerminal * value, bool AutoReconnect, bool LastTerminalClosed);
   void __fastcall SetActiveSession(TManagedTerminal * value);
   TManagedTerminal * GetActiveTerminal();
   void __fastcall UpdateAll();
@@ -148,7 +153,7 @@ private:
     TTerminal * Terminal, const UnicodeString & Str, bool Status, int Phase, const UnicodeString & Additional);
   void __fastcall TerminalCustomCommand(TTerminal * Terminal, const UnicodeString & Command, bool & Handled);
   void __fastcall FreeAll();
-  void __fastcall TerminalReady();
+  void __fastcall SessionReady();
   TStrings * __fastcall GetSessionList();
   int __fastcall GetActiveSessionIndex();
   TTerminalQueue * __fastcall GetActiveQueue();
@@ -185,6 +190,7 @@ private:
     TTerminal * Terminal, const UnicodeString & EntryType, const UnicodeString & FileName, bool & WrongRights);
   TManagedTerminal * __fastcall CreateManagedTerminal(TSessionData * Data);
   TManagedTerminal * __fastcall GetSession(int Index);
+  bool IsUpdating();
 };
 //---------------------------------------------------------------------------
 #endif

+ 3 - 3
source/windows/WinMain.cpp

@@ -73,17 +73,17 @@ void __fastcall GetLoginData(UnicodeString SessionName, TOptions * Options,
     DataList->Clear();
   }
   else if ((DataList->Count == 0) ||
-      !dynamic_cast<TSessionData *>(DataList->Items[0])->CanLogin ||
+      !dynamic_cast<TSessionData *>(DataList->Items[0])->CanOpen ||
       DefaultsOnly)
   {
-    // Note that GetFolderOrWorkspace never returns sites that !CanLogin,
+    // Note that GetFolderOrWorkspace never returns sites that !CanOpen,
     // so we should not get here with more than one site.
     // Though we should be good, if we ever do.
 
     // We get here when:
     // - we need session for explicit command-line operation
     // - after we handle "save" URL.
-    // - the specified session does not contain enough information to login [= not even hostname]
+    // - the specified session does not contain enough information to open [= not even hostname nor local browser]
 
     DebugAssert(DataList->Count <= 1);
     if (!DoLoginDialog(DataList, LinkedForm))