Files

887 lines
32 KiB
C#

using FluentTerminal.App.Services;
using FluentTerminal.App.Services.Utilities;
using FluentTerminal.Models;
using FluentTerminal.Models.Enums;
using Newtonsoft.Json;
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Windows.UI.Core;
using FluentTerminal.Models.Messages;
using System.Windows.Input;
using FluentTerminal.App.ViewModels.Menu;
using System.Collections.Generic;
using Microsoft.Toolkit.Mvvm.ComponentModel;
using Microsoft.Toolkit.Mvvm.Messaging;
using Microsoft.Toolkit.Mvvm.Input;
namespace FluentTerminal.App.ViewModels
{
public class TerminalViewModel : ObservableObject,
IRecipient<ApplicationSettingsChangedMessage>,
IRecipient<CurrentThemeChangedMessage>,
IRecipient<TerminalOptionsChangedMessage>,
IRecipient<KeyBindingsChangedMessage>
{
#region Static
private static readonly Regex SshTitleRx = new Regex(@"^\w+@\S+:", RegexOptions.Compiled);
#endregion Static
#region Serialize terminal state
private class TerminalState
{
public bool HasCustomTitle { get; set; }
public string ShellTitle { get; set; }
public string TabTitle { get; set; }
public TerminalTheme TerminalTheme { get; set; }
public int TabThemeId { get; set; }
public bool ShowSearchPanel { get; set; }
public string SearchText { get; set; }
public bool SearchMatchCase { get; set; }
public bool SearchWholeWord { get; set; }
public bool SearchWithRegex { get; set; }
public string XtermBufferState { get; set; }
public byte TerminalId { get; set; }
public ShellProfile ShellProfile { get; set; }
public int FontSize { get; set; }
}
public async Task<string> SerializeAsync()
{
TerminalState state = new TerminalState
{
HasCustomTitle = _hasCustomTitle,
ShellTitle = ShellTitle,
TabTitle = TabTitle,
TerminalTheme = TerminalTheme,
TabThemeId = TabTheme.Theme.Id,
ShowSearchPanel = ShowSearchPanel,
SearchText = SearchText,
SearchMatchCase = SearchMatchCase,
SearchWholeWord = SearchWholeWord,
SearchWithRegex = SearchWithRegex,
XtermBufferState = await SerializeXtermStateAsync().ConfigureAwait(false),
TerminalId = Terminal.Id,
ShellProfile = ShellProfile,
FontSize = FontSize
};
return JsonConvert.SerializeObject(state);
}
public void Restore(string data)
{
TerminalState state = JsonConvert.DeserializeObject<TerminalState>(data);
if (state != null)
{
_hasCustomTitle = state.HasCustomTitle;
ShellTitle = state.ShellTitle;
TabTitle = state.TabTitle;
TerminalTheme = state.TerminalTheme;
TabTheme = TabThemes.FirstOrDefault(t => t.Theme.Id == state.TabThemeId);
ShowSearchPanel = state.ShowSearchPanel;
SearchText = state.SearchText;
SearchMatchCase = state.SearchMatchCase;
SearchWholeWord = state.SearchWholeWord;
SearchWithRegex = state.SearchWithRegex;
XtermBufferState = state.XtermBufferState;
_terminalId = state.TerminalId;
ShellProfile = state.ShellProfile;
FontSize = state.FontSize;
TabTheme.IsSelected = true;
}
}
#endregion Serialize terminal state
private readonly IKeyboardCommandService _keyboardCommandService;
private bool _isSelected;
private bool _isHovered;
private bool _hasNewOutput;
private bool _hasExitedWithError;
private string _searchText;
private bool _showSearchPanel;
private bool _searchMatchCase;
private bool _searchWholeWord;
private bool _searchWithRegex;
private TabThemeViewModel _tabTheme;
private TerminalTheme _terminalTheme;
private TerminalOptions _terminalOptions;
private string _tabTitle;
private string _shellTitle;
private bool _hasCustomTitle;
private byte? _terminalId;
private bool _hasSelection;
private string _hoveredUri;
private MenuViewModel _contextMenu;
private MenuViewModel _tabContextMenu;
private readonly TabThemeViewModel _transparentTabThemeViewModel;
private int _fontSize;
public TerminalViewModel(ISettingsService settingsService, ITrayProcessCommunicationService trayProcessCommunicationService, IDialogService dialogService,
IKeyboardCommandService keyboardCommandService, ApplicationSettings applicationSettings, ShellProfile shellProfile,
IApplicationView applicationView, IClipboardService clipboardService, string terminalState = null)
{
SettingsService = settingsService;
_terminalOptions = SettingsService.GetTerminalOptions();
TrayProcessCommunicationService = trayProcessCommunicationService;
DialogService = dialogService;
_keyboardCommandService = keyboardCommandService;
ApplicationSettings = applicationSettings;
ApplicationView = applicationView;
ClipboardService = clipboardService;
ShellProfile = shellProfile;
TerminalTheme = shellProfile.TerminalThemeId == Guid.Empty ? SettingsService.GetCurrentTheme() : SettingsService.GetTheme(shellProfile.TerminalThemeId);
TabThemes = new ObservableCollection<TabThemeViewModel>(SettingsService.GetTabThemes().Select(theme => new TabThemeViewModel(theme, this)));
CloseCommand = new AsyncRelayCommand(TryCloseAsync, CanExecuteCommand);
CloseLeftTabsCommand = new RelayCommand(CloseLeftTabs, CanExecuteCommand);
CloseRightTabsCommand = new RelayCommand(CloseRightTabs, CanExecuteCommand);
CloseOtherTabsCommand = new RelayCommand(CloseOtherTabs, CanExecuteCommand);
FindNextCommand = new RelayCommand(FindNext, CanExecuteCommand);
FindPreviousCommand = new RelayCommand(FindPrevious, CanExecuteCommand);
CloseSearchPanelCommand = new RelayCommand(CloseSearchPanel, CanExecuteCommand);
EditTitleCommand = new AsyncRelayCommand(EditTitleAsync, CanExecuteCommand);
DuplicateTabCommand = new RelayCommand(DuplicateTab, CanExecuteCommand);
ReconnectTabCommand = new AsyncRelayCommand(ReconnectTabAsync, () => CanExecuteCommand() && HasExitedWithError);
CopyCommand = new AsyncRelayCommand(Copy, () => HasSelection);
PasteCommand = new AsyncRelayCommand(Paste);
CopyLinkCommand = new AsyncRelayCommand(() => CopyTextAsync(HoveredUri), () => !string.IsNullOrWhiteSpace(HoveredUri));
ShowSearchPanelCommand = new RelayCommand(() => ShowSearchPanel = true, () => !ShowSearchPanel);
FontSize = _terminalOptions.FontSize;
if (!string.IsNullOrEmpty(terminalState))
{
Restore(terminalState);
}
else
{
var defaultTabTheme = TabThemes.FirstOrDefault(t => t.Theme.Id == ShellProfile.TabThemeId);
defaultTabTheme.IsSelected = true;
}
_transparentTabThemeViewModel = TabThemes.FirstOrDefault(t => t.Theme.Id == 0);
Terminal = new Terminal(TrayProcessCommunicationService, _terminalId);
Terminal.KeyboardCommandReceived += Terminal_KeyboardCommandReceived;
Terminal.OutputReceived += Terminal_OutputReceived;
Terminal.SizeChanged += Terminal_SizeChanged;
Terminal.TitleChanged += Terminal_TitleChanged;
Terminal.Exited += Terminal_Exited;
Terminal.Closed += Terminal_Closed;
ContextMenu = BuidContextMenu();
TabContextMenu = BuildTabContextMenu();
WeakReferenceMessenger.Default.RegisterAll(this);
}
public MenuViewModel ContextMenu
{
get => _contextMenu;
set => SetProperty(ref _contextMenu, value);
}
public MenuViewModel TabContextMenu
{
get => _tabContextMenu;
set => SetProperty(ref _tabContextMenu, value);
}
private bool _disposalRequested;
public void DisposalPrepare()
{
_disposalRequested = true;
CloseCommand = null;
TerminalView.DisposalPrepare();
TerminalView = null;
Terminal = null;
Overlay = null;
}
public event EventHandler Activated;
public event EventHandler Closed;
public event EventHandler<SearchRequest> FindNextRequested;
public event EventHandler<SearchRequest> FindPreviousRequested;
public event EventHandler SearchStarted;
public event EventHandler<TerminalTheme> ThemeChanged;
public event EventHandler CloseLeftTabsRequested;
public event EventHandler CloseRightTabsRequested;
public event EventHandler CloseOtherTabsRequested;
public event EventHandler DuplicateTabRequested;
public event EventHandler<int> FontSizeChanged;
public ApplicationSettings ApplicationSettings { get; private set; }
public IApplicationView ApplicationView { get; }
public TabThemeViewModel BackgroundTabTheme
{
// The effective background theme depends on whether it is selected (use the theme), or if it is inactive
// (if we're set to underline inactive tabs, use the null theme).
get => IsSelected || (!IsSelected && ApplicationSettings.InactiveTabColorMode == InactiveTabColorMode.Background) ?
TabTheme :
_transparentTabThemeViewModel;
}
public IClipboardService ClipboardService { get; }
public string XtermBufferState { get; private set; }
public ICommand CloseCommand { get; private set; }
public ICommand CloseRightTabsCommand { get; }
public ICommand CloseLeftTabsCommand { get; }
public ICommand CloseOtherTabsCommand { get; }
public ICommand CloseSearchPanelCommand { get; }
public IDialogService DialogService { get; }
public ICommand EditTitleCommand { get; }
public ICommand FindNextCommand { get; }
public ICommand FindPreviousCommand { get; }
public ICommand DuplicateTabCommand { get; }
public AsyncRelayCommand ReconnectTabCommand { get; }
public ICommand CopyLinkCommand { get; private set; }
public ICommand CopyCommand { get; private set; }
public ICommand PasteCommand { get; private set; }
public ICommand ShowSearchPanelCommand { get; private set; }
public double BackgroundOpacity => _terminalOptions?.BackgroundOpacity ?? 1.0;
public bool IsSelected
{
get => _isSelected;
set
{
if (SetProperty(ref _isSelected, value))
{
if (IsSelected)
{
_keyboardCommandService.RegisterCommandHandler(nameof(Command.Search), () => ShowSearchPanel = true);
_keyboardCommandService.RegisterCommandHandler(nameof(Command.CloseSearch), () => CloseSearchPanel());
HasNewOutput = false;
}
else
{
_keyboardCommandService.DeregisterCommandHandler(nameof(Command.Search));
_keyboardCommandService.DeregisterCommandHandler(nameof(Command.CloseSearch));
}
OnPropertyChanged(nameof(IsUnderlined));
OnPropertyChanged(nameof(BackgroundTabTheme));
OnPropertyChanged(nameof(ShowCloseButton));
}
}
}
public bool HasSelection
{
get => _hasSelection;
set => SetProperty(ref _hasSelection, value);
}
public string HoveredUri
{
get => _hoveredUri;
set
{
if (SetProperty(ref _hoveredUri, value))
{
ContextMenu = BuidContextMenu();
}
}
}
public bool IsHovered
{
get => _isHovered;
set
{
if (SetProperty(ref _isHovered, value))
{
OnPropertyChanged(nameof(ShowCloseButton));
}
}
}
public bool ShowCloseButton
{
get => IsHovered || IsSelected;
}
public bool IsUnderlined => (IsSelected && ApplicationSettings.UnderlineSelectedTab) ||
(!IsSelected && ApplicationSettings.InactiveTabColorMode == InactiveTabColorMode.Underlined && TabTheme.Theme.Color != null);
public bool HasNewOutput
{
get => _hasNewOutput;
set => SetProperty(ref _hasNewOutput, value);
}
public bool HasExitedWithError
{
get => _hasExitedWithError;
set
{
if (SetProperty(ref _hasExitedWithError, value) && value)
{
HasNewOutput = false;
}
ReconnectTabCommand.NotifyCanExecuteChanged();
}
}
public string SearchText
{
get => _searchText;
set => SetProperty(ref _searchText, value);
}
public bool SearchMatchCase
{
get => _searchMatchCase;
set => SetProperty(ref _searchMatchCase, value);
}
public bool SearchWholeWord
{
get => _searchWholeWord;
set => SetProperty(ref _searchWholeWord, value);
}
public bool SearchWithRegex
{
get => _searchWithRegex;
set => SetProperty(ref _searchWithRegex, value);
}
public ISettingsService SettingsService { get; }
public ShellProfile ShellProfile { get; private set; }
public bool ShowSearchPanel
{
get => _showSearchPanel;
set => SetProperty(ref _showSearchPanel, value);
}
public bool SearchHasFocus { get; set; }
public TabThemeViewModel TabTheme
{
get => _tabTheme;
set
{
SetProperty(ref _tabTheme, value);
OnPropertyChanged(nameof(IsUnderlined));
OnPropertyChanged(nameof(BackgroundTabTheme));
// necessary to update this constantly so that duplicated tabs will also carry over the color
ShellProfile.TabThemeId = value.Theme.Id;
}
}
public ObservableCollection<TabThemeViewModel> TabThemes { get; }
public Terminal Terminal { get; private set; }
public OverlayViewModel Overlay { get; set; }
public TerminalTheme TerminalTheme
{
get => _terminalTheme;
set => SetProperty(ref _terminalTheme, value);
}
public bool Initialized { get; set; }
public string TabTitle
{
get => _tabTitle;
set
{
var title = value?.Trim() ?? string.Empty;
if (ShellProfile is SshProfile)
{
// For SshProfile we are adjusting title.
if (title.Equals("ssh", StringComparison.OrdinalIgnoreCase) ||
title.EndsWith("\\ssh.exe", StringComparison.OrdinalIgnoreCase))
{
title = $"[ssh] {I18N.Translate("Authenticate")}";
}
else if (title.Equals("mosh", StringComparison.OrdinalIgnoreCase) ||
title.EndsWith("\\mosh.exe", StringComparison.OrdinalIgnoreCase))
{
title = $"[mosh] {I18N.Translate("Authenticate")}";
}
else if (SshTitleRx.IsMatch(title))
{
title = $"[ssh] {title}";
}
}
SetProperty(ref _tabTitle, title);
ShellProfile.Name = title;
}
}
public string ShellTitle
{
get => _shellTitle;
set
{
if (SetProperty(ref _shellTitle, value) && !_hasCustomTitle)
{
TabTitle = value;
}
}
}
public int FontSize
{
get => _fontSize;
set
{
_fontSize = value > 0 ? value : 1;
}
}
public ITrayProcessCommunicationService TrayProcessCommunicationService { get; }
public Task CloseAsync()
{
//MessengerInstance.Unregister(this); // todo necessary?
return Terminal.CloseAsync();
}
public Task CopyTextAsync(string text)
{
return ApplicationView.ExecuteOnUiThreadAsync(() =>
{
ClipboardService.SetText(text);
if (ApplicationSettings.ShowTextCopied)
{
Overlay.Show(I18N.Translate("TextCopied"));
}
});
}
// Requires UI thread
public async Task EditTitleAsync()
{
// ConfigureAwait(true) because we're setting some view-model properties afterwards.
var result = await DialogService.ShowInputDialogAsync(I18N.Translate("EditTitleString"))
.ConfigureAwait(true);
if (result != null)
{
if (string.IsNullOrWhiteSpace(result))
{
_hasCustomTitle = false;
TabTitle = ShellTitle;
}
else
{
_hasCustomTitle = true;
TabTitle = result;
}
}
}
public void FocusTerminal()
{
Activated?.Invoke(this, EventArgs.Empty);
}
private void CloseSearchPanel()
{
SearchText = string.Empty;
ShowSearchPanel = false;
FocusTerminal();
}
private bool CanExecuteCommand<T>(T a)
{
return CanExecuteCommand();
}
private bool CanExecuteCommand()
{
return Initialized && !_disposalRequested;
}
private void CloseLeftTabs()
{
CloseLeftTabsRequested?.Invoke(this, EventArgs.Empty);
}
private void CloseRightTabs()
{
CloseRightTabsRequested?.Invoke(this, EventArgs.Empty);
}
private void CloseOtherTabs()
{
CloseOtherTabsRequested?.Invoke(this, EventArgs.Empty);
}
private void FindNext()
{
FindNextRequested?.Invoke(this, new SearchRequest { MatchCase = SearchMatchCase, Regex = SearchWithRegex, Term = SearchText, WholeWord = SearchWholeWord });
}
public ITerminalView TerminalView { get; set; }
private Task<string> SerializeXtermStateAsync()
{
return TerminalView?.SerializeXtermStateAsync() ?? Task.FromResult(string.Empty);
}
private void FindPrevious()
{
FindPreviousRequested?.Invoke(this, new SearchRequest { MatchCase = SearchMatchCase, Regex = SearchWithRegex, Term = SearchText, WholeWord = SearchWholeWord });
}
private void DuplicateTab()
{
DuplicateTabRequested?.Invoke(this, EventArgs.Empty);
}
public async Task ReconnectTabAsync()
{
HasExitedWithError = false;
await TerminalView?.ReconnectAsync();
}
public void Receive(ApplicationSettingsChangedMessage message)
{
ApplicationSettings = message.ApplicationSettings;
ApplicationView.ExecuteOnUiThreadAsync(() =>
{
OnPropertyChanged(nameof(IsUnderlined));
OnPropertyChanged(nameof(BackgroundTabTheme));
});
}
public void Receive(CurrentThemeChangedMessage message)
{
// only change theme if not overwritten by profile
if (ShellProfile.TerminalThemeId == Guid.Empty)
{
var currentTheme = SettingsService.GetTheme(message.ThemeId);
ApplicationView.ExecuteOnUiThreadAsync(() =>
{
TerminalTheme = currentTheme;
ThemeChanged?.Invoke(this, currentTheme);
});
}
}
public void Receive(TerminalOptionsChangedMessage message)
{
_terminalOptions = message.TerminalOptions;
ApplicationView.ExecuteOnUiThreadAsync(() => OnPropertyChanged(nameof(BackgroundOpacity)), CoreDispatcherPriority.Low);
}
public void Receive(KeyBindingsChangedMessage message)
{
ApplicationView.ExecuteOnUiThreadAsync(() =>
{
var contextMenu = BuidContextMenu();
if (!contextMenu.EquivalentTo(ContextMenu))
{
ContextMenu = contextMenu;
}
var tabContextMenu = BuildTabContextMenu();
if (!tabContextMenu.EquivalentTo(TabContextMenu))
{
TabContextMenu = tabContextMenu;
}
}, CoreDispatcherPriority.Low, true);
}
private void Terminal_Exited(object sender, int exitCode)
{
if (ShellProfile?.Tag is ISessionSuccessTracker tracker && exitCode != 0)
{
tracker.SetInvalid();
}
ApplicationView.ExecuteOnUiThreadAsync(() => HasExitedWithError = exitCode > 0);
}
private void Terminal_Closed(object sender, EventArgs e)
{
Closed?.Invoke(this, EventArgs.Empty);
Terminal.KeyboardCommandReceived -= Terminal_KeyboardCommandReceived;
Terminal.OutputReceived -= Terminal_OutputReceived;
Terminal.SizeChanged -= Terminal_SizeChanged;
Terminal.TitleChanged -= Terminal_TitleChanged;
Terminal.Exited -= Terminal_Exited;
Terminal.Closed -= Terminal_Closed;
}
private async void Terminal_KeyboardCommandReceived(object sender, string e)
{
switch (e)
{
case nameof(Command.Copy):
{
await Copy().ConfigureAwait(false);
return;
}
case nameof(Command.Paste):
{
await Paste().ConfigureAwait(false);
return;
}
case nameof(Command.PasteWithoutNewlines):
{
string content = await ClipboardService.GetTextAsync().ConfigureAwait(false);
if (content != null)
{
content = ShellProfile.NewlinePattern.Replace(content, string.Empty);
TerminalView.Paste(content);
}
return;
}
case nameof(Command.Search):
{
await ApplicationView.ExecuteOnUiThreadAsync(() =>
{
ShowSearchPanel = true;
SearchStarted?.Invoke(this, EventArgs.Empty);
}).ConfigureAwait(false);
return;
}
case nameof(Command.CloseSearch):
{
await ApplicationView.ExecuteOnUiThreadAsync(() =>
{
CloseSearchPanel();
}).ConfigureAwait(false);
return;
}
case nameof(Command.IncreaseFontSize):
{
FontSize++;
FontSizeChanged?.Invoke(this, FontSize);
return;
}
case nameof(Command.DecreaseFontSize):
{
if (FontSize > 2)
{
FontSize--;
FontSizeChanged?.Invoke(this, FontSize);
}
return;
}
case nameof(Command.ResetFontSize):
{
FontSize = _terminalOptions.FontSize;
FontSizeChanged?.Invoke(this, FontSize);
return;
}
default:
{
await ApplicationView.ExecuteOnUiThreadAsync(() => _keyboardCommandService.SendCommand(e))
.ConfigureAwait(false);
return;
}
}
}
private void Terminal_OutputReceived(object sender, byte[] e)
{
if (ShellProfile?.Tag is ISessionSuccessTracker tracker)
{
tracker.SetOutputReceived();
}
if (!IsSelected && ApplicationSettings.ShowNewOutputIndicator)
{
ApplicationView.ExecuteOnUiThreadAsync(() => HasNewOutput = true);
}
}
private void Terminal_SizeChanged(object sender, TerminalSize e)
{
ApplicationView.ExecuteOnUiThreadAsync(() => Overlay.Show($"{e.Columns} x {e.Rows}"));
}
private void Terminal_TitleChanged(object sender, string e)
{
if (e == Terminal.Profile.Location) return;
ApplicationView.ExecuteOnUiThreadAsync(() => ShellTitle = e);
}
private async Task TryCloseAsync()
{
if (ShellProfile?.Tag is ISessionSuccessTracker tracker)
{
tracker.SetInvalid();
}
if (!ApplicationSettings.ConfirmClosingTabs || await DialogService
.ShowMessageDialogAsync(I18N.Translate("PleaseConfirm"),
string.Format(I18N.Translate("ConfirmCloseTab"), ShellTitle), DialogButton.OK,
DialogButton.Cancel).ConfigureAwait(false) == DialogButton.OK)
{
await CloseAsync().ConfigureAwait(false);
}
}
private async Task Copy()
{
var selection = await Terminal.GetSelectedText().ConfigureAwait(false);
await CopyTextAsync(selection).ConfigureAwait(false);
}
private async Task Paste()
{
// prevent from pasting something into the terminal window when the actual command is executed
// while being in the search box
if (!SearchHasFocus)
{
var content = await ClipboardService.GetTextAsync().ConfigureAwait(false);
if (content != null)
{
TerminalView.Paste(content);
}
}
}
private MenuViewModel BuidContextMenu()
{
var commandKeyBindings = SettingsService.GetCommandKeyBindings();
var contextMenu = new MenuViewModel();
if (!string.IsNullOrWhiteSpace(HoveredUri))
{
var copyLink = new MenuItemViewModel(I18N.Translate("CopyLink"), CopyLinkCommand, icon: Mdl2Icon.Link());
contextMenu.Items.Add(copyLink);
contextMenu.Items.Add(new SeparatorMenuItemViewModel());
}
var copy = new MenuItemViewModel(I18N.Translate("Command.Copy"), CopyCommand, icon: Mdl2Icon.Copy());
AddKeyBindings(copy, Command.Copy, commandKeyBindings);
contextMenu.Items.Add(copy);
var paste = new MenuItemViewModel(I18N.Translate("Command.Paste"), PasteCommand, icon: Mdl2Icon.Paste());
AddKeyBindings(paste, Command.Paste, commandKeyBindings);
contextMenu.Items.Add(paste);
contextMenu.Items.Add(new SeparatorMenuItemViewModel());
var editTitle = new MenuItemViewModel(I18N.Translate("EditTitle.Text"), EditTitleCommand, icon: Mdl2Icon.Edit());
AddKeyBindings(editTitle, Command.ChangeTabTitle, commandKeyBindings);
contextMenu.Items.Add(editTitle);
var search = new MenuItemViewModel(I18N.Translate("Search.Text"), ShowSearchPanelCommand, icon: Mdl2Icon.Search());
AddKeyBindings(search, Command.Search, commandKeyBindings);
contextMenu.Items.Add(search);
contextMenu.Items.Add(new SeparatorMenuItemViewModel());
var duplicate = new MenuItemViewModel(I18N.Translate("Command.DuplicateTab"), DuplicateTabCommand);
AddKeyBindings(duplicate, Command.DuplicateTab, commandKeyBindings);
contextMenu.Items.Add(duplicate);
contextMenu.Items.Add(new SeparatorMenuItemViewModel());
var close = new MenuItemViewModel(I18N.Translate("Close"), CloseCommand, icon: Mdl2Icon.Cancel());
AddKeyBindings(close, Command.CloseTab, commandKeyBindings);
contextMenu.Items.Add(close);
return contextMenu;
}
private MenuViewModel BuildTabContextMenu()
{
var commandKeyBindings = SettingsService.GetCommandKeyBindings();
var contextMenu = new MenuViewModel();
RadioMenuItemViewModel createTabColorItem(TabThemeViewModel tabTheme)
{
//todo localise name
var icon = tabTheme.Theme.Id == 0 ? Mdl2Icon.PaginationDotOutline10() : Mdl2Icon.PaginationDotSolid10(tabTheme.Theme.Color);
return new RadioMenuItemViewModel(I18N.Translate($"{tabTheme.Theme.Name}"), $"{Terminal.Id}_tabTheme", tabTheme, bindingPath: nameof(TabThemeViewModel.IsSelected) , icon: icon);
}
var tabColor = new ExpandableMenuItemViewModel(I18N.Translate("Color.Text"));
foreach (var tabTheme in TabThemes)
{
tabColor.SubItems.Add(createTabColorItem(tabTheme));
}
contextMenu.Items.Add(tabColor);
var editTitle = new MenuItemViewModel(I18N.Translate("EditTitle.Text"), EditTitleCommand, icon: Mdl2Icon.Edit());
AddKeyBindings(editTitle, Command.ChangeTabTitle, commandKeyBindings);
contextMenu.Items.Add(editTitle);
contextMenu.Items.Add(new SeparatorMenuItemViewModel());
var duplicate = new MenuItemViewModel(I18N.Translate("Command.DuplicateTab"), DuplicateTabCommand);
AddKeyBindings(duplicate, Command.DuplicateTab, commandKeyBindings);
contextMenu.Items.Add(duplicate);
contextMenu.Items.Add(new SeparatorMenuItemViewModel());
var closeLeft = new MenuItemViewModel(I18N.Translate("CloseLeft.Text"), CloseLeftTabsCommand);
contextMenu.Items.Add(closeLeft);
var closeRight = new MenuItemViewModel(I18N.Translate("CloseRight.Text"), CloseRightTabsCommand);
contextMenu.Items.Add(closeRight);
var closeOther = new MenuItemViewModel(I18N.Translate("CloseOther.Text"), CloseOtherTabsCommand);
contextMenu.Items.Add(closeOther);
var close = new MenuItemViewModel(I18N.Translate("Close"), CloseCommand, icon: Mdl2Icon.Cancel());
AddKeyBindings(close, Command.CloseTab, commandKeyBindings);
contextMenu.Items.Add(close);
return contextMenu;
}
private void AddKeyBindings(MenuItemViewModel menuItem, Command command, IDictionary<string, ICollection<KeyBinding>> commandKeyBindings)
{
var keyBindings = commandKeyBindings[command.ToString()];
if (keyBindings?.Any() == true)
{
menuItem.KeyBinding = new MenuItemKeyBindingViewModel(keyBindings.First());
}
}
}
}