text: // Copyright (c) Microsoft Corporation.
  // Licensed under the MIT license.
  
  #include "pch.h"
  #include "App.h"
  #include <shellapi.h>
  #include <filesystem>
  #include <winrt/Microsoft.UI.Xaml.XamlTypeInfo.h>
  
  using namespace winrt::Windows::ApplicationModel::DataTransfer;
  using namespace winrt::Windows::UI::Xaml;
  using namespace winrt::Windows::UI::Core;
  using namespace winrt::Windows::System;
  using namespace winrt::Microsoft::Terminal;
  using namespace winrt::Microsoft::Terminal::Settings;
  using namespace winrt::Microsoft::Terminal::TerminalControl;
  using namespace ::TerminalApp;
  
  // Note: Generate GUID using TlgGuid.exe tool
  TRACELOGGING_DEFINE_PROVIDER(
      g_hTerminalAppProvider,
      "Microsoft.Windows.Terminal.App",
      // {24a1622f-7da7-5c77-3303-d850bd1ab2ed}
      (0x24a1622f, 0x7da7, 0x5c77, 0x33, 0x03, 0xd8, 0x50, 0xbd, 0x1a, 0xb2, 0xed),
      TraceLoggingOptionMicrosoftTelemetry());
  
  namespace winrt
  {
      namespace MUX = Microsoft::UI::Xaml;
      using IInspectable = Windows::Foundation::IInspectable;
  }
  
  namespace winrt::TerminalApp::implementation
  {
  
      App::App() :
          App(winrt::TerminalApp::XamlMetaDataProvider())
      {
      }
  
      App::App(Windows::UI::Xaml::Markup::IXamlMetadataProvider const& parentProvider) :
          base_type(parentProvider),
          _settings{  },
          _tabs{  },
          _loadedInitialSettings{ false }
      {
          // For your own sanity, it's better to do setup outside the ctor.
          // If you do any setup in the ctor that ends up throwing an exception,
          // then it might look like App just failed to activate, which will
          // cause you to chase down the rabbit hole of "why is App not
          // registered?" when it definitely is.
      }
  
      // Method Description:
      // - Build the UI for the terminal app. Before this method is called, it
      //   should not be assumed that the TerminalApp is usable. The Settings
      //   should be loaded before this is called, either with LoadSettings or
      //   GetLaunchDimensions (which will call LoadSettings)
      // Arguments:
      // - <none>
      // Return Value:
      // - <none>
      void App::Create()
      {
          // Assert that we've already loaded our settings. We have to do
          // this as a MTA, before the app is Create()'d
          WINRT_ASSERT(_loadedInitialSettings);
          TraceLoggingRegister(g_hTerminalAppProvider);
          _Create();
      }
  
      App::~App()
      {
          TraceLoggingUnregister(g_hTerminalAppProvider);
      }
  
      // Method Description:
      // - Create all of the initial UI elements of the Terminal app.
      //    * Creates the tab bar, initially hidden.
      //    * Creates the tab content area, which is where we'll display the tabs/panes.
      //    * Initializes the first terminal control, using the default profile,
      //      and adds it to our list of tabs.
      void App::_Create()
      {
          _tabView = MUX::Controls::TabView{};
  
          _tabView.SelectionChanged({ this, &App::_OnTabSelectionChanged });
          _tabView.TabClosing({ this, &App::_OnTabClosing });
          _tabView.Items().VectorChanged({ this, &App::_OnTabItemsChanged });
  
          _root = Controls::Grid{};
  
          _tabRow = Controls::Grid{};
          _tabRow.Name(L"Tab Row");
          _tabContent = Controls::Grid{};
          _tabContent.Name(L"Tab Content");
  
          // Set up two columns in the tabs row - one for the tabs themselves, and
          // another for the settings button.
          auto tabsColDef = Controls::ColumnDefinition();
          auto newTabBtnColDef = Controls::ColumnDefinition();
  
          newTabBtnColDef.Width(GridLengthHelper::Auto());
  
          _tabRow.ColumnDefinitions().Append(tabsColDef);
          _tabRow.ColumnDefinitions().Append(newTabBtnColDef);
  
          // Set up two rows - one for the tabs, the other for the tab content,
          // the terminal panes.
          auto tabBarRowDef = Controls::RowDefinition();
          tabBarRowDef.Height(GridLengthHelper::Auto());
          _root.RowDefinitions().Append(tabBarRowDef);
          _root.RowDefinitions().Append(Controls::RowDefinition{});
  
          if (_settings->GlobalSettings().GetShowTabsInTitlebar() == false)
          {
              _root.Children().Append(_tabRow);
              Controls::Grid::SetRow(_tabRow, 0);
          }
          _root.Children().Append(_tabContent);
          Controls::Grid::SetRow(_tabContent, 1);
          Controls::Grid::SetColumn(_tabView, 0);
  
          // Create the new tab button.
          _newTabButton = Controls::SplitButton{};
          Controls::SymbolIcon newTabIco{};
          newTabIco.Symbol(Controls::Symbol::Add);
          _newTabButton.Content(newTabIco);
          Controls::Grid::SetRow(_newTabButton, 0);
          Controls::Grid::SetColumn(_newTabButton, 1);
          _newTabButton.VerticalAlignment(VerticalAlignment::Stretch);
          _newTabButton.HorizontalAlignment(HorizontalAlignment::Left);
  
          // When the new tab button is clicked, open the default profile
          _newTabButton.Click([this](auto&&, auto&&){
              this->_OpenNewTab(std::nullopt);
          });
  
          // Populate the new tab button's flyout with entries for each profile
          _CreateNewTabFlyout();
  
          _tabRow.Children().Append(_tabView);
          _tabRow.Children().Append(_newTabButton);
  
          _tabContent.VerticalAlignment(VerticalAlignment::Stretch);
          _tabContent.HorizontalAlignment(HorizontalAlignment::Stretch);
  
          // Here, we're doing the equivalent of defining the _tabRow as the
          // following: <Grid Background="{ThemeResource
          // ApplicationPageBackgroundThemeBrush}"> We need to set the Background
          // to that ThemeResource, so it'll be colored appropriately regardless
          // of what theme the user has selected.
          // We're looking up the Style we've defined in App.xaml, and applying it
          // here. A ResourceDictionary is a Map<IInspectable, IInspectable>, so
          // you'll need to try_as to get the type we actually want.
          auto res = Resources();
          IInspectable key = winrt::box_value(L"BackgroundGridThemeStyle");
          if (res.HasKey(key))
          {
              IInspectable g = res.Lookup(key);
              winrt::Windows::UI::Xaml::Style style = g.try_as<winrt::Windows::UI::Xaml::Style>();
              _tabRow.Style(style);
          }
  
          // Apply the UI theme from our settings to our UI elements
          _ApplyTheme(_settings->GlobalSettings().GetRequestedTheme());
  
          _OpenNewTab(std::nullopt);
      }
  
      // Method Description:
      // - Get the size in pixels of the client area we'll need to launch this
      //   terminal app. This method will use the default profile's settings to do
      //   this calculation, as well as the _system_ dpi scaling. See also
      //   TermControl::GetProposedDimensions.
      // Arguments:
      // - <none>
      // Return Value:
      // - a point containing the requested dimensions in pixels.
      winrt::Windows::Foundation::Point App::GetLaunchDimensions(uint32_t dpi)
      {
          if (!_loadedInitialSettings)
          {
              // Load settings if we haven't already
              LoadSettings();
          }
  
          // Use the default profile to determine how big of a window we need.
          TerminalSettings settings = _settings->MakeSettings(std::nullopt);
  
          // TODO MSFT:21150597 - If the global setting "Always show tab bar" is
          // set, then we'll need to add the height of the tab bar here.
  
          return TermControl::GetProposedDimensions(settings, dpi);
      }
  
  
      bool App::GetShowTabsInTitlebar()
      {
          if (!_loadedInitialSettings)
          {
              // Load settings if we haven't already
              LoadSettings();
          }
  
          return _settings->GlobalSettings().GetShowTabsInTitlebar();
      }
  
      // Method Description:
      // - Builds the flyout (dropdown) attached to the new tab button, and
      //   attaches it to the button. Populates the flyout with one entry per
      //   Profile, displaying the profile's name. Clicking each flyout item will
      //   open a new tab with that profile.
      //   Below the profiles are the static menu items: settings, feedback
      void App::_CreateNewTabFlyout()
      {
          auto newTabFlyout = Controls::MenuFlyout{};
          for (int profileIndex = 0; profileIndex < _settings->GetProfiles().size(); profileIndex++)
          {
              const auto& profile = _settings->GetProfiles()[profileIndex];
              auto profileMenuItem = Controls::MenuFlyoutItem{};
  
              auto profileName = profile.GetName();
              winrt::hstring hName{ profileName };
              profileMenuItem.Text(hName);
  
              // If there's an icon set for this profile, set it as the icon for
              // this flyout item.
              if (profile.HasIcon())
              {
                  profileMenuItem.Icon(_GetIconFromProfile(profile));
              }
  
              profileMenuItem.Click([this, profileIndex](auto&&, auto&&){
                  this->_OpenNewTab({ profileIndex });
              });
              newTabFlyout.Items().Append(profileMenuItem);
          }
  
          // add menu separator
          auto separatorItem = Controls::MenuFlyoutSeparator{};
          newTabFlyout.Items().Append(separatorItem);
  
          // add static items
          {
              // Create the settings button.
              auto settingsItem = Controls::MenuFlyoutItem{};
              settingsItem.Text(L"Settings");
  
              Controls::SymbolIcon ico{};
              ico.Symbol(Controls::Symbol::Setting);
              settingsItem.Icon(ico);
  
              settingsItem.Click({ this, &App::_SettingsButtonOnClick });
              newTabFlyout.Items().Append(settingsItem);
  
              // Create the feedback button.
              auto feedbackFlyout = Controls::MenuFlyoutItem{};
              feedbackFlyout.Text(L"Feedback");
  
              Controls::FontIcon feedbackIco{};
              feedbackIco.Glyph(L"\xE939");
              feedbackIco.FontFamily(Media::FontFamily{ L"Segoe MDL2 Assets" });
              feedbackFlyout.Icon(feedbackIco);
  
              feedbackFlyout.Click({ this, &App::_FeedbackButtonOnClick });
              newTabFlyout.Items().Append(feedbackFlyout);
          }
  
          _newTabButton.Flyout(newTabFlyout);
      }
  
      // Function Description:
      // - Called when the settings button is clicked. ShellExecutes the settings
      //   file, as to open it in the default editor for .json files. Does this in
      //   a background thread, as to not hang/crash the UI thread.
      fire_and_forget LaunchSettings()
      {
          // This will switch the execution of the function to a background (not
          // UI) thread. This is IMPORTANT, because the Windows.Storage API's
          // (used for retrieving the path to the file) will crash on the UI
          // thread, because the main thread is a STA.
          co_await winrt::resume_background();
  
          const auto settingsPath = CascadiaSettings::GetSettingsPath();
          ShellExecute(nullptr, L"open", settingsPath.c_str(), nullptr, nullptr, SW_SHOW);
      }
  
      // Method Description:
      // - Called when the settings button is clicked. Launches a background
      //   thread to open the settings file in the default JSON editor.
      // Arguments:
      // - <none>
      // Return Value:
      // - <none>
      void App::_SettingsButtonOnClick(const IInspectable&,
                                           const RoutedEventArgs&)
      {
          LaunchSettings();
      }
  
      // Method Description:
      // - Called when the feedback button is clicked. Launches the feedback hub
      //   to the list of all feedback for the Terminal app.
      void App::_FeedbackButtonOnClick(const IInspectable&,
                                       const RoutedEventArgs&)
      {
          // If you want this to go to the new feedback page automatically, use &newFeedback=true
          winrt::Windows::System::Launcher::LaunchUriAsync({ L"feedback-hub://?tabid=2&appid=Microsoft.WindowsTerminal_8wekyb3d8bbwe!App" });
  
      }
  
      // Method Description:
      // - Register our event handlers with the given keybindings object. This
      //   should be done regardless of what the events are actually bound to -
      //   this simply ensures the AppKeyBindings object will call us correctly
      //   for each event.
      // Arguments:
      // - bindings: A AppKeyBindings object to wire up with our event handlers
      void App::_HookupKeyBindings(TerminalApp::AppKeyBindings bindings) noexcept
      {
          // Hook up the KeyBinding object's events to our handlers.
          // They should all be hooked up here, regardless of whether or not
          //      there's an actual keychord for them.
          bindings.NewTab([this]() { _OpenNewTab(std::nullopt); });
          bindings.CloseTab([this]() { _CloseFocusedTab(); });
          bindings.NewTabWithProfile([this](const auto index) { _OpenNewTab({ index }); });
          bindings.ScrollUp([this]() { _DoScroll(-1); });
          bindings.ScrollDown([this]() { _DoScroll(1); });
          bindings.NextTab([this]() { _SelectNextTab(true); });
          bindings.PrevTab([this]() { _SelectNextTab(false); });
          bindings.SwitchToTab([this](const auto index) { _SelectTab({ index }); });
          bindings.OpenSettings([this]() { _OpenSettings(); });
      }
  
      // Method Description:
      // - Initialized our settings. See CascadiaSettings for more details.
      //      Additionally hooks up our callbacks for keybinding events to the
      //      keybindings object.
      // NOTE: This must be called from a MTA if we're running as a packaged
      //      application. The Windows.Storage APIs require a MTA. If this isn't
      //      happening during startup, it'll need to happen on a background thread.
      void App::LoadSettings()
      {
          _settings = CascadiaSettings::LoadAll();
  
          _HookupKeyBindings(_settings->GetKeybindings());
  
          _loadedInitialSettings = true;
  
          // Register for directory change notification.
          _RegisterSettingsChange();
      }
  
      // Method Description:
      // - Registers for changes to the settings folder and upon a updated settings
      //      profile calls _ReloadSettings().
      // Arguments:
      // - <none>
      // Return Value:
      // - <none>
      void App::_RegisterSettingsChange()
      {
          // Make sure this hstring has a stack-local reference. If we don't it
          // might get cleaned up before we parse the path.
          const auto localPathCopy = CascadiaSettings::GetSettingsPath();
  
          // Getting the containing folder.
          std::filesystem::path fileParser = localPathCopy.c_str();
          const auto folder = fileParser.parent_path();
  
          _reader.create(folder.c_str(), false, wil::FolderChangeEvents::All,
              [this](wil::FolderChangeEvent event, PCWSTR fileModified)
          {
              // We want file modifications, AND when files are renamed to be
              // profiles.json. This second case will oftentimes happen with text
              // editors, who will write a temp file, then rename it to be the
              // actual file you wrote. So listen for that too.
              if (!(event == wil::FolderChangeEvent::Modified ||
                    event == wil::FolderChangeEvent::RenameNewName))
              {
                  return;
              }
  
              const auto localPathCopy = CascadiaSettings::GetSettingsPath();
              std::filesystem::path settingsParser = localPathCopy.c_str();
              std::filesystem::path modifiedParser = fileModified;
  
              // Getting basename (filename.ext)
              const auto settingsBasename = settingsParser.filename();
              const auto modifiedBasename = modifiedParser.filename();
  
              if (settingsBasename == modifiedBasename)
              {
                  this->_ReloadSettings();
              }
          });
      }
  
      // Method Description:
      // - Reloads the settings from the profile.json.
      // Arguments:
      // - <none>
      // Return Value:
      // - <none>
      void App::_ReloadSettings()
      {
          _settings = CascadiaSettings::LoadAll();
          // Re-wire the keybindings to their handlers, as we'll have created a
          // new AppKeyBindings object.
          _HookupKeyBindings(_settings->GetKeybindings());
  
          auto profiles = _settings->GetProfiles();
  
          for (auto &profile : profiles)
          {
              const GUID profileGuid = profile.GetGuid();
              TerminalSettings settings = _settings->MakeSettings(profileGuid);
  
              for (auto &tab : _tabs)
              {
                  const auto term = tab->GetTerminalControl();
                  const GUID tabProfile = tab->GetProfile();
  
                  if (profileGuid == tabProfile)
                  {
                      term.UpdateSettings(settings);
  
                      // Update the icons of the tabs with this profile open.
                      auto tabViewItem = tab->GetTabViewItem();
                      tabViewItem.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [profile, tabViewItem]() {
                          // _GetIconFromProfile has to run on the main thread
                          tabViewItem.Icon(App::_GetIconFromProfile(profile));
                      });
                  }
              }
          }
  
  
          _root.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [this]() {
              // Refresh the UI theme
              _ApplyTheme(_settings->GlobalSettings().GetRequestedTheme());
  
              // repopulate the new tab button's flyout with entries for each
              // profile, which might have changed
              _CreateNewTabFlyout();
          });
  
      }
  
      // Method Description:
      // - Update the current theme of the application. This will manually update
      //   all of the elements in our UI to match the given theme.
      // Arguments:
      // - newTheme: The ElementTheme to apply to our elements.
      void App::_ApplyTheme(const Windows::UI::Xaml::ElementTheme& newTheme)
      {
          _root.RequestedTheme(newTheme);
          _tabRow.RequestedTheme(newTheme);
      }
  
      UIElement App::GetRoot() noexcept
      {
          return _root;
      }
  
      UIElement App::GetTabs() noexcept
      {
          return _tabRow;
      }
  
      void App::_SetFocusedTabIndex(int tabIndex)
      {
          auto tab = _tabs.at(tabIndex);
          _tabView.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [tab, this](){
              auto tabViewItem = tab->GetTabViewItem();
              _tabView.SelectedItem(tabViewItem);
          });
      }
  
      // Method Description:
      // - Handle changes in tab layout.
      void App::_UpdateTabView()
      {
          // Show tabs when there's more than 1, or the user has chosen to always
          // show the tab bar.
          const bool isVisible = _settings->GlobalSettings().GetShowTabsInTitlebar() ||
                                 (_tabs.size() > 1) ||
                                 _settings->GlobalSettings().GetAlwaysShowTabs();
  
          // collapse/show the tabs themselves
          _tabView.Visibility(isVisible ? Visibility::Visible : Visibility::Collapsed);
  
          // collapse/show the row that the tabs are in.
          // NaN is the special value XAML uses for "Auto" sizing.
          _tabRow.Height(isVisible ? NAN : 0);
      }
  
      // Method Description:
      // - Open a new tab. This will create the TerminalControl hosting the
      //      terminal, and add a new Tab to our list of tabs. The method can
      //      optionally be provided a profile index, which will be used to create
      //      a tab using the profile in that index.
      //      If no index is provided, the default profile will be used.
      // Arguments:
      // - profileIndex: an optional index into the list of profiles to use to
      //      initialize this tab up with.
      void App::_OpenNewTab(std::optional<int> profileIndex)
      {
          GUID profileGuid;
  
          if (profileIndex)
          {
              const auto realIndex = profileIndex.value();
              const auto profiles = _settings->GetProfiles();
  
              // If we don't have that many profiles, then do nothing.
              if (realIndex >= profiles.size())
              {
                  return;
              }
  
              const auto& selectedProfile = profiles[realIndex];
              profileGuid = selectedProfile.GetGuid();
          }
          else
          {
              // Getting Guid for default profile
              const auto globalSettings = _settings->GlobalSettings();
              profileGuid = globalSettings.GetDefaultProfile();
          }
  
          TerminalSettings settings = _settings->MakeSettings(profileGuid);
          _CreateNewTabFromSettings(profileGuid, settings);
  
          const int tabCount = static_cast<int>(_tabs.size());
          TraceLoggingWrite(
              g_hTerminalAppProvider, // handle to TerminalApp tracelogging provider
              "TabInformation",
              TraceLoggingDescription("Event emitted upon new tab creation in TerminalApp"),
              TraceLoggingInt32(tabCount, "TabCount", "Count of tabs curently opened in TerminalApp"),
              TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
              TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance));
      }
  
      // Function Description:
      // - Copies and processes the text data from the Windows Clipboard.
      //   Does some of this in a background thread, as to not hang/crash the UI thread.
      // Arguments:
      // - eventArgs: the PasteFromClipboard event sent from the TermControl
      fire_and_forget PasteFromClipboard(PasteFromClipboardEventArgs eventArgs)
      {
          const DataPackageView data = Clipboard::GetContent();
  
          // This will switch the execution of the function to a background (not
          // UI) thread. This is IMPORTANT, because the getting the clipboard data
          // will crash on the UI thread, because the main thread is a STA.
          co_await winrt::resume_background();
  
          hstring text = L"";
          if (data.Contains(StandardDataFormats::Text()))
          {
              text = co_await data.GetTextAsync();
          }
          eventArgs.HandleClipboardData(text);
      }
  
      // Method Description:
      // - Creates a new tab with the given settings. If the tab bar is not being
      //      currently displayed, it will be shown.
      // Arguments:
      // - settings: the TerminalSettings object to use to create the TerminalControl with.
      void App::_CreateNewTabFromSettings(GUID profileGuid, TerminalSettings settings)
      {
          // Initialize the new tab
          TermControl term{ settings };
  
          // Add an event handler when the terminal's selection wants to be copied.
          // When the text buffer data is retrieved, we'll copy the data into the Clipboard
          term.CopyToClipboard([=](auto copiedData) {
              _root.Dispatcher().RunAsync(CoreDispatcherPriority::High, [copiedData]() {
                  DataPackage dataPack = DataPackage();
                  dataPack.RequestedOperation(DataPackageOperation::Copy);
                  dataPack.SetText(copiedData);
                  Clipboard::SetContent(dataPack);
  
                  // TODO: MSFT 20642290 and 20642291
                  // rtf copy and html copy
              });
          });
  
          // Add an event handler when the terminal wants to paste data from the Clipboard.
          term.PasteFromClipboard([=](auto /*sender*/, auto eventArgs) {
              _root.Dispatcher().RunAsync(CoreDispatcherPriority::High, [eventArgs]() {
                  PasteFromClipboard(eventArgs);
              });
          });
  
          // Add the new tab to the list of our tabs.
          auto newTab = _tabs.emplace_back(std::make_shared<Tab>(profileGuid, term));
  
          // Add an event handler when the terminal's title changes. When the
          // title changes, we'll bubble it up to listeners of our own title
          // changed event, so they can handle it.
          newTab->GetTerminalControl().TitleChanged([=](auto newTitle){
              // Only bubble the change if this tab is the focused tab.
              if (_settings->GlobalSettings().GetShowTitleInTitlebar() &&
                  newTab->IsFocused())
              {
                  _titleChangeHandlers(newTitle);
              }
          });
  
          auto tabViewItem = newTab->GetTabViewItem();
          _tabView.Items().Append(tabViewItem);
  
          const auto* const profile = _settings->FindProfile(profileGuid);
  
          // Set this profile's tab to the icon the user specified
          if (profile != nullptr && profile->HasIcon())
          {
              tabViewItem.Icon(_GetIconFromProfile(*profile));
          }
  
          // Add an event handler when the terminal's connection is closed.
          newTab->GetTerminalControl().ConnectionClosed([=]() {
              _tabView.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [newTab, tabViewItem, this]() {
                  const GUID tabProfile = newTab->GetProfile();
                  // Don't just capture this pointer, because the profile might
                  // get destroyed before this is called (case in point -
                  // reloading settings)
                  const auto* const p = _settings->FindProfile(tabProfile);
  
                  if (p != nullptr && p->GetCloseOnExit())
                  {
                      _RemoveTabViewItem(tabViewItem);
                  }
              });
          });
  
          tabViewItem.PointerPressed({ this, &App::_OnTabClick });
  
          // This is one way to set the tab's selected background color.
          //   tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackgroundSelected"), a Brush?);
  
          // This kicks off TabView::SelectionChanged, in response to which we'll attach the terminal's
          // Xaml control to the Xaml root.
          _tabView.SelectedItem(tabViewItem);
      }
  
      // Method Description:
      // - Returns the index in our list of tabs of the currently focused tab. If
      //      no tab is currently selected, returns -1.
      // Return Value:
      // - the index of the currently focused tab if there is one, else -1
      int App::_GetFocusedTabIndex() const
      {
          return _tabView.SelectedIndex();
      }
  
      void App::_OpenSettings()
      {
          LaunchSettings();
      }
  
      // Method Description:
      // - Close the currently focused tab. Focus will move to the left, if possible.
      void App::_CloseFocusedTab()
      {
          int focusedTabIndex = _GetFocusedTabIndex();
          std::shared_ptr<Tab> focusedTab{ _tabs[focusedTabIndex] };
          _RemoveTabViewItem(focusedTab->GetTabViewItem());
      }
  
      // Method Description:
      // - Move the viewport of the terminal of the currently focused tab up or
      //      down a number of lines. Negative values of `delta` will move the
      //      view up, and positive values will move the viewport down.
      // Arguments:
      // - delta: a number of lines to move the viewport relative to the current viewport.
      void App::_DoScroll(int delta)
      {
          int focusedTabIndex = _GetFocusedTabIndex();
          _tabs[focusedTabIndex]->Scroll(delta);
      }
  
      // Method Description:
      // - Copy text from the focused terminal to the Windows Clipboard
      // Arguments:
      // - trimTrailingWhitespace: enable removing any whitespace from copied selection
      //    and get text to appear on separate lines.
      void App::_CopyText(const bool trimTrailingWhitespace)
      {
          const int focusedTabIndex = _GetFocusedTabIndex();
          std::shared_ptr<Tab> focusedTab{ _tabs[focusedTabIndex] };
  
          const auto control = focusedTab->GetTerminalControl();
          control.CopySelectionToClipboard(trimTrailingWhitespace);
      }
  
      // Method Description:
      // - Sets focus to the tab to the right or left the currently selected tab.
      void App::_SelectNextTab(const bool bMoveRight)
      {
          int focusedTabIndex = _GetFocusedTabIndex();
          auto tabCount = _tabs.size();
          // Wraparound math. By adding tabCount and then calculating modulo tabCount,
          // we clamp the values to the range [0, tabCount) while still supporting moving
          // leftward from 0 to tabCount - 1.
          _SetFocusedTabIndex(
              static_cast<int>((tabCount + focusedTabIndex + (bMoveRight ? 1 : -1)) % tabCount)
          );
      }
  
      // Method Description:
      // - Sets focus to the desired tab.
      void App::_SelectTab(const int tabIndex)
      {
          if (tabIndex >= 0 && tabIndex < _tabs.size())
          {
              _SetFocusedTabIndex(tabIndex);
          }
      }
  
      // Method Description:
      // - Responds to the TabView control's Selection Changed event (to move a
      //      new terminal control into focus.)
      // Arguments:
      // - sender: the control that originated this event
      // - eventArgs: the event's constituent arguments
      void App::_OnTabSelectionChanged(const IInspectable& sender, const Controls::SelectionChangedEventArgs& eventArgs)
      {
          auto tabView = sender.as<MUX::Controls::TabView>();
          auto selectedIndex = tabView.SelectedIndex();
  
          // Unfocus all the tabs.
          for (auto tab : _tabs)
          {
              tab->SetFocused(false);
          }
  
          if (selectedIndex >= 0)
          {
              try
              {
                  auto tab = _tabs.at(selectedIndex);
                  auto control = tab->GetTerminalControl().GetControl();
  
                  _tabContent.Children().Clear();
                  _tabContent.Children().Append(control);
  
                  tab->SetFocused(true);
                  _titleChangeHandlers(GetTitle());
              }
              CATCH_LOG();
          }
      }
  
      // Method Description:
      // - Responds to the TabView control's Tab Closing event by removing
      //      the indicated tab from the set and focusing another one.
      //      The event is cancelled so App maintains control over the
      //      items in the tabview.
      // Arguments:
      // - sender: the control that originated this event
      // - eventArgs: the event's constituent arguments
      void App::_OnTabClosing(const IInspectable& sender, const MUX::Controls::TabViewTabClosingEventArgs& eventArgs)
      {
          const auto tabViewItem = eventArgs.Item();
          _RemoveTabViewItem(tabViewItem);
  
          // If we don't cancel the event, the TabView will remove the item itself.
          eventArgs.Cancel(true);
      }
  
      // Method Description:
      // - Responds to changes in the TabView's item list by changing the tabview's
      //      visibility.
      // Arguments:
      // - sender: the control that originated this event
      // - eventArgs: the event's constituent arguments
      void App::_OnTabItemsChanged(const IInspectable& sender, const Windows::Foundation::Collections::IVectorChangedEventArgs& eventArgs)
      {
          _UpdateTabView();
      }
  
      // Method Description:
      // - Gets the title of the currently focused terminal control. If there
      //   isn't a control selected for any reason, returns "Windows Terminal"
      // Arguments:
      // - <none>
      // Return Value:
      // - the title of the focused control if there is one, else "Windows Terminal"
      hstring App::GetTitle()
      {
          if (_settings->GlobalSettings().GetShowTitleInTitlebar())
          {
              auto selectedIndex = _tabView.SelectedIndex();
              if (selectedIndex >= 0)
              {
                  try
                  {
                      auto tab = _tabs.at(selectedIndex);
                      return tab->GetTerminalControl().Title();
                  }
                  CATCH_LOG();
              }
          }
          return { L"Windows Terminal" };
      }
  
      // Method Description:
      // - Additional responses to clicking on a TabView's item. Currently, just remove tab with middle click
      // Arguments:
      // - sender: the control that originated this event (TabViewItem)
      // - eventArgs: the event's constituent arguments
      void App::_OnTabClick(const IInspectable& sender, const Windows::UI::Xaml::Input::PointerRoutedEventArgs& eventArgs)
      {
          if (eventArgs.GetCurrentPoint(_root).Properties().IsMiddleButtonPressed())
          {
              _RemoveTabViewItem(sender);
              eventArgs.Handled(true);
          }
      }
  
      // Method Description:
      // - Removes the tab (both TerminalControl and XAML)
      // Arguments:
      // - tabViewItem: the TabViewItem in the TabView that is being removed.
      void App::_RemoveTabViewItem(const IInspectable& tabViewItem)
      {
          // TODO: GitHub:627: Need a better story for what should happen when the last tab is closed.
          if (_tabs.size() <= 1)
          {
              return;
          }
  
          uint32_t tabIndexFromControl = 0;
          _tabView.Items().IndexOf(tabViewItem, tabIndexFromControl);
  
          if (tabIndexFromControl == _GetFocusedTabIndex())
          {
              _tabView.SelectedIndex((tabIndexFromControl > 0) ? tabIndexFromControl - 1 : 1);
          }
  
          // Removing the tab from the collection will destroy its control and disconnect its connection.
          _tabs.erase(_tabs.begin() + tabIndexFromControl);
          _tabView.Items().RemoveAt(tabIndexFromControl);
  
          // ensure tabs and focus is sync
          _tabView.SelectedIndex(tabIndexFromControl > 0 ? tabIndexFromControl - 1 : 0);
      }
  
      // Method Description:
      // - Gets a colored IconElement for the profile in question. If the profile
      //   has an `icon` set in the settings, this will return an icon with that
      //   image in it. Otherwise it will return a nullptr-initialized
      //   IconElement.
      // Arguments:
      // - profile: the profile to get the icon from
      // Return Value:
      // - an IconElement for the profile's icon, if it has one.
      Controls::IconElement App::_GetIconFromProfile(const Profile& profile)
      {
          if (profile.HasIcon())
          {
              auto path = profile.GetIconPath();
              winrt::hstring iconPath{ path };
              winrt::Windows::Foundation::Uri iconUri{ iconPath };
              Controls::BitmapIconSource iconSource;
              // Make sure to set this to false, so we keep the RGB data of the
              // image. Otherwise, the icon will be white for all the
              // non-transparent pixels in the image.
              iconSource.ShowAsMonochrome(false);
              iconSource.UriSource(iconUri);
              Controls::IconSourceElement elem;
              elem.IconSource(iconSource);
              return elem;
          }
          else
          {
              return { nullptr };
          }
      }
  
      // -------------------------------- WinRT Events ---------------------------------
      // Winrt events need a method for adding a callback to the event and removing the callback.
      // These macros will define them both for you.
      DEFINE_EVENT(App, TitleChanged, _titleChangeHandlers, TerminalControl::TitleChangedEventArgs);
  }
  