Compare commits

...
20 Commits
Author SHA1 Message Date
FelixSe 0a4a1baeba increase version 2018-07-19 21:59:19 +02:00
FelixSe 14d111c4de Added mouse settings for copy&paste 2018-07-19 21:22:47 +02:00
FelixSe db73d7e1cb Fixed orphaned terminals on window close 2018-07-19 18:54:52 +02:00
FelixSe 825e9883d7 increase version 2018-07-19 12:39:04 +02:00
Felix b2866c34df Update README.md 2018-07-19 12:08:34 +02:00
Felix 58e3ab9115 Use AppService for Commnunication instead of WebSocket (#55)
* Use AppService for Commnunication instead of WebSocket

* Code cleanup

* Some polishing

* Added some tests
2018-07-19 12:05:02 +02:00
Felix 2b66b3c87f Update README.md 2018-07-18 21:14:11 +02:00
FelixSe 4505504732 Refactored paste to be compatible with xterm 3.5 2018-07-17 21:55:10 +02:00
FelixSe 567da9b0a3 Update xterm to 3.5.1 2018-07-17 20:44:45 +02:00
FelixSe 7ccc36b121 Added option to display tabs on the bottom of the window 2018-07-08 20:47:26 +02:00
Felix 559bf9d394 Improved scrolling behavior of the tabs bar (#54) 2018-07-08 18:28:16 +02:00
FelixSe cee34dfb63 Show title of selected tab in windows task bar 2018-07-06 23:56:24 +02:00
Felix 55995bd7a9 Update README.md 2018-07-02 22:43:28 +02:00
Felix 36edfddf74 Tests for Services (#52) 2018-07-01 13:54:14 +02:00
FelixSe 188046e60a Fixed invisible text when background color was used as text color (e.g. micro editor) 2018-06-25 21:48:55 +02:00
FelixSe 5338f3cae8 fix reversed blue/red colors in ubuntu theme 2018-06-25 21:40:40 +02:00
Felix 624146ecd7 Update README.md 2018-06-25 07:46:31 +02:00
Mike 8f711d1a9e Update readme to address issues with loopback exemption (#51)
* Update README for installation instructions

Changed from requiring developer mode to demonstrate to install when sideloading the app. Including the Powershell snippet necessary to enable the loopback exemption for the package family that is FluentTerminal.

* Updated notes on developer mode

* Linking to issue 43 directly.
2018-06-24 22:45:20 +02:00
Felix b50090060b Update README.md 2018-06-24 12:59:26 +02:00
DF 7db2321a82 Add notification for new version (#46)
* Add notification for new version

* fix spelling error

* Fix review comments
2018-06-23 17:30:47 +02:00
147 changed files with 3227 additions and 913 deletions

No files matched your search

+1
View File
@@ -2,6 +2,7 @@
FluentTerminal.App/Win32
FluentTerminal.App/Client/
FluentTerminal.Client/dist
/.sonarqube
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
@@ -0,0 +1,157 @@
using FluentAssertions;
using FluentTerminal.App.Services.Implementation;
using FluentTerminal.Models.Enums;
using System.Collections.Generic;
using System.Linq;
using Xunit;
namespace FluentTerminal.App.Services.Test
{
public class DefaultValueProviderTests
{
public static IEnumerable<object[]> Commands => System.Enum.GetValues(typeof(Command)).Cast<Command>().Select(x => new object[] { x });
[Fact]
public void GetDefaultApplicationSettings_Default_DoesNotReturnNull()
{
var defaultValueProvider = new DefaultValueProvider();
var applicationSettings = defaultValueProvider.GetDefaultApplicationSettings();
applicationSettings.Should().NotBeNull();
}
[Theory]
[MemberData(nameof(Commands))]
public void GetDefaultKeyBindings_AllCommands_ReturnsANonEmptyList(Command command)
{
var defaultValueProvider = new DefaultValueProvider();
var keyBindings = defaultValueProvider.GetDefaultKeyBindings(command);
keyBindings.Should().NotBeNullOrEmpty();
}
[Fact]
public void GetDefaultShellProfileId_Default_DoesNotReturnEmptyGuid()
{
var defaultValueProvider = new DefaultValueProvider();
var defaultShellProfileId = defaultValueProvider.GetDefaultShellProfileId();
defaultShellProfileId.Should().NotBeEmpty();
}
[Fact]
public void GetDefaultShellProfileId_Default_IdIsOneOfThePreInstalledShellProfiles()
{
var defaultValueProvider = new DefaultValueProvider();
var shellProfiles = defaultValueProvider.GetPreinstalledShellProfiles();
var defaultShellProfileId = defaultValueProvider.GetDefaultShellProfileId();
shellProfiles.Select(x => x.Id).Should().Contain(defaultShellProfileId);
}
[Fact]
public void GetDefaultTerminalOptions_Default_DoesNotReturnNull()
{
var defaultValueProvider = new DefaultValueProvider();
var defaultTerminalOptions = defaultValueProvider.GetDefaultTerminalOptions();
defaultTerminalOptions.Should().NotBeNull();
}
[Fact]
public void GetDefaultThemeId_Default_DoesNotReturnEmptyGuid()
{
var defaultValueProvider = new DefaultValueProvider();
var defaultThemeId = defaultValueProvider.GetDefaultThemeId();
defaultThemeId.Should().NotBeEmpty();
}
[Fact]
public void GetDefaultThemeId_Default_IdIsOneOfThePreInstalledThemes()
{
var defaultValueProvider = new DefaultValueProvider();
var themes = defaultValueProvider.GetPreInstalledThemes();
var defaultThemeId = defaultValueProvider.GetDefaultThemeId();
themes.Select(x => x.Id).Should().Contain(defaultThemeId);
}
[Fact]
public void GetPreinstalledShellProfiles_Default_ReturnsANonEmptyList()
{
var defaultValueProvider = new DefaultValueProvider();
var shellProfiles = defaultValueProvider.GetPreinstalledShellProfiles();
shellProfiles.Should().NotBeNullOrEmpty();
}
[Fact]
public void GetPreinstalledShellProfiles_Default_HaveUniqueIds()
{
var defaultValueProvider = new DefaultValueProvider();
var shellProfiles = defaultValueProvider.GetPreinstalledShellProfiles();
shellProfiles.Select(x => x.Id).Should().OnlyHaveUniqueItems();
}
[Fact]
public void GetPreinstalledShellProfiles_Default_HavePreInstalledSetToTrue()
{
var defaultValueProvider = new DefaultValueProvider();
var shellProfiles = defaultValueProvider.GetPreinstalledShellProfiles();
shellProfiles.Select(x => x.PreInstalled).Should().AllBeEquivalentTo(true);
}
[Fact]
public void GetPreinstalledShellProfiles_Default_HaveNonEmptyLocations()
{
var defaultValueProvider = new DefaultValueProvider();
var shellProfiles = defaultValueProvider.GetPreinstalledShellProfiles();
shellProfiles.Select(x => x.Location).Should().NotBeNullOrEmpty();
}
[Fact]
public void GetPreInstalledThemes_Default_ReturnsANonEmptyList()
{
var defaultValueProvider = new DefaultValueProvider();
var themes = defaultValueProvider.GetPreInstalledThemes();
themes.Should().NotBeNullOrEmpty();
}
[Fact]
public void GetPreinstalledThemes_Default_HaveUniqueIds()
{
var defaultValueProvider = new DefaultValueProvider();
var themes = defaultValueProvider.GetPreInstalledThemes();
themes.Select(x => x.Id).Should().OnlyHaveUniqueItems();
}
[Fact]
public void GetPreinstalledThemes_Default_HavePreInstalledSetToTrue()
{
var defaultValueProvider = new DefaultValueProvider();
var themes = defaultValueProvider.GetPreInstalledThemes();
themes.Select(x => x.PreInstalled).Should().AllBeEquivalentTo(true);
}
}
}
@@ -1,11 +1,11 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using AutoFixture;
using AutoFixture;
using FluentAssertions;
using FluentTerminal.App.Services.Dialogs;
using FluentTerminal.App.Services.Implementation;
using Moq;
using System;
using System.Linq;
using System.Threading.Tasks;
using Xunit;
namespace FluentTerminal.App.Services.Test
@@ -20,41 +20,77 @@ namespace FluentTerminal.App.Services.Test
}
[Fact]
public void ShowDialogAsync_TitleIsEmpty_ThrowsArgumentNullException()
public void ShowCreateKeyBindingDialog_Default_UsesCreateKeyBindingDialog()
{
var createKeyBindingDialog = new Mock<ICreateKeyBindingDialog>();
var dialogService = new DialogService(Mock.Of<IShellProfileSelectionDialog>, Mock.Of<IMessageDialog>, () => createKeyBindingDialog.Object);
dialogService.ShowCreateKeyBindingDialog();
createKeyBindingDialog.Verify(x => x.CreateKeyBinding(), Times.Once);
}
[Fact]
public void ShowMessageDialogAsnyc_TitleIsEmpty_ThrowsArgumentNullException()
{
var title = string.Empty;
var content = _fixture.Create<string>();
var buttons = _fixture.CreateMany<DialogButton>(2);
var dialogService = new DialogService(() => Mock.Of<IShellProfileSelectionDialog>(), () => Mock.Of<IMessageDialog>(), () => Mock.Of<ICreateKeyBindingDialog>());
var dialogService = new DialogService(Mock.Of<IShellProfileSelectionDialog>, Mock.Of<IMessageDialog>, Mock.Of<ICreateKeyBindingDialog>);
Func<Task<DialogButton>> invoke = () => dialogService.ShowMessageDialogAsnyc(title, content, buttons.ElementAt(0), buttons.ElementAt(1));
Func<Task<DialogButton>> showMessageDialogAsnyc = () => dialogService.ShowMessageDialogAsnyc(title, content, buttons.ElementAt(0), buttons.ElementAt(1));
invoke.Should().Throw<ArgumentNullException>().And.ParamName.Should().Be("title");
showMessageDialogAsnyc.Should().Throw<ArgumentNullException>().And.ParamName.Should().Be("title");
}
[Fact]
public void ShowDialogAsync_ContentIsEmpty_ThrowsArgumentNullException()
public void ShowMessageDialogAsnyc_ContentIsEmpty_ThrowsArgumentNullException()
{
var title = _fixture.Create<string>();
var content = string.Empty;
var buttons = _fixture.CreateMany<DialogButton>(2);
var dialogService = new DialogService(() => Mock.Of<IShellProfileSelectionDialog>(), () => Mock.Of<IMessageDialog>(), () => Mock.Of<ICreateKeyBindingDialog>());
var dialogService = new DialogService(Mock.Of<IShellProfileSelectionDialog>, Mock.Of<IMessageDialog>, Mock.Of<ICreateKeyBindingDialog>);
Func<Task<DialogButton>> invoke = () => dialogService.ShowMessageDialogAsnyc(title, content, buttons.ElementAt(0), buttons.ElementAt(1));
Func<Task<DialogButton>> showMessageDialogAsnyc = () => dialogService.ShowMessageDialogAsnyc(title, content, buttons.ElementAt(0), buttons.ElementAt(1));
invoke.Should().Throw<ArgumentNullException>().And.ParamName.Should().Be("content");
showMessageDialogAsnyc.Should().Throw<ArgumentNullException>().And.ParamName.Should().Be("content");
}
[Fact]
public void ShowDialogAsync_NoButtonsPassed_ThrowsArgumentException()
public void ShowMessageDialogAsnyc_NoButtonsPassed_ThrowsArgumentException()
{
var title = _fixture.Create<string>();
var content = _fixture.Create<string>();
var dialogService = new DialogService(() => Mock.Of<IShellProfileSelectionDialog>(), () => Mock.Of<IMessageDialog>(), () => Mock.Of<ICreateKeyBindingDialog>());
var dialogService = new DialogService(Mock.Of<IShellProfileSelectionDialog>, Mock.Of<IMessageDialog>, Mock.Of<ICreateKeyBindingDialog>);
Func<Task<DialogButton>> invoke = () => dialogService.ShowMessageDialogAsnyc(title, content);
Func<Task<DialogButton>> showMessageDialogAsnyc = () => dialogService.ShowMessageDialogAsnyc(title, content);
invoke.Should().Throw<ArgumentException>().And.ParamName.Should().Be("buttons");
showMessageDialogAsnyc.Should().Throw<ArgumentException>().And.ParamName.Should().Be("buttons");
}
[Fact]
public void ShowMessageDialogAsnyc_Default_UsesMessageDialog()
{
var title = _fixture.Create<string>();
var content = _fixture.Create<string>();
var buttons = _fixture.CreateMany<DialogButton>(2);
var messageDialog = new Mock<IMessageDialog>();
var dialogService = new DialogService(Mock.Of<IShellProfileSelectionDialog>, () => messageDialog.Object, Mock.Of<ICreateKeyBindingDialog>);
dialogService.ShowMessageDialogAsnyc(title, content, buttons.ElementAt(0), buttons.ElementAt(1));
messageDialog.Verify(x => x.ShowAsync(), Times.Once);
}
[Fact]
public void ShowProfileSelectionDialogAsync_Default_UsesShellProfileSelectionDialog()
{
var shellProfileSelectionDialog = new Mock<IShellProfileSelectionDialog>();
var dialogService = new DialogService(() => shellProfileSelectionDialog.Object, Mock.Of<IMessageDialog>, Mock.Of<ICreateKeyBindingDialog>);
dialogService.ShowProfileSelectionDialogAsync();
shellProfileSelectionDialog.Verify(x => x.SelectProfile(), Times.Once);
}
}
}
}
@@ -6,16 +6,27 @@
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<None Remove="TestData\AdventureTime.itermcolors" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="AutoFixture" Version="4.4.0" />
<PackageReference Include="FluentAssertions" Version="5.4.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.7.0" />
<PackageReference Include="Moq" Version="4.8.3" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="xunit" Version="2.3.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
<DotNetCliToolReference Include="dotnet-xunit" Version="2.3.1" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="TestData\AdventureTime.itermcolors">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\FluentTerminal.App.Services\FluentTerminal.App.Services.csproj" />
</ItemGroup>
@@ -0,0 +1,80 @@
using AutoFixture;
using FluentAssertions;
using FluentTerminal.App.Services.Implementation;
using FluentTerminal.Models;
using Newtonsoft.Json;
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Xunit;
namespace FluentTerminal.App.Services.Test
{
public class FluentTerminalThemeParserTests
{
private readonly Fixture _fixture;
public FluentTerminalThemeParserTests()
{
_fixture = new Fixture();
}
[Fact]
public async Task Parse_ValidFileStream_ReturnsTheme()
{
var theme = _fixture.Create<TerminalTheme>();
var serialized = JsonConvert.SerializeObject(theme);
var stream = new MemoryStream(Encoding.UTF8.GetBytes(serialized));
var parser = new FluentTerminalThemeParser();
var parsedTheme = await parser.Parse(theme.Name, stream);
parsedTheme.Name.Should().Be(theme.Name);
parsedTheme.Author.Should().Be(theme.Author);
parsedTheme.PreInstalled.Should().Be(false);
parsedTheme.Colors.Should().BeEquivalentTo(theme.Colors);
}
[Fact]
public void Parse_InvalidFileStream_ThrowsException()
{
var fileName = _fixture.Create<string>();
var serialized = _fixture.Create<string>();
var stream = new MemoryStream(Encoding.UTF8.GetBytes(serialized));
var parser = new FluentTerminalThemeParser();
Func<Task<TerminalTheme>> parse = () => parser.Parse(fileName, stream);
parse.Should().Throw<Exception>();
}
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData(" ")]
public void Parse_InvalidFileName_ThrowsArgumentNullException(string fileName)
{
var theme = _fixture.Create<TerminalTheme>();
var serialized = JsonConvert.SerializeObject(theme);
var stream = new MemoryStream(Encoding.UTF8.GetBytes(serialized));
var parser = new FluentTerminalThemeParser();
Func<Task<TerminalTheme>> parse = () => parser.Parse(fileName, stream);
parse.Should().Throw<ArgumentNullException>().And.ParamName.Should().Be("fileName");
}
[Fact]
public void Parse_FileStreamIsNull_ThrowsArgumentNullException()
{
var fileName = _fixture.Create<string>();
Stream stream = null;
var parser = new FluentTerminalThemeParser();
Func<Task<TerminalTheme>> parse = () => parser.Parse(fileName, stream);
parse.Should().Throw<ArgumentNullException>().And.ParamName.Should().Be("fileContent");
}
}
}
@@ -0,0 +1,91 @@
using AutoFixture;
using FluentAssertions;
using FluentTerminal.App.Services.Implementation;
using FluentTerminal.Models;
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Xunit;
namespace FluentTerminal.App.Services.Test
{
public class ITermThemeParserTests
{
private readonly Fixture _fixture;
public ITermThemeParserTests()
{
_fixture = new Fixture();
}
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData(" ")]
public void Parse_InvalidFileName_ThrowsArgumentNullException(string fileName)
{
var stream = typeof(ITermThemeParserTests).Assembly.GetManifestResourceStream("FluentTerminal.App.Services.Test.TestData.AdventureTime.itermcolors");
var parser = new ITermThemeParser();
Func<Task<TerminalTheme>> parse = () => parser.Parse(fileName, stream);
parse.Should().Throw<ArgumentNullException>().And.ParamName.Should().Be("fileName");
}
[Fact]
public void Parse_FileStreamIsNull_ThrowsArgumentNullException()
{
var fileName = _fixture.Create<string>();
Stream stream = null;
var parser = new ITermThemeParser();
Func<Task<TerminalTheme>> parse = () => parser.Parse(fileName, stream);
parse.Should().Throw<ArgumentNullException>().And.ParamName.Should().Be("fileContent");
}
[Fact]
public void Parse_InvalidFileStream_ThrowsException()
{
var fileName = _fixture.Create<string>();
var serialized = _fixture.Create<string>();
var stream = new MemoryStream(Encoding.UTF8.GetBytes(serialized));
var parser = new ITermThemeParser();
Func<Task<TerminalTheme>> parse = () => parser.Parse(fileName, stream);
parse.Should().Throw<Exception>();
}
[Fact]
public async Task Parse_ValidThemeFile_ParsesCorrectly()
{
var fileName = _fixture.Create<string>();
var stream = typeof(ITermThemeParserTests).Assembly.GetManifestResourceStream("FluentTerminal.App.Services.Test.TestData.AdventureTime.itermcolors");
var parser = new ITermThemeParser();
var theme = await parser.Parse(fileName, stream);
theme.Colors.Background.ToLower().Should().Be("#1f1d45");
theme.Colors.Cursor.ToLower().Should().Be("#efbf38");
theme.Colors.Foreground.ToLower().Should().Be("#f8dcc0");
theme.Colors.Black.ToLower().Should().Be("#050404");
theme.Colors.Red.ToLower().Should().Be("#bd0013");
theme.Colors.Green.ToLower().Should().Be("#4ab118");
theme.Colors.Yellow.ToLower().Should().Be("#e7741e");
theme.Colors.Blue.ToLower().Should().Be("#0f4ac6");
theme.Colors.Magenta.ToLower().Should().Be("#665993");
theme.Colors.Cyan.ToLower().Should().Be("#70a598");
theme.Colors.White.ToLower().Should().Be("#f8dcc0");
theme.Colors.BrightBlack.ToLower().Should().Be("#4e7cbf");
theme.Colors.BrightRed.ToLower().Should().Be("#fc5f5a");
theme.Colors.BrightGreen.ToLower().Should().Be("#9eff6e");
theme.Colors.BrightYellow.ToLower().Should().Be("#efc11a");
theme.Colors.BrightBlue.ToLower().Should().Be("#1997c6");
theme.Colors.BrightMagenta.ToLower().Should().Be("#9b5953");
theme.Colors.BrightCyan.ToLower().Should().Be("#c8faf4");
theme.Colors.BrightWhite.ToLower().Should().Be("#f6f5fb");
}
}
}
@@ -66,4 +66,4 @@ namespace FluentTerminal.App.Services.Test
invoke.Should().Throw<KeyNotFoundException>();
}
}
}
}
@@ -0,0 +1,705 @@
using AutoFixture;
using FluentAssertions;
using FluentTerminal.App.Services.Implementation;
using FluentTerminal.Models;
using FluentTerminal.Models.Enums;
using Moq;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Xunit;
namespace FluentTerminal.App.Services.Test
{
public class SettingsServiceTests
{
private readonly Fixture _fixture;
public SettingsServiceTests()
{
_fixture = new Fixture();
}
[Fact]
public void Constructor_Default_WritesAllPreinstalledThemesInThemeContainer()
{
var themes = _fixture.CreateMany<TerminalTheme>(3);
var defaultValueProvider = new Mock<IDefaultValueProvider>();
defaultValueProvider.Setup(x => x.GetPreInstalledThemes()).Returns(themes);
var themesContainer = new Mock<IApplicationDataContainer>();
var applicationDataContainers = new ApplicationDataContainers
{
Themes = themesContainer.Object,
KeyBindings = Mock.Of<IApplicationDataContainer>(),
LocalSettings = Mock.Of<IApplicationDataContainer>(),
RoamingSettings = Mock.Of<IApplicationDataContainer>(),
ShellProfiles = Mock.Of<IApplicationDataContainer>()
};
new SettingsService(defaultValueProvider.Object, applicationDataContainers);
defaultValueProvider.Verify(x => x.GetPreInstalledThemes(), Times.Once);
foreach (var theme in themes)
{
themesContainer.Verify(x => x.WriteValueAsJson(theme.Id.ToString(), theme), Times.Once);
}
}
[Fact]
public void Constructor_Default_WritesAllPreinstalledShellProfilesInShellProfilesContainer()
{
var shellProfiles = _fixture.CreateMany<ShellProfile>(3);
var defaultValueProvider = new Mock<IDefaultValueProvider>();
defaultValueProvider.Setup(x => x.GetPreinstalledShellProfiles()).Returns(shellProfiles);
var shellProfilesContainer = new Mock<IApplicationDataContainer>();
var applicationDataContainers = new ApplicationDataContainers
{
ShellProfiles = shellProfilesContainer.Object,
KeyBindings = Mock.Of<IApplicationDataContainer>(),
LocalSettings = Mock.Of<IApplicationDataContainer>(),
RoamingSettings = Mock.Of<IApplicationDataContainer>(),
Themes = Mock.Of<IApplicationDataContainer>()
};
new SettingsService(defaultValueProvider.Object, applicationDataContainers);
defaultValueProvider.Verify(x => x.GetPreinstalledShellProfiles(), Times.Once);
foreach (var shellProfile in shellProfiles)
{
shellProfilesContainer.Verify(x => x.WriteValueAsJson(shellProfile.Id.ToString(), shellProfile), Times.Once);
}
}
[Fact]
public void GetCurrentThemeId_ValueExistsInRoamingSettings_ReturnsCurrentThemeId()
{
var defaultValueProvider = Mock.Of<IDefaultValueProvider>();
var roamingSettings = new Mock<IApplicationDataContainer>();
var currentThemeId = (object)_fixture.Create<Guid>();
roamingSettings.Setup(x => x.TryGetValue(SettingsService.CurrentThemeKey, out currentThemeId)).Returns(true);
var applicationDataContainers = new ApplicationDataContainers
{
RoamingSettings = roamingSettings.Object,
KeyBindings = Mock.Of<IApplicationDataContainer>(),
LocalSettings = Mock.Of<IApplicationDataContainer>(),
ShellProfiles = Mock.Of<IApplicationDataContainer>(),
Themes = Mock.Of<IApplicationDataContainer>()
};
var settingsService = new SettingsService(defaultValueProvider, applicationDataContainers);
var result = settingsService.GetCurrentThemeId();
result.Should().Be((Guid)currentThemeId);
roamingSettings.Verify(x => x.TryGetValue(SettingsService.CurrentThemeKey, out currentThemeId), Times.Once);
}
[Fact]
public void GetCurrentThemeId_ValueDoesNotExistInRoamingSettings_ReturnsDefaultThemeIdFromDefaultValueProvider()
{
var defaultValueProvider = new Mock<IDefaultValueProvider>();
var defaultThemeId = _fixture.Create<Guid>();
defaultValueProvider.Setup(x => x.GetDefaultThemeId()).Returns(defaultThemeId);
var roamingSettings = new Mock<IApplicationDataContainer>();
var currentThemeId = (object)_fixture.Create<Guid>();
roamingSettings.Setup(x => x.TryGetValue(SettingsService.CurrentThemeKey, out currentThemeId)).Returns(false);
var applicationDataContainers = new ApplicationDataContainers
{
RoamingSettings = roamingSettings.Object,
KeyBindings = Mock.Of<IApplicationDataContainer>(),
LocalSettings = Mock.Of<IApplicationDataContainer>(),
ShellProfiles = Mock.Of<IApplicationDataContainer>(),
Themes = Mock.Of<IApplicationDataContainer>()
};
var settingsService = new SettingsService(defaultValueProvider.Object, applicationDataContainers);
var result = settingsService.GetCurrentThemeId();
result.Should().Be(defaultThemeId);
defaultValueProvider.Verify(x => x.GetDefaultThemeId(), Times.Once);
roamingSettings.Verify(x => x.TryGetValue(SettingsService.CurrentThemeKey, out currentThemeId), Times.Once);
}
[Fact]
public void SaveCurrentThemeId_Default_SetsValueOnRoamingSettings()
{
var currentThemeId = _fixture.Create<Guid>();
var defaultValueProvider = Mock.Of<IDefaultValueProvider>();
var roamingSettings = new Mock<IApplicationDataContainer>();
var applicationDataContainers = new ApplicationDataContainers
{
RoamingSettings = roamingSettings.Object,
KeyBindings = Mock.Of<IApplicationDataContainer>(),
LocalSettings = Mock.Of<IApplicationDataContainer>(),
ShellProfiles = Mock.Of<IApplicationDataContainer>(),
Themes = Mock.Of<IApplicationDataContainer>()
};
var settingsService = new SettingsService(defaultValueProvider, applicationDataContainers);
settingsService.SaveCurrentThemeId(currentThemeId);
roamingSettings.Verify(x => x.SetValue(SettingsService.CurrentThemeKey, currentThemeId), Times.Once);
}
[Fact]
public void SaveCurrentThemeId_Default_InvokesCurrentThemeChangedEvent()
{
var currentThemeId = _fixture.Create<Guid>();
var defaultValueProvider = Mock.Of<IDefaultValueProvider>();
var roamingSettings = new Mock<IApplicationDataContainer>();
var applicationDataContainers = new ApplicationDataContainers
{
RoamingSettings = roamingSettings.Object,
KeyBindings = Mock.Of<IApplicationDataContainer>(),
LocalSettings = Mock.Of<IApplicationDataContainer>(),
ShellProfiles = Mock.Of<IApplicationDataContainer>(),
Themes = Mock.Of<IApplicationDataContainer>()
};
var currentThemeChangedEventInvoked = false;
var settingsService = new SettingsService(defaultValueProvider, applicationDataContainers);
settingsService.CurrentThemeChanged += (s, e) => currentThemeChangedEventInvoked = true;
settingsService.SaveCurrentThemeId(currentThemeId);
currentThemeChangedEventInvoked.Should().BeTrue();
}
[Fact]
public void SaveTheme_Default_WritesToThemesContainer()
{
var theme = _fixture.Create<TerminalTheme>();
var defaultValueProvider = Mock.Of<IDefaultValueProvider>();
var themesContainer = new Mock<IApplicationDataContainer>();
var applicationDataContainers = new ApplicationDataContainers
{
Themes = themesContainer.Object,
KeyBindings = Mock.Of<IApplicationDataContainer>(),
LocalSettings = Mock.Of<IApplicationDataContainer>(),
RoamingSettings = Mock.Of<IApplicationDataContainer>(),
ShellProfiles = Mock.Of<IApplicationDataContainer>()
};
var settingsService = new SettingsService(defaultValueProvider, applicationDataContainers);
settingsService.SaveTheme(theme);
themesContainer.Verify(x => x.WriteValueAsJson(theme.Id.ToString(), theme), Times.Once);
}
[Fact]
public void SaveTheme_ThemeIsCurrentTheme_InvokesCurrentThemeChangedEvent()
{
var theme = _fixture.Create<TerminalTheme>();
var defaultValueProvider = Mock.Of<IDefaultValueProvider>();
var themesContainer = new Mock<IApplicationDataContainer>();
var roamingSettings = new Mock<IApplicationDataContainer>();
var currentThemeId = (object)theme.Id;
roamingSettings.Setup(x => x.TryGetValue(SettingsService.CurrentThemeKey, out currentThemeId)).Returns(true);
var currentThemeChangedEventInvoked = false;
var applicationDataContainers = new ApplicationDataContainers
{
Themes = themesContainer.Object,
KeyBindings = Mock.Of<IApplicationDataContainer>(),
LocalSettings = Mock.Of<IApplicationDataContainer>(),
RoamingSettings = roamingSettings.Object,
ShellProfiles = Mock.Of<IApplicationDataContainer>()
};
var settingsService = new SettingsService(defaultValueProvider, applicationDataContainers);
settingsService.CurrentThemeChanged += (s, e) => currentThemeChangedEventInvoked = true;
settingsService.SaveTheme(theme);
currentThemeChangedEventInvoked.Should().BeTrue();
}
[Fact]
public void DeleteTheme_Default_CallsDeleteOnThemesContainer()
{
var themeId = _fixture.Create<Guid>();
var defaultValueProvider = Mock.Of<IDefaultValueProvider>();
var themesContainer = new Mock<IApplicationDataContainer>();
var applicationDataContainers = new ApplicationDataContainers
{
Themes = themesContainer.Object,
KeyBindings = Mock.Of<IApplicationDataContainer>(),
LocalSettings = Mock.Of<IApplicationDataContainer>(),
RoamingSettings = Mock.Of<IApplicationDataContainer>(),
ShellProfiles = Mock.Of<IApplicationDataContainer>()
};
var settingsService = new SettingsService(defaultValueProvider, applicationDataContainers);
settingsService.DeleteTheme(themeId);
themesContainer.Verify(x => x.Delete(themeId.ToString()), Times.Once);
}
[Fact]
public void GetThemes_Default_CallsGetAllOnThemesContainer()
{
var themes = _fixture.CreateMany<TerminalTheme>(3);
var defaultValueProvider = Mock.Of<IDefaultValueProvider>();
var themesContainer = new Mock<IApplicationDataContainer>();
themesContainer.Setup(x => x.GetAll()).Returns(themes.Select(JsonConvert.SerializeObject));
var applicationDataContainers = new ApplicationDataContainers
{
Themes = themesContainer.Object,
KeyBindings = Mock.Of<IApplicationDataContainer>(),
LocalSettings = Mock.Of<IApplicationDataContainer>(),
RoamingSettings = Mock.Of<IApplicationDataContainer>(),
ShellProfiles = Mock.Of<IApplicationDataContainer>()
};
var settingsService = new SettingsService(defaultValueProvider, applicationDataContainers);
var result = settingsService.GetThemes();
result.Should().BeEquivalentTo(themes);
themesContainer.Verify(x => x.GetAll(), Times.Once);
}
[Fact]
public void GetTheme_ValidThemeId_ReturnsThemeFromThemesContainer()
{
var theme = _fixture.Create<TerminalTheme>();
var defaultValueProvider = Mock.Of<IDefaultValueProvider>();
var themesContainer = new Mock<IApplicationDataContainer>();
themesContainer.Setup(x => x.ReadValueFromJson(theme.Id.ToString(), default(TerminalTheme))).Returns(theme);
var applicationDataContainers = new ApplicationDataContainers
{
Themes = themesContainer.Object,
KeyBindings = Mock.Of<IApplicationDataContainer>(),
LocalSettings = Mock.Of<IApplicationDataContainer>(),
RoamingSettings = Mock.Of<IApplicationDataContainer>(),
ShellProfiles = Mock.Of<IApplicationDataContainer>()
};
var settingsService = new SettingsService(defaultValueProvider, applicationDataContainers);
var result = settingsService.GetTheme(theme.Id);
result.Should().BeEquivalentTo(theme);
themesContainer.Verify(x => x.ReadValueFromJson(theme.Id.ToString(), default(TerminalTheme)), Times.Once);
}
[Fact]
public void GetTheme_UnknownThemeId_ReturnsNull()
{
var themeId = _fixture.Create<Guid>();
var defaultValueProvider = Mock.Of<IDefaultValueProvider>();
var themesContainer = new Mock<IApplicationDataContainer>();
themesContainer.Setup(x => x.ReadValueFromJson(themeId.ToString(), default(TerminalTheme))).Returns(value: null);
var applicationDataContainers = new ApplicationDataContainers
{
Themes = themesContainer.Object,
KeyBindings = Mock.Of<IApplicationDataContainer>(),
LocalSettings = Mock.Of<IApplicationDataContainer>(),
RoamingSettings = Mock.Of<IApplicationDataContainer>(),
ShellProfiles = Mock.Of<IApplicationDataContainer>()
};
var settingsService = new SettingsService(defaultValueProvider, applicationDataContainers);
var result = settingsService.GetTheme(themeId);
result.Should().BeNull();
themesContainer.Verify(x => x.ReadValueFromJson(themeId.ToString(), default(TerminalTheme)), Times.Once);
}
[Fact]
public void GetTerminalOptions_Default_ReturnsTerminalOptionsFromRoamingSettings()
{
var terminalOptions = _fixture.Create<TerminalOptions>();
var defaultValueProvider = Mock.Of<IDefaultValueProvider>();
var roamingSettings = new Mock<IApplicationDataContainer>();
roamingSettings.Setup(x => x.ReadValueFromJson(nameof(TerminalOptions), It.IsAny<TerminalOptions>())).Returns(terminalOptions);
var applicationDataContainers = new ApplicationDataContainers
{
RoamingSettings = roamingSettings.Object,
KeyBindings = Mock.Of<IApplicationDataContainer>(),
LocalSettings = Mock.Of<IApplicationDataContainer>(),
Themes = Mock.Of<IApplicationDataContainer>(),
ShellProfiles = Mock.Of<IApplicationDataContainer>()
};
var settingsService = new SettingsService(defaultValueProvider, applicationDataContainers);
var result = settingsService.GetTerminalOptions();
result.Should().BeEquivalentTo(terminalOptions);
roamingSettings.Verify(x => x.ReadValueFromJson(nameof(TerminalOptions), It.IsAny<TerminalOptions>()), Times.Once);
}
[Fact]
public void SaveTerminalOptions_Default_WritesToRoamingSettings()
{
var terminalOptions = _fixture.Create<TerminalOptions>();
var defaultValueProvider = Mock.Of<IDefaultValueProvider>();
var roamingSettings = new Mock<IApplicationDataContainer>();
var applicationDataContainers = new ApplicationDataContainers
{
RoamingSettings = roamingSettings.Object,
KeyBindings = Mock.Of<IApplicationDataContainer>(),
LocalSettings = Mock.Of<IApplicationDataContainer>(),
Themes = Mock.Of<IApplicationDataContainer>(),
ShellProfiles = Mock.Of<IApplicationDataContainer>()
};
var settingsService = new SettingsService(defaultValueProvider, applicationDataContainers);
settingsService.SaveTerminalOptions(terminalOptions);
roamingSettings.Verify(x => x.WriteValueAsJson(nameof(TerminalOptions), terminalOptions), Times.Once);
}
[Fact]
public void SaveTerminalOptions_Default_InvokesTerminalOptionsChangedEvent()
{
var terminalOptions = _fixture.Create<TerminalOptions>();
var defaultValueProvider = Mock.Of<IDefaultValueProvider>();
var roamingSettings = new Mock<IApplicationDataContainer>();
var terminalOptionsChangedEventInvoked = false;
var applicationDataContainers = new ApplicationDataContainers
{
RoamingSettings = roamingSettings.Object,
KeyBindings = Mock.Of<IApplicationDataContainer>(),
LocalSettings = Mock.Of<IApplicationDataContainer>(),
Themes = Mock.Of<IApplicationDataContainer>(),
ShellProfiles = Mock.Of<IApplicationDataContainer>()
};
var settingsService = new SettingsService(defaultValueProvider, applicationDataContainers);
settingsService.TerminalOptionsChanged += (s, e) => terminalOptionsChangedEventInvoked = true;
settingsService.SaveTerminalOptions(terminalOptions);
terminalOptionsChangedEventInvoked.Should().BeTrue();
}
[Fact]
public void GetApplicationSettings_Default_ReturnsApplicationSettingsFromRoamingSettings()
{
var applicationSettings = _fixture.Create<ApplicationSettings>();
var defaultValueProvider = Mock.Of<IDefaultValueProvider>();
var roamingSettings = new Mock<IApplicationDataContainer>();
roamingSettings.Setup(x => x.ReadValueFromJson(nameof(ApplicationSettings), It.IsAny<ApplicationSettings>())).Returns(applicationSettings);
var applicationDataContainers = new ApplicationDataContainers
{
RoamingSettings = roamingSettings.Object,
KeyBindings = Mock.Of<IApplicationDataContainer>(),
LocalSettings = Mock.Of<IApplicationDataContainer>(),
Themes = Mock.Of<IApplicationDataContainer>(),
ShellProfiles = Mock.Of<IApplicationDataContainer>()
};
var settingsService = new SettingsService(defaultValueProvider, applicationDataContainers);
var result = settingsService.GetApplicationSettings();
result.Should().BeEquivalentTo(applicationSettings);
roamingSettings.Verify(x => x.ReadValueFromJson(nameof(ApplicationSettings), It.IsAny<ApplicationSettings>()), Times.Once);
}
[Fact]
public void SaveApplicationSettings_Default_WritesToRoamingSettings()
{
var applicationSettings = _fixture.Create<ApplicationSettings>();
var defaultValueProvider = Mock.Of<IDefaultValueProvider>();
var roamingSettings = new Mock<IApplicationDataContainer>();
var applicationDataContainers = new ApplicationDataContainers
{
RoamingSettings = roamingSettings.Object,
KeyBindings = Mock.Of<IApplicationDataContainer>(),
LocalSettings = Mock.Of<IApplicationDataContainer>(),
Themes = Mock.Of<IApplicationDataContainer>(),
ShellProfiles = Mock.Of<IApplicationDataContainer>()
};
var settingsService = new SettingsService(defaultValueProvider, applicationDataContainers);
settingsService.SaveApplicationSettings(applicationSettings);
roamingSettings.Verify(x => x.WriteValueAsJson(nameof(ApplicationSettings), applicationSettings), Times.Once);
}
[Fact]
public void SaveApplicationSettings_Default_InvokesApplicationSettingsChangedEvent()
{
var applicationSettings = _fixture.Create<ApplicationSettings>();
var defaultValueProvider = Mock.Of<IDefaultValueProvider>();
var roamingSettings = new Mock<IApplicationDataContainer>();
var applicationSettingsChangedEventInvoked = false;
var applicationDataContainers = new ApplicationDataContainers
{
RoamingSettings = roamingSettings.Object,
KeyBindings = Mock.Of<IApplicationDataContainer>(),
LocalSettings = Mock.Of<IApplicationDataContainer>(),
Themes = Mock.Of<IApplicationDataContainer>(),
ShellProfiles = Mock.Of<IApplicationDataContainer>()
};
var settingsService = new SettingsService(defaultValueProvider, applicationDataContainers);
settingsService.ApplicationSettingsChanged += (s, e) => applicationSettingsChangedEventInvoked = true;
settingsService.SaveApplicationSettings(applicationSettings);
applicationSettingsChangedEventInvoked.Should().BeTrue();
}
[Fact]
public void GetKeyBindings_KeyBindingsInKeyBindingsContainer_ReturnsKeyBindingsFromKeyBindingsContainer()
{
var keyBindings = _fixture.CreateMany<KeyBinding>(5);
var defaultValueProvider = Mock.Of<IDefaultValueProvider>();
var keyBindingsContainer = new Mock<IApplicationDataContainer>();
foreach (var keyBinding in keyBindings)
{
keyBindingsContainer.Setup(x => x.ReadValueFromJson<Collection<KeyBinding>>(keyBinding.Command.ToString(), null)).Returns(new Collection<KeyBinding> { keyBinding });
}
var applicationDataContainers = new ApplicationDataContainers
{
KeyBindings = keyBindingsContainer.Object,
RoamingSettings = Mock.Of<IApplicationDataContainer>(),
LocalSettings = Mock.Of<IApplicationDataContainer>(),
Themes = Mock.Of<IApplicationDataContainer>(),
ShellProfiles = Mock.Of<IApplicationDataContainer>()
};
var settingsService = new SettingsService(defaultValueProvider, applicationDataContainers);
var result = settingsService.GetKeyBindings();
foreach (var keyBinding in keyBindings)
{
keyBindingsContainer.Verify(x => x.ReadValueFromJson<Collection<KeyBinding>>(keyBinding.Command.ToString(), null), Times.Once);
}
}
[Fact]
public void GetKeyBindings_KeyBindingsNotInKeyBindingsContainer_ReturnsKeyBindingsFromDefaultValueProvider()
{
var keyBindings = _fixture.CreateMany<KeyBinding>(5);
var defaultValueProvider = new Mock<IDefaultValueProvider>();
var keyBindingsContainer = new Mock<IApplicationDataContainer>();
foreach (var keyBinding in keyBindings)
{
keyBindingsContainer.Setup(x => x.ReadValueFromJson<Collection<KeyBinding>>(keyBinding.Command.ToString(), null)).Returns(new Collection<KeyBinding> { keyBinding });
}
var applicationDataContainers = new ApplicationDataContainers
{
KeyBindings = keyBindingsContainer.Object,
RoamingSettings = Mock.Of<IApplicationDataContainer>(),
LocalSettings = Mock.Of<IApplicationDataContainer>(),
Themes = Mock.Of<IApplicationDataContainer>(),
ShellProfiles = Mock.Of<IApplicationDataContainer>()
};
var settingsService = new SettingsService(defaultValueProvider.Object, applicationDataContainers);
var result = settingsService.GetKeyBindings();
foreach (Command command in Enum.GetValues(typeof(Command)))
{
if (keyBindings.Select(x => x.Command).Contains(command))
{
continue;
}
defaultValueProvider.Verify(x => x.GetDefaultKeyBindings(command), Times.Once);
}
}
[Fact]
public void SaveKeyBindings_Default_WritesToKeyBindingsContainer()
{
var command = _fixture.Create<Command>();
var keyBindings = _fixture.Create<ICollection<KeyBinding>>();
var defaultValueProvider = Mock.Of<IDefaultValueProvider>();
var keyBindingsContainer = new Mock<IApplicationDataContainer>();
var applicationDataContainers = new ApplicationDataContainers
{
KeyBindings = keyBindingsContainer.Object,
RoamingSettings = Mock.Of<IApplicationDataContainer>(),
LocalSettings = Mock.Of<IApplicationDataContainer>(),
Themes = Mock.Of<IApplicationDataContainer>(),
ShellProfiles = Mock.Of<IApplicationDataContainer>()
};
var settingsService = new SettingsService(defaultValueProvider, applicationDataContainers);
settingsService.SaveKeyBindings(command, keyBindings);
keyBindingsContainer.Verify(x => x.WriteValueAsJson(command.ToString(), keyBindings), Times.Once);
}
[Fact]
public void SaveKeyBindings_Default_InvokesKeyBindingsChangedEvent()
{
var command = _fixture.Create<Command>();
var keyBindings = _fixture.Create<Collection<KeyBinding>>();
var defaultValueProvider = Mock.Of<IDefaultValueProvider>();
var keyBindingsContainer = new Mock<IApplicationDataContainer>();
var keyBindingsChangedEventInvoked = false;
var applicationDataContainers = new ApplicationDataContainers
{
KeyBindings = keyBindingsContainer.Object,
RoamingSettings = Mock.Of<IApplicationDataContainer>(),
LocalSettings = Mock.Of<IApplicationDataContainer>(),
Themes = Mock.Of<IApplicationDataContainer>(),
ShellProfiles = Mock.Of<IApplicationDataContainer>()
};
var settingsService = new SettingsService(defaultValueProvider, applicationDataContainers);
settingsService.KeyBindingsChanged += (s, e) => keyBindingsChangedEventInvoked = true;
settingsService.SaveKeyBindings(command, keyBindings);
keyBindingsChangedEventInvoked.Should().BeTrue();
}
[Fact]
public void ResetKeyBindings_Default_WritesToKeyBindingsContainerFromDefaultValueProvider()
{
var defaultValueProvider = new Mock<IDefaultValueProvider>();
var keyBindingsContainer = new Mock<IApplicationDataContainer>();
var applicationDataContainers = new ApplicationDataContainers
{
KeyBindings = keyBindingsContainer.Object,
RoamingSettings = Mock.Of<IApplicationDataContainer>(),
LocalSettings = Mock.Of<IApplicationDataContainer>(),
Themes = Mock.Of<IApplicationDataContainer>(),
ShellProfiles = Mock.Of<IApplicationDataContainer>()
};
var settingsService = new SettingsService(defaultValueProvider.Object, applicationDataContainers);
settingsService.ResetKeyBindings();
foreach (Command command in Enum.GetValues(typeof(Command)))
{
keyBindingsContainer.Verify(x => x.WriteValueAsJson(command.ToString(), It.IsAny<ICollection<KeyBinding>>()), Times.Once);
defaultValueProvider.Verify(x => x.GetDefaultKeyBindings(command), Times.Once);
}
}
[Fact]
public void ResetKeyBindings_Default_InvokesKeyBindingsChangedEvent()
{
var defaultValueProvider = Mock.Of<IDefaultValueProvider>();
var keyBindingsContainer = new Mock<IApplicationDataContainer>();
var keyBindingsChangedEventInvoked = false;
var applicationDataContainers = new ApplicationDataContainers
{
KeyBindings = keyBindingsContainer.Object,
RoamingSettings = Mock.Of<IApplicationDataContainer>(),
LocalSettings = Mock.Of<IApplicationDataContainer>(),
Themes = Mock.Of<IApplicationDataContainer>(),
ShellProfiles = Mock.Of<IApplicationDataContainer>()
};
var settingsService = new SettingsService(defaultValueProvider, applicationDataContainers);
settingsService.KeyBindingsChanged += (s, e) => keyBindingsChangedEventInvoked = true;
settingsService.ResetKeyBindings();
keyBindingsChangedEventInvoked.Should().BeTrue();
}
[Fact]
public void GetDefaultShellProfileId_ValueExistsInRoamingSettings_ReturnsDefaultShellProfileId()
{
var defaultValueProvider = Mock.Of<IDefaultValueProvider>();
var roamingSettings = new Mock<IApplicationDataContainer>();
var defaultShellProfileId = (object)_fixture.Create<Guid>();
roamingSettings.Setup(x => x.TryGetValue(SettingsService.DefaultShellProfileKey, out defaultShellProfileId)).Returns(true);
var applicationDataContainers = new ApplicationDataContainers
{
RoamingSettings = roamingSettings.Object,
KeyBindings = Mock.Of<IApplicationDataContainer>(),
LocalSettings = Mock.Of<IApplicationDataContainer>(),
ShellProfiles = Mock.Of<IApplicationDataContainer>(),
Themes = Mock.Of<IApplicationDataContainer>()
};
var settingsService = new SettingsService(defaultValueProvider, applicationDataContainers);
var result = settingsService.GetDefaultShellProfileId();
result.Should().Be((Guid)defaultShellProfileId);
roamingSettings.Verify(x => x.TryGetValue(SettingsService.DefaultShellProfileKey, out defaultShellProfileId), Times.Once);
}
[Fact]
public void GetDefaultShellProfileId_ValueDoesNotExistInRoamingSettings_ReturnsDefaultShellProfileIdFromDefaultValueProvider()
{
var defaultValueProvider = new Mock<IDefaultValueProvider>();
var defaultShellProfileId = _fixture.Create<Guid>();
defaultValueProvider.Setup(x => x.GetDefaultShellProfileId()).Returns(defaultShellProfileId);
var roamingSettings = new Mock<IApplicationDataContainer>();
var currentDefaultShellProfileId = (object)_fixture.Create<Guid>();
roamingSettings.Setup(x => x.TryGetValue(SettingsService.DefaultShellProfileKey, out currentDefaultShellProfileId)).Returns(false);
var applicationDataContainers = new ApplicationDataContainers
{
RoamingSettings = roamingSettings.Object,
KeyBindings = Mock.Of<IApplicationDataContainer>(),
LocalSettings = Mock.Of<IApplicationDataContainer>(),
ShellProfiles = Mock.Of<IApplicationDataContainer>(),
Themes = Mock.Of<IApplicationDataContainer>()
};
var settingsService = new SettingsService(defaultValueProvider.Object, applicationDataContainers);
var result = settingsService.GetDefaultShellProfileId();
result.Should().Be(defaultShellProfileId);
defaultValueProvider.Verify(x => x.GetDefaultShellProfileId(), Times.Once);
roamingSettings.Verify(x => x.TryGetValue(SettingsService.DefaultShellProfileKey, out currentDefaultShellProfileId), Times.Once);
}
[Fact]
public void GetShellProfiles_Default_CallsGetAllOnShellProfilesContainer()
{
var shellProfiles = _fixture.CreateMany<ShellProfile>(3);
var defaultValueProvider = Mock.Of<IDefaultValueProvider>();
var shellProfilesContainer = new Mock<IApplicationDataContainer>();
shellProfilesContainer.Setup(x => x.GetAll()).Returns(shellProfiles.Select(JsonConvert.SerializeObject));
var applicationDataContainers = new ApplicationDataContainers
{
ShellProfiles = shellProfilesContainer.Object,
KeyBindings = Mock.Of<IApplicationDataContainer>(),
LocalSettings = Mock.Of<IApplicationDataContainer>(),
RoamingSettings = Mock.Of<IApplicationDataContainer>(),
Themes = Mock.Of<IApplicationDataContainer>()
};
var settingsService = new SettingsService(defaultValueProvider, applicationDataContainers);
var result = settingsService.GetShellProfiles();
result.Should().BeEquivalentTo(shellProfiles);
shellProfilesContainer.Verify(x => x.GetAll(), Times.Once);
}
[Fact]
public void SaveShellProfile_Default_WritesToShellProfilesContainer()
{
var shellProfile = _fixture.Create<ShellProfile>();
var defaultValueProvider = Mock.Of<IDefaultValueProvider>();
var shellProfilesContainer = new Mock<IApplicationDataContainer>();
var applicationDataContainers = new ApplicationDataContainers
{
ShellProfiles = shellProfilesContainer.Object,
KeyBindings = Mock.Of<IApplicationDataContainer>(),
LocalSettings = Mock.Of<IApplicationDataContainer>(),
RoamingSettings = Mock.Of<IApplicationDataContainer>(),
Themes = Mock.Of<IApplicationDataContainer>()
};
var settingsService = new SettingsService(defaultValueProvider, applicationDataContainers);
settingsService.SaveShellProfile(shellProfile);
shellProfilesContainer.Verify(x => x.WriteValueAsJson(shellProfile.Id.ToString(), shellProfile), Times.Once);
}
[Fact]
public void DeleteShellProfile_Default_CallsDeleteOnShellProfilesContainer()
{
var shellProfileId = _fixture.Create<Guid>();
var defaultValueProvider = Mock.Of<IDefaultValueProvider>();
var shellProfilesContainer = new Mock<IApplicationDataContainer>();
var applicationDataContainers = new ApplicationDataContainers
{
ShellProfiles = shellProfilesContainer.Object,
KeyBindings = Mock.Of<IApplicationDataContainer>(),
LocalSettings = Mock.Of<IApplicationDataContainer>(),
RoamingSettings = Mock.Of<IApplicationDataContainer>(),
Themes = Mock.Of<IApplicationDataContainer>()
};
var settingsService = new SettingsService(defaultValueProvider, applicationDataContainers);
settingsService.DeleteShellProfile(shellProfileId);
shellProfilesContainer.Verify(x => x.Delete(shellProfileId.ToString()), Times.Once);
}
}
}
@@ -0,0 +1,213 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Ansi 0 Color</key>
<dict>
<key>Blue Component</key>
<real>0.016337040811777115</real>
<key>Green Component</key>
<real>0.016232240945100784</real>
<key>Red Component</key>
<real>0.020433688536286354</real>
</dict>
<key>Ansi 1 Color</key>
<dict>
<key>Blue Component</key>
<real>0.075083047151565552</real>
<key>Green Component</key>
<real>0.0</real>
<key>Red Component</key>
<real>0.73976802825927734</real>
</dict>
<key>Ansi 10 Color</key>
<dict>
<key>Blue Component</key>
<real>0.43171143531799316</real>
<key>Green Component</key>
<real>1</real>
<key>Red Component</key>
<real>0.61819148063659668</real>
</dict>
<key>Ansi 11 Color</key>
<dict>
<key>Blue Component</key>
<real>0.10242157429456711</real>
<key>Green Component</key>
<real>0.7578997015953064</real>
<key>Red Component</key>
<real>0.93761706352233887</real>
</dict>
<key>Ansi 12 Color</key>
<dict>
<key>Blue Component</key>
<real>0.77801346778869629</real>
<key>Green Component</key>
<real>0.59102588891983032</real>
<key>Red Component</key>
<real>0.096501544117927551</real>
</dict>
<key>Ansi 13 Color</key>
<dict>
<key>Blue Component</key>
<real>0.32394066452980042</real>
<key>Green Component</key>
<real>0.34950375556945801</real>
<key>Red Component</key>
<real>0.60686224699020386</real>
</dict>
<key>Ansi 14 Color</key>
<dict>
<key>Blue Component</key>
<real>0.95550096035003662</real>
<key>Green Component</key>
<real>0.97915208339691162</real>
<key>Red Component</key>
<real>0.78500419855117798</real>
</dict>
<key>Ansi 15 Color</key>
<dict>
<key>Blue Component</key>
<real>0.98438382148742676</real>
<key>Green Component</key>
<real>0.96043074131011963</real>
<key>Red Component</key>
<real>0.96449893712997437</real>
</dict>
<key>Ansi 2 Color</key>
<dict>
<key>Blue Component</key>
<real>0.092498354613780975</real>
<key>Green Component</key>
<real>0.6950421929359436</real>
<key>Red Component</key>
<real>0.28945007920265198</real>
</dict>
<key>Ansi 3 Color</key>
<dict>
<key>Blue Component</key>
<real>0.1164485365152359</real>
<key>Green Component</key>
<real>0.45553147792816162</real>
<key>Red Component</key>
<real>0.90543299913406372</real>
</dict>
<key>Ansi 4 Color</key>
<dict>
<key>Blue Component</key>
<real>0.77829605340957642</real>
<key>Green Component</key>
<real>0.28880593180656433</real>
<key>Red Component</key>
<real>0.059818677604198456</real>
</dict>
<key>Ansi 5 Color</key>
<dict>
<key>Blue Component</key>
<real>0.57602518796920776</real>
<key>Green Component</key>
<real>0.35004982352256775</real>
<key>Red Component</key>
<real>0.40191537141799927</real>
</dict>
<key>Ansi 6 Color</key>
<dict>
<key>Blue Component</key>
<real>0.59465551376342773</real>
<key>Green Component</key>
<real>0.64633101224899292</real>
<key>Red Component</key>
<real>0.43781715631484985</real>
</dict>
<key>Ansi 7 Color</key>
<dict>
<key>Blue Component</key>
<real>0.75428962707519531</real>
<key>Green Component</key>
<real>0.86175870895385742</real>
<key>Red Component</key>
<real>0.97308963537216187</real>
</dict>
<key>Ansi 8 Color</key>
<dict>
<key>Blue Component</key>
<real>0.7491685152053833</real>
<key>Green Component</key>
<real>0.48549851775169373</real>
<key>Red Component</key>
<real>0.30726605653762817</real>
</dict>
<key>Ansi 9 Color</key>
<dict>
<key>Blue Component</key>
<real>0.35247993469238281</real>
<key>Green Component</key>
<real>0.37095117568969727</real>
<key>Red Component</key>
<real>0.98876720666885376</real>
</dict>
<key>Background Color</key>
<dict>
<key>Blue Component</key>
<real>0.27054747939109802</real>
<key>Green Component</key>
<real>0.11274909973144531</real>
<key>Red Component</key>
<real>0.12003477662801743</real>
</dict>
<key>Bold Color</key>
<dict>
<key>Blue Component</key>
<real>0.075083047151565552</real>
<key>Green Component</key>
<real>0.0</real>
<key>Red Component</key>
<real>0.73976802825927734</real>
</dict>
<key>Cursor Color</key>
<dict>
<key>Blue Component</key>
<real>0.21945112943649292</real>
<key>Green Component</key>
<real>0.74987590312957764</real>
<key>Red Component</key>
<real>0.93704277276992798</real>
</dict>
<key>Cursor Text Color</key>
<dict>
<key>Blue Component</key>
<real>0.037519864737987518</real>
<key>Green Component</key>
<real>0.032785907387733459</real>
<key>Red Component</key>
<real>0.02988031692802906</real>
</dict>
<key>Foreground Color</key>
<dict>
<key>Blue Component</key>
<real>0.75428962707519531</real>
<key>Green Component</key>
<real>0.86175870895385742</real>
<key>Red Component</key>
<real>0.97308963537216187</real>
</dict>
<key>Selected Text Color</key>
<dict>
<key>Blue Component</key>
<real>0.767447829246521</real>
<key>Green Component</key>
<real>0.85237473249435425</real>
<key>Red Component</key>
<real>0.95287787914276123</real>
</dict>
<key>Selection Color</key>
<dict>
<key>Blue Component</key>
<real>0.30699285864830017</real>
<key>Green Component</key>
<real>0.41871801018714905</real>
<key>Red Component</key>
<real>0.43789717555046082</real>
</dict>
</dict>
</plist>
@@ -0,0 +1,45 @@
using AutoFixture;
using FluentAssertions;
using FluentTerminal.App.Services.Implementation;
using Moq;
using Xunit;
namespace FluentTerminal.App.Services.Test
{
public class ThemeParserFactoryTests
{
private readonly Fixture _fixture;
public ThemeParserFactoryTests()
{
_fixture = new Fixture();
}
[Fact]
public void GetParser_SupportedFileType_ReturnsParser()
{
var supportedFileType = _fixture.Create<string>();
var parser = new Mock<IThemeParser>();
parser.Setup(x => x.SupportedFileTypes).Returns(new string[] { supportedFileType });
var themeParserFactory = new ThemeParserFactory(new IThemeParser[] { parser.Object });
var result = themeParserFactory.GetParser(supportedFileType);
parser.Should().Be(parser);
}
[Fact]
public void GetParser_UnsupportedFileType_ReturnsNull()
{
var unSupportedFileType = _fixture.Create<string>();
var supportedFileType = _fixture.Create<string>();
var parser = new Mock<IThemeParser>();
parser.Setup(x => x.SupportedFileTypes).Returns(new string[] { supportedFileType });
var themeParserFactory = new ThemeParserFactory(new IThemeParser[] { parser.Object });
var result = themeParserFactory.GetParser(unSupportedFileType);
result.Should().BeNull();
}
}
}
@@ -0,0 +1,200 @@
using AutoFixture;
using FluentAssertions;
using FluentTerminal.App.Services.Implementation;
using FluentTerminal.Models;
using FluentTerminal.Models.Enums;
using FluentTerminal.Models.Requests;
using FluentTerminal.Models.Responses;
using Moq;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Xunit;
namespace FluentTerminal.App.Services.Test
{
public class TrayProcessCommunicationServiceTests
{
private readonly Fixture _fixture;
public TrayProcessCommunicationServiceTests()
{
_fixture = new Fixture();
}
[Fact]
public async Task CreateTerminal_Default_ReturnsResponseFromAppServiceConnection()
{
var settingsService = new Mock<ISettingsService>();
var keyBindings = _fixture.CreateMany<KeyBinding>(3);
settingsService.Setup(x => x.GetKeyBindings()).Returns(new Dictionary<Command, ICollection<KeyBinding>>
{
[Command.ToggleWindow] = keyBindings.ToList()
});
var appServiceConnection = new Mock<IAppServiceConnection>();
var response = _fixture.Create<CreateTerminalResponse>();
var responseMessage = new Dictionary<string, string>
{
[MessageKeys.Type] = _fixture.Create<string>(),
[MessageKeys.Content] = JsonConvert.SerializeObject(response)
};
appServiceConnection.Setup(x => x.SendMessageAsync(It.IsAny<IDictionary<string, string>>())).Returns(Task.FromResult((IDictionary<string, string>)responseMessage));
var terminalSize = _fixture.Create<TerminalSize>();
var shellProfile = _fixture.Create<ShellProfile>();
var trayProcessCommunicationService = new TrayProcessCommunicationService(settingsService.Object);
await trayProcessCommunicationService.Initialize(appServiceConnection.Object);
var result = await trayProcessCommunicationService.CreateTerminal(terminalSize, shellProfile);
result.Should().BeEquivalentTo(response);
}
[Fact]
public async Task Initialize_Default_SendsSetToggleWindowKeyBindingsRequest()
{
var settingsService = new Mock<ISettingsService>();
var keyBindings = _fixture.CreateMany<KeyBinding>(3);
settingsService.Setup(x => x.GetKeyBindings()).Returns(new Dictionary<Command, ICollection<KeyBinding>>
{
[Command.ToggleWindow] = keyBindings.ToList()
});
var appServiceConnection = new Mock<IAppServiceConnection>();
var trayProcessCommunicationService = new TrayProcessCommunicationService(settingsService.Object);
await trayProcessCommunicationService.Initialize(appServiceConnection.Object);
appServiceConnection.Verify(x => x.SendMessageAsync(It.Is<IDictionary<string, string>>(d => d[MessageKeys.Type] == nameof(SetToggleWindowKeyBindingsRequest))), Times.Once);
}
[Fact]
public async Task ResizeTerminal_Default_SendsResizeTerminalRequest()
{
var terminalId = _fixture.Create<int>();
var terminalSize = _fixture.Create<TerminalSize>();
var settingsService = new Mock<ISettingsService>();
var keyBindings = _fixture.CreateMany<KeyBinding>(3);
settingsService.Setup(x => x.GetKeyBindings()).Returns(new Dictionary<Command, ICollection<KeyBinding>>
{
[Command.ToggleWindow] = keyBindings.ToList()
});
var appServiceConnection = new Mock<IAppServiceConnection>();
var trayProcessCommunicationService = new TrayProcessCommunicationService(settingsService.Object);
await trayProcessCommunicationService.Initialize(appServiceConnection.Object);
await trayProcessCommunicationService.ResizeTerminal(terminalId, terminalSize);
appServiceConnection.Verify(x => x.SendMessageAsync(It.Is<IDictionary<string, string>>(d => d[MessageKeys.Type] == nameof(ResizeTerminalRequest))), Times.Once);
}
[Fact]
public async Task WriteText_Default_SendsWriteTextRequest()
{
var terminalId = _fixture.Create<int>();
var text = _fixture.Create<string>();
var settingsService = new Mock<ISettingsService>();
var keyBindings = _fixture.CreateMany<KeyBinding>(3);
settingsService.Setup(x => x.GetKeyBindings()).Returns(new Dictionary<Command, ICollection<KeyBinding>>
{
[Command.ToggleWindow] = keyBindings.ToList()
});
var appServiceConnection = new Mock<IAppServiceConnection>();
var trayProcessCommunicationService = new TrayProcessCommunicationService(settingsService.Object);
await trayProcessCommunicationService.Initialize(appServiceConnection.Object);
await trayProcessCommunicationService.WriteText(terminalId, text);
appServiceConnection.Verify(x => x.SendMessageAsync(It.Is<IDictionary<string, string>>(d => d[MessageKeys.Type] == nameof(WriteTextRequest))), Times.Once);
}
[Fact]
public async Task CloseTerminal_Default_SendsTerminalExitedRequest()
{
var terminalId = _fixture.Create<int>();
var settingsService = new Mock<ISettingsService>();
var keyBindings = _fixture.CreateMany<KeyBinding>(3);
settingsService.Setup(x => x.GetKeyBindings()).Returns(new Dictionary<Command, ICollection<KeyBinding>>
{
[Command.ToggleWindow] = keyBindings.ToList()
});
var appServiceConnection = new Mock<IAppServiceConnection>();
var trayProcessCommunicationService = new TrayProcessCommunicationService(settingsService.Object);
await trayProcessCommunicationService.Initialize(appServiceConnection.Object);
await trayProcessCommunicationService.CloseTerminal(terminalId);
appServiceConnection.Verify(x => x.SendMessageAsync(It.Is<IDictionary<string, string>>(d => d[MessageKeys.Type] == nameof(TerminalExitedRequest))), Times.Once);
}
[Fact]
public async Task OnMessageReceived_TerminalExitedRequest_InvokesTerminalExitedEvent()
{
var terminalId = _fixture.Create<int>();
var receivedTerminalId = 0;
var terminalExitedEventCalled = false;
var request = new TerminalExitedRequest
{
TerminalId = terminalId
};
var message = new Dictionary<string, string>
{
[MessageKeys.Type] = nameof(TerminalExitedRequest),
[MessageKeys.Content] = JsonConvert.SerializeObject(request)
};
var settingsService = new Mock<ISettingsService>();
var keyBindings = _fixture.CreateMany<KeyBinding>(3);
settingsService.Setup(x => x.GetKeyBindings()).Returns(new Dictionary<Command, ICollection<KeyBinding>>
{
[Command.ToggleWindow] = keyBindings.ToList()
});
var trayProcessCommunicationService = new TrayProcessCommunicationService(settingsService.Object);
var appServiceConnection = new Mock<IAppServiceConnection>();
await trayProcessCommunicationService.Initialize(appServiceConnection.Object);
trayProcessCommunicationService.TerminalExited += (s, e) =>
{
terminalExitedEventCalled = true;
receivedTerminalId = e;
};
appServiceConnection.Raise(x => x.MessageReceived += null, null, message);
terminalExitedEventCalled.Should().BeTrue();
receivedTerminalId.Should().Be(terminalId);
}
[Fact]
public async Task OnMessageReceived_DisplayTerminalOutputRequest_InvokesCorrectOutputHandler()
{
var terminalId = _fixture.Create<int>();
var output = _fixture.Create<string>();
var receivedOutput = string.Empty;
var request = new DisplayTerminalOutputRequest
{
TerminalId = terminalId,
Output = output
};
var message = new Dictionary<string, string>
{
[MessageKeys.Type] = nameof(DisplayTerminalOutputRequest),
[MessageKeys.Content] = JsonConvert.SerializeObject(request)
};
var settingsService = new Mock<ISettingsService>();
var keyBindings = _fixture.CreateMany<KeyBinding>(3);
settingsService.Setup(x => x.GetKeyBindings()).Returns(new Dictionary<Command, ICollection<KeyBinding>>
{
[Command.ToggleWindow] = keyBindings.ToList()
});
var trayProcessCommunicationService = new TrayProcessCommunicationService(settingsService.Object);
var appServiceConnection = new Mock<IAppServiceConnection>();
await trayProcessCommunicationService.Initialize(appServiceConnection.Object);
trayProcessCommunicationService.SubscribeForTerminalOutput(terminalId, o =>
{
receivedOutput = o;
});
appServiceConnection.Raise(x => x.MessageReceived += null, null, message);
receivedOutput.Should().Be(output);
}
}
}
@@ -8,4 +8,4 @@
public IApplicationDataContainer KeyBindings { get; set; }
public IApplicationDataContainer ShellProfiles { get; set; }
}
}
}
@@ -7,4 +7,4 @@ namespace FluentTerminal.App.Services.Dialogs
{
Task<KeyBinding> CreateKeyBinding();
}
}
}
@@ -11,4 +11,4 @@ namespace FluentTerminal.App.Services.Dialogs
Task<DialogButton> ShowAsync();
}
}
}
@@ -9,4 +9,4 @@ namespace FluentTerminal.App.Services.Dialogs
Task<ShellProfile> SelectProfile();
}
}
}
@@ -4,4 +4,4 @@
{
public bool Cancelled { get; set; }
}
}
}
@@ -1,10 +1,13 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace FluentTerminal.App.Services
{
public interface IAppServiceConnection
{
event EventHandler<IDictionary<string, string>> MessageReceived;
Task<IDictionary<string, string>> SendMessageAsync(IDictionary<string, string> message);
}
}
}
@@ -16,4 +16,4 @@ namespace FluentTerminal.App.Services
IEnumerable<object> GetAll();
}
}
}
@@ -13,7 +13,9 @@ namespace FluentTerminal.App.Services
string Title { get; set; }
Task RunOnDispatcherThread(Action action);
Task<bool> TryClose();
bool ToggleFullScreen();
}
}
}
@@ -5,6 +5,7 @@ namespace FluentTerminal.App.Services
public interface IClipboardService
{
Task<string> GetText();
void SetText(string text);
}
}
}
@@ -8,11 +8,17 @@ namespace FluentTerminal.App.Services
public interface IDefaultValueProvider
{
IEnumerable<ShellProfile> GetPreinstalledShellProfiles();
Guid GetDefaultShellProfileId();
TerminalOptions GetDefaultTerminalOptions();
ICollection<KeyBinding> GetDefaultKeyBindings(Command command);
ApplicationSettings GetDefaultApplicationSettings();
Guid GetDefaultThemeId();
IEnumerable<TerminalTheme> GetPreInstalledThemes();
}
}
}
@@ -17,4 +17,4 @@ namespace FluentTerminal.App.Services
Task<KeyBinding> ShowCreateKeyBindingDialog();
}
}
}
@@ -5,6 +5,7 @@ namespace FluentTerminal.App.Services
public interface IDispatcherTimer
{
void Start();
void Stop();
TimeSpan Interval { get; set; }
@@ -12,4 +13,4 @@ namespace FluentTerminal.App.Services
event EventHandler<object> Tick;
}
}
}
@@ -12,4 +12,4 @@ namespace FluentTerminal.App.Services
Task SaveTextFile(string name, string fileTypeDescription, string fileType, string content);
}
}
}
@@ -6,6 +6,7 @@ namespace FluentTerminal.App.Services
public interface IKeyboardCommandService
{
void RegisterCommandHandler(Command command, Action handler);
void SendCommand(Command command);
}
}
}
@@ -8,35 +8,51 @@ namespace FluentTerminal.App.Services
public interface ISettingsService
{
event EventHandler<Guid> CurrentThemeChanged;
event EventHandler<TerminalOptions> TerminalOptionsChanged;
event EventHandler<ApplicationSettings> ApplicationSettingsChanged;
event EventHandler KeyBindingsChanged;
Guid GetDefaultShellProfileId();
ShellProfile GetDefaultShellProfile();
void SaveDefaultShellProfileId(Guid id);
IEnumerable<ShellProfile> GetShellProfiles();
void SaveShellProfile(ShellProfile shellProfile);
void DeleteShellProfile(Guid id);
TerminalOptions GetTerminalOptions();
void SaveTerminalOptions(TerminalOptions terminalOptions);
ApplicationSettings GetApplicationSettings();
void SaveApplicationSettings(ApplicationSettings applicationSettings);
IDictionary<Command, ICollection<KeyBinding>> GetKeyBindings();
void SaveKeyBindings(Command command, ICollection<KeyBinding> keyBindings);
void ResetKeyBindings();
TerminalTheme GetCurrentTheme();
Guid GetCurrentThemeId();
TerminalTheme GetTheme(Guid id);
void SaveCurrentThemeId(Guid id);
IEnumerable<TerminalTheme> GetThemes();
void SaveTheme(TerminalTheme theme);
void DeleteTheme(Guid id);
}
}
}
@@ -6,4 +6,4 @@ namespace FluentTerminal.App.Services
{
IEnumerable<string> GetSystemFontFamilies();
}
}
}
+12 -1
View File
@@ -9,20 +9,31 @@ namespace FluentTerminal.App.Services
public interface ITerminalView
{
event EventHandler<TerminalSize> TerminalSizeChanged;
event EventHandler<string> TerminalTitleChanged;
event EventHandler<Command> KeyboardCommandReceived;
Task ChangeTheme(TerminalColors theme);
Task ChangeOptions(TerminalOptions options);
Task ChangeKeyBindings(IEnumerable<KeyBinding> keyBindings);
Task<TerminalSize> CreateTerminal(TerminalOptions options, TerminalColors theme, IEnumerable<KeyBinding> keyBindings);
Task ConnectToSocket(string url);
void Close();
Task FocusTerminal();
Task<string> GetSelection();
Task Write(string text);
Task FindNext(string searchText);
Task FindPrevious(string searchText);
void FocusSearchTextBox();
}
}
+1 -1
View File
@@ -11,4 +11,4 @@ namespace FluentTerminal.App.Services
Task<TerminalTheme> Parse(string fileName, Stream fileContent);
}
}
}
@@ -8,4 +8,4 @@ namespace FluentTerminal.App.Services
IThemeParser GetParser(string fileType);
}
}
}
@@ -1,14 +1,26 @@
using FluentTerminal.Models;
using FluentTerminal.Models.Responses;
using System;
using System.Threading.Tasks;
namespace FluentTerminal.App.Services
{
public interface ITrayProcessCommunicationService
{
event EventHandler<int> TerminalExited;
Task Initialize(IAppServiceConnection appServiceConnection);
Task<CreateTerminalResponse> CreateTerminal(TerminalSize size, ShellProfile shellProfile);
Task ResizeTerminal(int id, TerminalSize size);
Task UpdateToggleWindowKeyBindings();
Task WriteText(int terminalId, string text);
void SubscribeForTerminalOutput(int terminalId, Action<string> callback);
Task CloseTerminal(int terminalId);
}
}
}
@@ -14,7 +14,11 @@ namespace FluentTerminal.App.Services.Implementation
ConfirmClosingTabs = false,
ConfirmClosingWindows = false,
UnderlineSelectedTab = false,
NewTerminalLocation = NewTerminalLocation.Tab
NewTerminalLocation = NewTerminalLocation.Tab,
TabsPosition = TabsPosition.Top,
CopyOnSelect = false,
MouseMiddleClickAction = MouseAction.None,
MouseRightClickAction = MouseAction.ContextMenu
};
}
@@ -23,7 +27,7 @@ namespace FluentTerminal.App.Services.Implementation
switch (command)
{
case Command.ToggleWindow:
return new List<KeyBinding>
return new List<KeyBinding>
{
new KeyBinding
{
@@ -36,7 +40,7 @@ namespace FluentTerminal.App.Services.Implementation
};
case Command.NextTab:
return new List<KeyBinding>
return new List<KeyBinding>
{
new KeyBinding
{
@@ -49,7 +53,7 @@ namespace FluentTerminal.App.Services.Implementation
};
case Command.PreviousTab:
return new List<KeyBinding>
return new List<KeyBinding>
{
new KeyBinding
{
@@ -62,7 +66,7 @@ namespace FluentTerminal.App.Services.Implementation
};
case Command.NewTab:
return new List<KeyBinding>
return new List<KeyBinding>
{
new KeyBinding
{
@@ -75,7 +79,7 @@ namespace FluentTerminal.App.Services.Implementation
};
case Command.ConfigurableNewTab:
return new List<KeyBinding>
return new List<KeyBinding>
{
new KeyBinding
{
@@ -88,7 +92,7 @@ namespace FluentTerminal.App.Services.Implementation
};
case Command.CloseTab:
return new List<KeyBinding>
return new List<KeyBinding>
{
new KeyBinding
{
@@ -101,7 +105,7 @@ namespace FluentTerminal.App.Services.Implementation
};
case Command.NewWindow:
return new List<KeyBinding>
return new List<KeyBinding>
{
new KeyBinding
{
@@ -114,7 +118,7 @@ namespace FluentTerminal.App.Services.Implementation
};
case Command.ShowSettings:
return new List<KeyBinding>
return new List<KeyBinding>
{
new KeyBinding
{
@@ -127,7 +131,7 @@ namespace FluentTerminal.App.Services.Implementation
};
case Command.Copy:
return new List<KeyBinding>
return new List<KeyBinding>
{
new KeyBinding
{
@@ -140,7 +144,7 @@ namespace FluentTerminal.App.Services.Implementation
};
case Command.Paste:
return new List<KeyBinding>
return new List<KeyBinding>
{
new KeyBinding
{
@@ -153,7 +157,7 @@ namespace FluentTerminal.App.Services.Implementation
};
case Command.Search:
return new List<KeyBinding>
return new List<KeyBinding>
{
new KeyBinding
{
@@ -166,7 +170,7 @@ namespace FluentTerminal.App.Services.Implementation
};
case Command.ToggleFullScreen:
return new List<KeyBinding>
return new List<KeyBinding>
{
new KeyBinding
{
@@ -179,7 +183,7 @@ namespace FluentTerminal.App.Services.Implementation
};
case Command.SelectAll:
return new List<KeyBinding>
return new List<KeyBinding>
{
new KeyBinding
{
@@ -192,7 +196,7 @@ namespace FluentTerminal.App.Services.Implementation
};
case Command.Clear:
return new List<KeyBinding>
return new List<KeyBinding>
{
new KeyBinding
{
@@ -411,18 +415,18 @@ namespace FluentTerminal.App.Services.Implementation
CursorAccent = "#000000",
Selection = "#4DABEDC1",
Black = "#300A24",
Red = "#3465A4",
Red = "#CC0000",
Green = "#4E9A06",
Yellow = "#06989A",
Blue = "#CC0000",
Blue = "#3465A4",
Magenta = "#75507B",
Cyan = "#C4A000",
White = "#D3D7CF",
BrightBlack = "#554E53",
BrightRed = "#729FCF",
BrightRed = "#EF2929",
BrightGreen = "#8AE234",
BrightYellow = "#34E2E2",
BrightBlue = "#EF2929",
BrightBlue = "#729FCF",
BrightMagenta = "#AD7FA8",
BrightCyan = "#FCE94F",
BrightWhite = "#EEEEEE"
@@ -432,4 +436,4 @@ namespace FluentTerminal.App.Services.Implementation
return new[] { defaultXterm, powerShell, homebrew, tomorrow, ubuntu };
}
}
}
}
@@ -1,7 +1,7 @@
using System;
using System.Threading.Tasks;
using FluentTerminal.App.Services.Dialogs;
using FluentTerminal.App.Services.Dialogs;
using FluentTerminal.Models;
using System;
using System.Threading.Tasks;
namespace FluentTerminal.App.Services.Implementation
{
@@ -61,4 +61,4 @@ namespace FluentTerminal.App.Services.Implementation
return dialog.SelectProfile();
}
}
}
}
@@ -1,9 +1,9 @@
using System;
using FluentTerminal.Models;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using FluentTerminal.Models;
using Newtonsoft.Json;
namespace FluentTerminal.App.Services.Implementation
{
@@ -13,6 +13,16 @@ namespace FluentTerminal.App.Services.Implementation
public async Task<TerminalTheme> Parse(string fileName, Stream fileContent)
{
if (string.IsNullOrWhiteSpace(fileName))
{
throw new ArgumentNullException(nameof(fileName));
}
if (fileContent == null)
{
throw new ArgumentNullException(nameof(fileContent));
}
using (var streamReader = new StreamReader(fileContent))
{
var content = await streamReader.ReadToEndAsync();
@@ -25,4 +35,4 @@ namespace FluentTerminal.App.Services.Implementation
}
}
}
}
}
@@ -53,6 +53,16 @@ namespace FluentTerminal.App.Services.Implementation
public async Task<TerminalTheme> Parse(string fileName, Stream fileContent)
{
if (string.IsNullOrWhiteSpace(fileName))
{
throw new ArgumentNullException(nameof(fileName));
}
if (fileContent == null)
{
throw new ArgumentNullException(nameof(fileContent));
}
var node = PList.Load(fileContent) as DictionaryNode ?? throw new ParseThemeException("Root node was not a dictionary.");
return new TerminalTheme
@@ -124,4 +134,4 @@ namespace FluentTerminal.App.Services.Implementation
return (alpha / 256.0).ToString(CultureInfo.InvariantCulture);
}
}
}
}
@@ -1,6 +1,6 @@
using System;
using FluentTerminal.Models.Enums;
using System;
using System.Collections.Generic;
using FluentTerminal.Models.Enums;
namespace FluentTerminal.App.Services.Implementation
{
@@ -35,4 +35,4 @@ namespace FluentTerminal.App.Services.Implementation
throw new KeyNotFoundException($"No handler registered for command: {command}");
}
}
}
}
@@ -1,10 +1,10 @@
using System;
using FluentTerminal.Models;
using FluentTerminal.Models.Enums;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using FluentTerminal.Models;
using FluentTerminal.Models.Enums;
using Newtonsoft.Json;
namespace FluentTerminal.App.Services.Implementation
{
@@ -21,8 +21,11 @@ namespace FluentTerminal.App.Services.Implementation
private readonly IApplicationDataContainer _roamingSettings;
public event EventHandler<Guid> CurrentThemeChanged;
public event EventHandler<TerminalOptions> TerminalOptionsChanged;
public event EventHandler<ApplicationSettings> ApplicationSettingsChanged;
public event EventHandler KeyBindingsChanged;
public SettingsService(IDefaultValueProvider defaultValueProvider, ApplicationDataContainers containers)
@@ -118,9 +121,8 @@ namespace FluentTerminal.App.Services.Implementation
public IDictionary<Command, ICollection<KeyBinding>> GetKeyBindings()
{
var keyBindings = new Dictionary<Command, ICollection<KeyBinding>>();
foreach (var value in Enum.GetValues(typeof(Command)))
foreach (Command command in Enum.GetValues(typeof(Command)))
{
var command = (Command)value;
keyBindings.Add(command, _keyBindings.ReadValueFromJson<Collection<KeyBinding>>(command.ToString(), null) ?? _defaultValueProvider.GetDefaultKeyBindings(command));
}
@@ -130,15 +132,13 @@ namespace FluentTerminal.App.Services.Implementation
public void SaveKeyBindings(Command command, ICollection<KeyBinding> keyBindings)
{
_keyBindings.WriteValueAsJson(command.ToString(), keyBindings);
_roamingSettings.WriteValueAsJson(nameof(KeyBindings), keyBindings);
KeyBindingsChanged?.Invoke(this, System.EventArgs.Empty);
}
public void ResetKeyBindings()
{
foreach (var value in Enum.GetValues(typeof(Command)))
foreach (Command command in Enum.GetValues(typeof(Command)))
{
var command = (Command)value;
_keyBindings.WriteValueAsJson(command.ToString(), _defaultValueProvider.GetDefaultKeyBindings(command));
}
@@ -20,4 +20,4 @@ namespace FluentTerminal.App.Services.Implementation
return _parsers.FirstOrDefault(p => p.SupportedFileTypes.Contains(fileType));
}
}
}
}
@@ -1,10 +1,12 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using FluentTerminal.Models;
using FluentTerminal.Models;
using FluentTerminal.Models.Enums;
using FluentTerminal.Models.Requests;
using FluentTerminal.Models.Responses;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
namespace FluentTerminal.App.Services.Implementation
{
@@ -12,10 +14,14 @@ namespace FluentTerminal.App.Services.Implementation
{
private readonly ISettingsService _settingsService;
private IAppServiceConnection _appServiceConnection;
private Dictionary<int, Action<string>> _terminalOutputHandlers;
public event EventHandler<int> TerminalExited;
public TrayProcessCommunicationService(ISettingsService settingsService)
{
_settingsService = settingsService;
_terminalOutputHandlers = new Dictionary<int, Action<string>>();
}
public async Task<CreateTerminalResponse> CreateTerminal(TerminalSize size, ShellProfile shellProfile)
@@ -26,13 +32,7 @@ namespace FluentTerminal.App.Services.Implementation
Profile = shellProfile
};
var message = new Dictionary<string, string>
{
{ MessageKeys.Type, MessageTypes.CreateTerminalRequest },
{ MessageKeys.Content, JsonConvert.SerializeObject(request) }
};
var responseMessage = await _appServiceConnection.SendMessageAsync(message);
var responseMessage = await _appServiceConnection.SendMessageAsync(CreateMessage(request));
return JsonConvert.DeserializeObject<CreateTerminalResponse>(responseMessage[MessageKeys.Content]);
}
@@ -40,9 +40,36 @@ namespace FluentTerminal.App.Services.Implementation
public Task Initialize(IAppServiceConnection appServiceConnection)
{
_appServiceConnection = appServiceConnection;
_appServiceConnection.MessageReceived += OnMessageReceived;
return UpdateToggleWindowKeyBindings();
}
private void OnMessageReceived(object sender, IDictionary<string, string> e)
{
var messageType = e[MessageKeys.Type];
var messageContent = e[MessageKeys.Content];
if (messageType == nameof(DisplayTerminalOutputRequest))
{
var request = JsonConvert.DeserializeObject<DisplayTerminalOutputRequest>(messageContent);
if (_terminalOutputHandlers.ContainsKey(request.TerminalId))
{
_terminalOutputHandlers[request.TerminalId].Invoke(request.Output);
}
else
{
Debug.WriteLine("output was not handled: " + request.Output);
}
}
else if (messageType == nameof(TerminalExitedRequest))
{
var request = JsonConvert.DeserializeObject<TerminalExitedRequest>(messageContent);
TerminalExited?.Invoke(this, request.TerminalId);
}
}
public Task ResizeTerminal(int id, TerminalSize size)
{
var request = new ResizeTerminalRequest
@@ -51,13 +78,12 @@ namespace FluentTerminal.App.Services.Implementation
NewSize = size
};
var message = new Dictionary<string, string>
{
{ MessageKeys.Type, MessageTypes.ResizeTerminalRequest },
{ MessageKeys.Content, JsonConvert.SerializeObject(request) }
};
return _appServiceConnection.SendMessageAsync(CreateMessage(request));
}
return _appServiceConnection.SendMessageAsync(message);
public void SubscribeForTerminalOutput(int terminalId, Action<string> callback)
{
_terminalOutputHandlers[terminalId] = callback;
}
public Task UpdateToggleWindowKeyBindings()
@@ -69,13 +95,37 @@ namespace FluentTerminal.App.Services.Implementation
KeyBindings = keyBindings
};
var message = new Dictionary<string, string>
return _appServiceConnection.SendMessageAsync(CreateMessage(request));
}
public Task WriteText(int id, string text)
{
var request = new WriteTextRequest
{
{ MessageKeys.Type, MessageTypes.SetToggleWindowKeyBindingsRequest },
{ MessageKeys.Content, JsonConvert.SerializeObject(request) }
TerminalId = id,
Text = text
};
return _appServiceConnection.SendMessageAsync(message);
return _appServiceConnection.SendMessageAsync(CreateMessage(request));
}
public Task CloseTerminal(int terminalId)
{
var request = new TerminalExitedRequest
{
TerminalId = terminalId
};
return _appServiceConnection.SendMessageAsync(CreateMessage(request));
}
private IDictionary<string, string> CreateMessage(object content)
{
return new Dictionary<string, string>
{
[MessageKeys.Type] = content.GetType().Name,
[MessageKeys.Content] = JsonConvert.SerializeObject(content)
};
}
}
}
}
@@ -18,4 +18,4 @@ namespace FluentTerminal.App.Services.Utilities
return value.ToString();
}
}
}
}
@@ -24,4 +24,4 @@ namespace FluentTerminal.App.Services.Utilities
return property;
}
}
}
}
@@ -5,6 +5,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Fleck" Version="1.0.3" />
<PackageReference Include="MvvmLightLibs" Version="5.4.1" />
</ItemGroup>
+27 -47
View File
@@ -13,23 +13,18 @@ namespace FluentTerminal.App.ViewModels
{
public class MainViewModel : ViewModelBase
{
private readonly IApplicationView _applicationView;
private readonly IClipboardService _clipboardService;
private readonly IDialogService _dialogService;
private readonly IDispatcherTimer _dispatcherTimer;
private readonly IKeyboardCommandService _keyboardCommandService;
private readonly ISettingsService _settingsService;
private readonly ITrayProcessCommunicationService _trayProcessCommunicationService;
private ApplicationSettings _applicationSettings;
private string _background;
private double _backgroundOpacity;
private int _nextTerminalId;
private TerminalViewModel _selectedTerminal;
private string _title;
private readonly IApplicationView _applicationView;
private readonly IDispatcherTimer _dispatcherTimer;
private readonly IClipboardService _clipboardService;
public event EventHandler Closed;
public event EventHandler NewWindowRequested;
public event EventHandler ShowSettingsRequested;
private TabsPosition _tabsPosition;
public MainViewModel(ISettingsService settingsService, ITrayProcessCommunicationService trayProcessCommunicationService, IDialogService dialogService, IKeyboardCommandService keyboardCommandService,
IApplicationView applicationView, IDispatcherTimer dispatcherTimer, IClipboardService clipboardService)
@@ -57,24 +52,19 @@ namespace FluentTerminal.App.ViewModels
Background = currentTheme.Colors.Background;
BackgroundOpacity = options.BackgroundOpacity;
_applicationSettings = _settingsService.GetApplicationSettings();
TabsPosition = _applicationSettings.TabsPosition;
AddTerminalCommand = new RelayCommand(() => AddTerminal(null, false));
ShowSettingsCommand = new RelayCommand(ShowSettings);
Terminals.CollectionChanged += OnTerminalsCollectionChanged;
Title = "Fluent Terminal";
_applicationView.CloseRequested += OnCloseRequest;
}
private async void OnTerminalOptionsChanged(object sender, TerminalOptions e)
{
await _applicationView.RunOnDispatcherThread(() =>
{
BackgroundOpacity = e.BackgroundOpacity;
});
}
public event EventHandler Closed;
public event EventHandler NewWindowRequested;
public event EventHandler ShowSettingsRequested;
public RelayCommand AddTerminalCommand { get; }
@@ -112,14 +102,14 @@ namespace FluentTerminal.App.ViewModels
public RelayCommand ShowSettingsCommand { get; }
public ObservableCollection<TerminalViewModel> Terminals { get; } = new ObservableCollection<TerminalViewModel>();
public string Title
public TabsPosition TabsPosition
{
get => _title;
set => Set(ref _title, value);
get => _tabsPosition;
set => Set(ref _tabsPosition, value);
}
public ObservableCollection<TerminalViewModel> Terminals { get; } = new ObservableCollection<TerminalViewModel>();
public Task AddTerminal(string startupDirectory, bool showProfileSelection)
{
return _applicationView.RunOnDispatcherThread(async () =>
@@ -139,10 +129,9 @@ namespace FluentTerminal.App.ViewModels
profile = _settingsService.GetDefaultShellProfile();
}
var terminal = new TerminalViewModel(GetNextTerminalId(), _settingsService, _trayProcessCommunicationService, _dialogService, _keyboardCommandService,
var terminal = new TerminalViewModel(_settingsService, _trayProcessCommunicationService, _dialogService, _keyboardCommandService,
_applicationSettings, startupDirectory, profile, _applicationView, _dispatcherTimer, _clipboardService);
terminal.CloseRequested += OnTerminalCloseRequested;
terminal.TitleChanged += OnTitleChanged;
terminal.Closed += OnTerminalCloseRequested;
Terminals.Add(terminal);
SelectedTerminal = terminal;
@@ -162,11 +151,6 @@ namespace FluentTerminal.App.ViewModels
SelectedTerminal?.CloseCommand.Execute(null);
}
private int GetNextTerminalId()
{
return _nextTerminalId++;
}
private void NewWindow()
{
NewWindowRequested?.Invoke(this, EventArgs.Empty);
@@ -174,7 +158,11 @@ namespace FluentTerminal.App.ViewModels
private async void OnApplicationSettingsChanged(object sender, ApplicationSettings e)
{
await _applicationView.RunOnDispatcherThread(() => _applicationSettings = e);
await _applicationView.RunOnDispatcherThread(() =>
{
_applicationSettings = e;
TabsPosition = e.TabsPosition;
});
}
private async Task OnCloseRequest(object sender, CancelableEventArgs e)
@@ -228,20 +216,12 @@ namespace FluentTerminal.App.ViewModels
}
}
private void OnTerminalsCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
private async void OnTerminalOptionsChanged(object sender, TerminalOptions e)
{
if (Terminals.Any())
await _applicationView.RunOnDispatcherThread(() =>
{
Title = Terminals.First().Title;
}
}
private void OnTitleChanged(object sender, string e)
{
if (sender is TerminalViewModel terminal && Terminals.First() == terminal)
{
Title = e;
}
BackgroundOpacity = e.BackgroundOpacity;
});
}
private void SelectNextTab()
@@ -262,7 +242,7 @@ namespace FluentTerminal.App.ViewModels
{
ShowSettingsRequested?.Invoke(this, EventArgs.Empty);
}
private void ToggleFullScreen()
{
_applicationView.ToggleFullScreen();
@@ -14,6 +14,7 @@ namespace FluentTerminal.App.ViewModels.Settings
private readonly ISettingsService _settingsService;
private readonly ApplicationSettings _applicationSettings;
private bool _editingNewTerminalLocation;
private bool _editingTabsPosition;
public GeneralPageViewModel(ISettingsService settingsService, IDialogService dialogService, IDefaultValueProvider defaultValueProvider)
{
@@ -99,9 +100,39 @@ namespace FluentTerminal.App.ViewModels.Settings
set => NewTerminalLocation = NewTerminalLocation.Window;
}
public TabsPosition TabsPosition
{
get => _applicationSettings.TabsPosition;
set
{
if (_applicationSettings.TabsPosition != value && !_editingTabsPosition)
{
_editingTabsPosition = true;
_applicationSettings.TabsPosition = value;
_settingsService.SaveApplicationSettings(_applicationSettings);
RaisePropertyChanged();
RaisePropertyChanged(nameof(TopIsSelected));
RaisePropertyChanged(nameof(BottomIsSelected));
_editingTabsPosition = false;
}
}
}
public bool TopIsSelected
{
get => TabsPosition == TabsPosition.Top;
set => TabsPosition = TabsPosition.Top;
}
public bool BottomIsSelected
{
get => TabsPosition == TabsPosition.Bottom;
set => TabsPosition = TabsPosition.Bottom;
}
private async Task RestoreDefaults()
{
var result = await _dialogService.ShowMessageDialogAsnyc("Please confirm", "Are you sure you want to restore the general settings?", DialogButton.OK, DialogButton.Cancel).ConfigureAwait(false);
var result = await _dialogService.ShowMessageDialogAsnyc("Please confirm", "Are you sure you want to restore the general settings?", DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
if (result == DialogButton.OK)
{
@@ -0,0 +1,135 @@
using FluentTerminal.App.Services;
using FluentTerminal.Models;
using FluentTerminal.Models.Enums;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace FluentTerminal.App.ViewModels.Settings
{
public class MousePageViewModel : ViewModelBase
{
private readonly ISettingsService _settingsService;
private readonly IDialogService _dialogService;
private readonly IDefaultValueProvider _defaultValueProvider;
private readonly ApplicationSettings _applicationSettings;
private bool _editingMouseRightClickAction;
private bool _editingMouseMiddleClickAction;
public MousePageViewModel(ISettingsService settingsService, IDialogService dialogService, IDefaultValueProvider defaultValueProvider)
{
_settingsService = settingsService;
_dialogService = dialogService;
_defaultValueProvider = defaultValueProvider;
_applicationSettings = _settingsService.GetApplicationSettings();
RestoreDefaultsCommand = new RelayCommand(async () => await RestoreDefaults().ConfigureAwait(false));
}
public bool CopyOnSelect
{
get => _applicationSettings.CopyOnSelect;
set
{
if (_applicationSettings.CopyOnSelect != value)
{
_applicationSettings.CopyOnSelect = value;
_settingsService.SaveApplicationSettings(_applicationSettings);
RaisePropertyChanged();
}
}
}
public MouseAction MouseRightClickAction
{
get => _applicationSettings.MouseRightClickAction;
set
{
if (_applicationSettings.MouseRightClickAction != value && !_editingMouseRightClickAction)
{
_editingMouseRightClickAction = true;
_applicationSettings.MouseRightClickAction = value;
_settingsService.SaveApplicationSettings(_applicationSettings);
RaisePropertyChanged();
RaisePropertyChanged(nameof(MouseRightClickNoneIsSelected));
RaisePropertyChanged(nameof(MouseRightClickContextMenuIsSelected));
RaisePropertyChanged(nameof(MouseRightClickPasteIsSelected));
_editingMouseRightClickAction = false;
}
}
}
public bool MouseRightClickNoneIsSelected
{
get => MouseRightClickAction == MouseAction.None;
set => MouseRightClickAction = MouseAction.None;
}
public bool MouseRightClickContextMenuIsSelected
{
get => MouseRightClickAction == MouseAction.ContextMenu;
set => MouseRightClickAction = MouseAction.ContextMenu;
}
public bool MouseRightClickPasteIsSelected
{
get => MouseRightClickAction == MouseAction.Paste;
set => MouseRightClickAction = MouseAction.Paste;
}
public MouseAction MouseMiddleClickAction
{
get => _applicationSettings.MouseMiddleClickAction;
set
{
if (_applicationSettings.MouseMiddleClickAction != value && !_editingMouseMiddleClickAction)
{
_editingMouseMiddleClickAction = true;
_applicationSettings.MouseMiddleClickAction = value;
_settingsService.SaveApplicationSettings(_applicationSettings);
RaisePropertyChanged();
RaisePropertyChanged(nameof(MouseMiddleClickNoneIsSelected));
RaisePropertyChanged(nameof(MouseMiddleClickContextMenuIsSelected));
RaisePropertyChanged(nameof(MouseMiddleClickPasteIsSelected));
_editingMouseMiddleClickAction = false;
}
}
}
public bool MouseMiddleClickNoneIsSelected
{
get => MouseMiddleClickAction == MouseAction.None;
set => MouseMiddleClickAction = MouseAction.None;
}
public bool MouseMiddleClickContextMenuIsSelected
{
get => MouseMiddleClickAction == MouseAction.ContextMenu;
set => MouseMiddleClickAction = MouseAction.ContextMenu;
}
public bool MouseMiddleClickPasteIsSelected
{
get => MouseMiddleClickAction == MouseAction.Paste;
set => MouseMiddleClickAction = MouseAction.Paste;
}
public RelayCommand RestoreDefaultsCommand { get; }
private async Task RestoreDefaults()
{
var result = await _dialogService.ShowMessageDialogAsnyc("Please confirm", "Are you sure you want to restore the mouse settings?", DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
if (result == DialogButton.OK)
{
var defaults = _defaultValueProvider.GetDefaultApplicationSettings();
CopyOnSelect = defaults.CopyOnSelect;
MouseMiddleClickAction = defaults.MouseMiddleClickAction;
MouseRightClickAction = defaults.MouseRightClickAction;
}
}
}
}
@@ -15,7 +15,6 @@ namespace FluentTerminal.App.ViewModels.Settings
private readonly ISettingsService _settingsService;
private readonly IFileSystemService _fileSystemService;
private ShellProfileViewModel _selectedShellProfile;
public ProfilesPageViewModel(ISettingsService settingsService, IDialogService dialogService, IDefaultValueProvider defaultValueProvider, IFileSystemService fileSystemService)
{
@@ -211,4 +211,4 @@ namespace FluentTerminal.App.ViewModels.Settings
_terminalOptions = _settingsService.GetTerminalOptions();
}
}
}
}
@@ -15,6 +15,7 @@ namespace FluentTerminal.App.ViewModels
Shell = new ProfilesPageViewModel(settingsService, dialogService, defaultValueProvider, fileSystemService);
Terminal = new TerminalPageViewModel(settingsService, dialogService, defaultValueProvider, systemFontService);
Themes = new ThemesPageViewModel(settingsService, dialogService, defaultValueProvider, themeParserFactory, fileSystemService);
Mouse = new MousePageViewModel(settingsService, dialogService, defaultValueProvider);
}
public event EventHandler Closed;
@@ -24,6 +25,7 @@ namespace FluentTerminal.App.ViewModels
public ProfilesPageViewModel Shell { get; }
public TerminalPageViewModel Terminal { get; }
public ThemesPageViewModel Themes { get; }
public MousePageViewModel Mouse { get; }
public void Close()
{
@@ -47,6 +47,7 @@ namespace FluentTerminal.App.ViewModels
}
public event EventHandler Deleted;
public event EventHandler SetAsDefault;
public RelayCommand DeleteCommand { get; }
@@ -174,4 +175,4 @@ namespace FluentTerminal.App.ViewModels
}
}
}
}
}
@@ -1,4 +1,5 @@
using FluentTerminal.App.Services;
using Fleck;
using FluentTerminal.App.Services;
using FluentTerminal.Models;
using FluentTerminal.Models.Enums;
using GalaSoft.MvvmLight;
@@ -6,81 +7,84 @@ using GalaSoft.MvvmLight.Command;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace FluentTerminal.App.ViewModels
{
public class TerminalViewModel : ViewModelBase
{
private readonly IApplicationView _applicationView;
private readonly IClipboardService _clipboardService;
private readonly ManualResetEventSlim _connectedEvent;
private readonly IDialogService _dialogService;
private readonly IKeyboardCommandService _keyboardCommandService;
private readonly IDispatcherTimer _resizeOverlayTimer;
private readonly ISettingsService _settingsService;
private readonly ITrayProcessCommunicationService _trayProcessCommunicationService;
private ApplicationSettings _applicationSettings;
private string _resizeOverlayContent;
private bool _showResizeOverlay;
private readonly ShellProfile _shellProfile;
private readonly string _startupDirectory;
private readonly ITrayProcessCommunicationService _trayProcessCommunicationService;
private bool _isSelected;
private string _resizeOverlayContent;
private string _searchText;
private bool _showResizeOverlay;
private bool _showSearchPanel;
private int _terminalId;
private ITerminalView _terminalView;
private string _title;
private readonly ShellProfile _shellProfile;
private bool _showSearchPanel;
private string _searchText;
private bool _isSelected;
private readonly IApplicationView _applicationView;
private readonly IDispatcherTimer _resizeOverlayTimer;
private readonly IClipboardService _clipboardService;
private IWebSocketConnection _webSocket;
public TerminalViewModel(int id, ISettingsService settingsService, ITrayProcessCommunicationService trayProcessCommunicationService, IDialogService dialogService,
public TerminalViewModel(ISettingsService settingsService, ITrayProcessCommunicationService trayProcessCommunicationService, IDialogService dialogService,
IKeyboardCommandService keyboardCommandService, ApplicationSettings applicationSettings, string startupDirectory, ShellProfile shellProfile,
IApplicationView applicationView, IDispatcherTimer dispatcherTimer, IClipboardService clipboardService)
{
Id = id;
Title = DefaultTitle;
_connectedEvent = new ManualResetEventSlim(false);
_settingsService = settingsService;
_settingsService.CurrentThemeChanged += OnCurrentThemeChanged;
_settingsService.TerminalOptionsChanged += OnTerminalOptionsChanged;
_settingsService.ApplicationSettingsChanged += OnApplicationSettingsChanged;
_settingsService.KeyBindingsChanged += OnKeyBindingsChanged;
_trayProcessCommunicationService = trayProcessCommunicationService;
_trayProcessCommunicationService.TerminalExited += OnTerminalExited;
_dialogService = dialogService;
_keyboardCommandService = keyboardCommandService;
_applicationSettings = applicationSettings;
ApplicationSettings = applicationSettings;
_startupDirectory = startupDirectory;
_shellProfile = shellProfile;
_applicationView = applicationView;
_clipboardService = clipboardService;
_resizeOverlayTimer = dispatcherTimer;
_resizeOverlayTimer.Interval = new TimeSpan(0, 0, 2);
_resizeOverlayTimer.Tick += OnResizeOverlayTimerFinished;
CloseCommand = new RelayCommand(async () => await InvokeCloseRequested().ConfigureAwait(false));
CloseCommand = new RelayCommand(async () => await TryClose().ConfigureAwait(false));
FindNextCommand = new RelayCommand(async () => await FindNext().ConfigureAwait(false));
FindPreviousCommand = new RelayCommand(async () => await FindPrevious().ConfigureAwait(false));
CloseSearchPanelCommand = new RelayCommand(CloseSearchPanel);
}
private async void OnKeyBindingsChanged(object sender, EventArgs e)
{
var keyBindings = _settingsService.GetKeyBindings();
await _applicationView.RunOnDispatcherThread(async () =>
{
await _terminalView.ChangeKeyBindings(FlattenKeyBindings(keyBindings)).ConfigureAwait(false);
});
}
public ApplicationSettings ApplicationSettings { get; private set; }
public event EventHandler CloseRequested;
public event EventHandler<string> TitleChanged;
public event EventHandler Closed;
public RelayCommand CloseCommand { get; }
public RelayCommand FindPreviousCommand { get; }
public RelayCommand FindNextCommand { get; }
public RelayCommand CloseSearchPanelCommand { get; }
public string DefaultTitle { get; private set; } = string.Empty;
public RelayCommand FindNextCommand { get; }
public RelayCommand FindPreviousCommand { get; }
public bool Initialized { get; private set; }
public bool IsSelected
{
get => _isSelected;
@@ -88,30 +92,16 @@ namespace FluentTerminal.App.ViewModels
{
if (Set(ref _isSelected, value))
{
if (IsSelected)
{
_applicationView.Title = Title;
}
RaisePropertyChanged(nameof(IsUnderlined));
}
}
}
public bool IsUnderlined => IsSelected && _applicationSettings.UnderlineSelectedTab;
public int Id { get; }
public bool Initialized { get; private set; }
public string DefaultTitle { get; private set; } = "Fluent Terminal";
public string SearchText
{
get => _searchText;
set => Set(ref _searchText, value);
}
public bool ShowSearchPanel
{
get => _showSearchPanel;
set => Set(ref _showSearchPanel, value);
}
public bool IsUnderlined => IsSelected && ApplicationSettings.UnderlineSelectedTab;
public string ResizeOverlayContent
{
@@ -119,6 +109,12 @@ namespace FluentTerminal.App.ViewModels
set => Set(ref _resizeOverlayContent, value);
}
public string SearchText
{
get => _searchText;
set => Set(ref _searchText, value);
}
public bool ShowResizeOverlay
{
get => _showResizeOverlay;
@@ -136,6 +132,12 @@ namespace FluentTerminal.App.ViewModels
}
}
public bool ShowSearchPanel
{
get => _showSearchPanel;
set => Set(ref _showSearchPanel, value);
}
public string Title
{
get => _title;
@@ -143,16 +145,17 @@ namespace FluentTerminal.App.ViewModels
{
value = string.IsNullOrWhiteSpace(value) ? DefaultTitle : value;
if (Set(ref _title, value))
if (Set(ref _title, value) && IsSelected)
{
TitleChanged?.Invoke(this, Title);
_applicationView.Title = Title;
}
}
}
public void CloseView()
public async Task CloseView()
{
_terminalView.Close();
await _trayProcessCommunicationService.CloseTerminal(_terminalId).ConfigureAwait(true);
}
public Task FocusTerminal()
@@ -184,10 +187,25 @@ namespace FluentTerminal.App.ViewModels
_terminalView.TerminalTitleChanged += OnTerminalTitleChanged;
_terminalView.KeyboardCommandReceived += OnKeyboardCommandReceived;
_trayProcessCommunicationService.SubscribeForTerminalOutput(_terminalId, t =>
{
_connectedEvent.Wait();
_webSocket.Send(t);
});
DefaultTitle = response.ShellExecutableName;
Title = DefaultTitle;
await _terminalView.ConnectToSocket(response.WebSocketUrl).ConfigureAwait(true);
var webSocketUrl = "ws://127.0.0.1:" + response.Id;
var webSocketServer = new WebSocketServer(webSocketUrl);
webSocketServer.Start(socket =>
{
_webSocket = socket;
socket.OnOpen = () => _connectedEvent.Set();
socket.OnMessage = message => _trayProcessCommunicationService.WriteText(_terminalId, message);
});
await _terminalView.ConnectToSocket(webSocketUrl).ConfigureAwait(true);
Initialized = true;
}
else
@@ -198,26 +216,33 @@ namespace FluentTerminal.App.ViewModels
await FocusTerminal().ConfigureAwait(true);
}
private async Task InvokeCloseRequested()
private void CloseSearchPanel()
{
if (_applicationSettings.ConfirmClosingTabs)
{
var result = await _dialogService.ShowMessageDialogAsnyc("Please confirm", "Are you sure you want to close this tab?", DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
SearchText = string.Empty;
ShowSearchPanel = false;
_terminalView.FocusTerminal();
}
if (result == DialogButton.Cancel)
{
return;
}
}
private Task FindNext()
{
return _terminalView.FindNext(SearchText);
}
CloseRequested?.Invoke(this, EventArgs.Empty);
private Task FindPrevious()
{
return _terminalView.FindPrevious(SearchText);
}
private IEnumerable<KeyBinding> FlattenKeyBindings(IDictionary<Command, ICollection<KeyBinding>> keyBindings)
{
return keyBindings.Values.SelectMany(k => k);
}
private async void OnApplicationSettingsChanged(object sender, ApplicationSettings e)
{
await _applicationView.RunOnDispatcherThread(() =>
{
_applicationSettings = e;
ApplicationSettings = e;
RaisePropertyChanged(nameof(IsUnderlined));
});
}
@@ -231,6 +256,15 @@ namespace FluentTerminal.App.ViewModels
});
}
private async void OnKeyBindingsChanged(object sender, EventArgs e)
{
var keyBindings = _settingsService.GetKeyBindings();
await _applicationView.RunOnDispatcherThread(async () =>
{
await _terminalView.ChangeKeyBindings(FlattenKeyBindings(keyBindings)).ConfigureAwait(false);
});
}
private async void OnKeyboardCommandReceived(object sender, Command e)
{
if (e == Command.Copy)
@@ -243,7 +277,7 @@ namespace FluentTerminal.App.ViewModels
var content = await _clipboardService.GetText().ConfigureAwait(true);
if (content != null)
{
await _terminalView.Write(content).ConfigureAwait(true);
await _trayProcessCommunicationService.WriteText(_terminalId, content).ConfigureAwait(true);
}
}
else if (e == Command.Search)
@@ -266,6 +300,14 @@ namespace FluentTerminal.App.ViewModels
ShowResizeOverlay = false;
}
private void OnTerminalExited(object sender, int e)
{
if (e == _terminalId)
{
_applicationView.RunOnDispatcherThread(() => Closed?.Invoke(this, EventArgs.Empty));
}
}
private async void OnTerminalOptionsChanged(object sender, TerminalOptions e)
{
await _applicationView.RunOnDispatcherThread(async () =>
@@ -290,26 +332,20 @@ namespace FluentTerminal.App.ViewModels
Title = e;
}
private IEnumerable<KeyBinding> FlattenKeyBindings(IDictionary<Command, ICollection<KeyBinding>> keyBindings)
private async Task TryClose()
{
return keyBindings.Values.SelectMany(k => k);
}
if (ApplicationSettings.ConfirmClosingTabs)
{
var result = await _dialogService.ShowMessageDialogAsnyc("Please confirm", "Are you sure you want to close this tab?", DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
private Task FindNext()
{
return _terminalView.FindNext(SearchText);
}
if (result == DialogButton.Cancel)
{
return;
}
}
private Task FindPrevious()
{
return _terminalView.FindPrevious(SearchText);
}
private void CloseSearchPanel()
{
SearchText = string.Empty;
ShowSearchPanel = false;
_terminalView.FocusTerminal();
await _trayProcessCommunicationService.CloseTerminal(_terminalId).ConfigureAwait(true);
Closed?.Invoke(this, EventArgs.Empty);
}
}
}
@@ -1,4 +1,5 @@
using FluentTerminal.App.Services;
using FluentTerminal.Models;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
@@ -11,9 +12,24 @@ namespace FluentTerminal.App.Adapters
{
private readonly AppServiceConnection _appServiceConnection;
public event EventHandler<IDictionary<string, string>> MessageReceived;
public AppServiceConnectionAdapter(AppServiceConnection appServiceConnection)
{
_appServiceConnection = appServiceConnection;
_appServiceConnection.RequestReceived += OnRequestReceived;
}
private void OnRequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)
{
var messageType = (string)args.Request.Message[MessageKeys.Type];
var messageContent = (string)args.Request.Message[MessageKeys.Content];
MessageReceived?.Invoke(this, new Dictionary<string, string>
{
[MessageKeys.Type] = messageType,
[MessageKeys.Content] = messageContent
});
}
public async Task<IDictionary<string, string>> SendMessageAsync(IDictionary<string, string> message)
@@ -39,4 +55,4 @@ namespace FluentTerminal.App.Adapters
return null;
}
}
}
}
@@ -50,10 +50,9 @@ namespace FluentTerminal.App.Adapters
return _applicationDataContainer.Values.TryGetValue(key, out value);
}
public void WriteValueAsJson<T>(string name, T value)
{
_applicationDataContainer.Values[name] = JsonConvert.SerializeObject(value);
}
}
}
}
@@ -64,5 +64,4 @@ namespace FluentTerminal.App.Adapters
deferral.Complete();
}
}
}
}
@@ -1,5 +1,5 @@
using System;
using FluentTerminal.App.Services;
using FluentTerminal.App.Services;
using System;
using Windows.UI.Xaml;
namespace FluentTerminal.App.Adapters
@@ -39,4 +39,4 @@ namespace FluentTerminal.App.Adapters
_dispatcherTimer.Stop();
}
}
}
}
+2 -1
View File
@@ -29,6 +29,7 @@
<converters:IntToExtendedVirtualKeyConverter x:Key="IntToExtendedVirtualKeyConverter" />
<converters:BackgroundToApplicationThemeConverter x:Key="BackgroundToApplicationThemeConverter" />
<converters:StringToColorConverter x:Key="StringToColorConverter" />
<converters:EnumValueToVisibiltyConverter x:Key="EnumValueToVisibiltyConverter" />
</ResourceDictionary>
</Application.Resources>
</Application>
</Application>
Binary file not shown.
@@ -14,7 +14,8 @@ namespace FluentTerminal.App.Converters
if (value is string colorString)
{
color = colorString.ToColor();
} else if (value is Color)
}
else if (value is Color)
{
color = (Color)value;
}
@@ -32,4 +33,4 @@ namespace FluentTerminal.App.Converters
throw new NotImplementedException();
}
}
}
}
@@ -0,0 +1,23 @@
using System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Data;
namespace FluentTerminal.App.Converters
{
public class EnumValueToVisibiltyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value.GetType().IsEnum && Enum.TryParse(value.GetType(), parameter.ToString(), true, out object result))
{
return Equals(value, result) ? Visibility.Visible : Visibility.Collapsed;
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}
@@ -24,4 +24,4 @@ namespace FluentTerminal.App.Converters
return null;
}
}
}
}
@@ -21,4 +21,4 @@ namespace FluentTerminal.App.Converters
throw new NotImplementedException();
}
}
}
}
@@ -19,4 +19,4 @@ namespace FluentTerminal.App.Converters
throw new NotImplementedException();
}
}
}
}
@@ -25,4 +25,4 @@ namespace FluentTerminal.App.Converters
return null;
}
}
}
}
@@ -8,24 +8,24 @@ namespace FluentTerminal.App.Converters
{
internal class TerminalViewModelToViewConverter : IValueConverter
{
private readonly Dictionary<int, TerminalView> _viewDictionary;
private readonly Dictionary<TerminalViewModel, TerminalView> _viewDictionary;
public TerminalViewModelToViewConverter()
{
_viewDictionary = new Dictionary<int, TerminalView>();
_viewDictionary = new Dictionary<TerminalViewModel, TerminalView>();
}
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is TerminalViewModel terminal)
{
if (_viewDictionary.TryGetValue(terminal.Id, out TerminalView view))
if (_viewDictionary.TryGetValue(terminal, out TerminalView view))
{
return view;
}
var newView = new TerminalView(terminal);
_viewDictionary.Add(terminal.Id, newView);
_viewDictionary.Add(terminal, newView);
return newView;
}
return null;
@@ -24,4 +24,4 @@ namespace FluentTerminal.App.Converters
return null;
}
}
}
}
@@ -76,4 +76,4 @@
PreviewKeyDown="OnResetButtonPreviewKeyDown" />
</StackPanel>
</StackPanel>
</ContentDialog>
</ContentDialog>
@@ -1,13 +1,13 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using FluentTerminal.App.Services.Dialogs;
using FluentTerminal.App.Services.Dialogs;
using FluentTerminal.Models;
using GalaSoft.MvvmLight.Command;
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Windows.System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using System;
namespace FluentTerminal.App.Dialogs
{
@@ -92,16 +92,20 @@ namespace FluentTerminal.App.Dialogs
case VirtualKey.Control:
Ctrl = true;
break;
case VirtualKey.Shift:
Shift = true;
break;
case VirtualKey.Menu:
Alt = true;
break;
case VirtualKey.LeftWindows:
case VirtualKey.RightWindows:
Meta = true;
break;
default:
Key = (int)e.Key;
break;
@@ -14,4 +14,4 @@
DisplayMemberPath="Name"
ItemsSource="{x:Bind Profiles}"
SelectedItem="{x:Bind SelectedProfile, Mode=TwoWay}" />
</ContentDialog>
</ContentDialog>
@@ -1,8 +1,8 @@
using System;
using FluentTerminal.App.Services;
using FluentTerminal.App.Services;
using FluentTerminal.App.Services.Dialogs;
using FluentTerminal.App.Utilities;
using FluentTerminal.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
@@ -35,4 +35,4 @@ namespace FluentTerminal.App.Dialogs
return null;
}
}
}
}
+17 -1
View File
@@ -25,6 +25,7 @@
<GenerateAppInstallerFile>False</GenerateAppInstallerFile>
<AppInstallerUpdateFrequency>1</AppInstallerUpdateFrequency>
<AppInstallerCheckForUpdateFrequency>OnApplicationRun</AppInstallerCheckForUpdateFrequency>
<AppxSymbolPackageEnabled>True</AppxSymbolPackageEnabled>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<DebugSymbols>true</DebugSymbols>
@@ -112,6 +113,7 @@
</Compile>
<Compile Include="Behaviors\MiddleClickBehavior.cs" />
<Compile Include="Converters\BackgroundToApplicationThemeConverter.cs" />
<Compile Include="Converters\EnumValueToVisibiltyConverter.cs" />
<Compile Include="Converters\FalseToVisibleConverter.cs" />
<Compile Include="Converters\IntToExtendedVirtualKeyConverter.cs" />
<Compile Include="Converters\IntToVisibilityConverter.cs" />
@@ -141,6 +143,9 @@
<Compile Include="Views\SettingsPage.xaml.cs">
<DependentUpon>SettingsPage.xaml</DependentUpon>
</Compile>
<Compile Include="Views\SettingsPages\MouseSettings.xaml.cs">
<DependentUpon>MouseSettings.xaml</DependentUpon>
</Compile>
<Compile Include="Views\SettingsPages\ThemeSettings.xaml.cs">
<DependentUpon>ThemeSettings.xaml</DependentUpon>
</Compile>
@@ -156,6 +161,9 @@
<Compile Include="Views\SettingsPages\ShellProfileSettings.xaml.cs">
<DependentUpon>ShellProfileSettings.xaml</DependentUpon>
</Compile>
<Compile Include="Views\TabBar.xaml.cs">
<DependentUpon>TabBar.xaml</DependentUpon>
</Compile>
<Compile Include="Views\TerminalColorPicker.xaml.cs">
<DependentUpon>TerminalColorPicker.xaml</DependentUpon>
</Compile>
@@ -169,13 +177,13 @@
</AppxManifest>
<Content Include="Assets\settingsicons.ttf" />
<Content Include="Win32\Autofac.dll" />
<Content Include="Win32\Fleck.dll" />
<Content Include="Win32\FluentTerminal.Models.dll" />
<Content Include="Win32\FluentTerminal.Models.pdb" />
<Content Include="Win32\FluentTerminal.SystemTray.exe" />
<Content Include="Win32\FluentTerminal.SystemTray.pdb" />
<Content Include="Win32\GlobalHotKey.dll" />
<Content Include="Win32\Newtonsoft.Json.dll" />
<Content Include="Win32\RestSharp.dll" />
<Content Include="Win32\System.Data.Common.dll" />
<Content Include="Win32\System.Diagnostics.StackTrace.dll" />
<Content Include="Win32\System.Diagnostics.Tracing.dll" />
@@ -279,6 +287,10 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\SettingsPages\MouseSettings.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\SettingsPages\ThemeSettings.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
@@ -299,6 +311,10 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\TabBar.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\TerminalColorPicker.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest" xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" xmlns:uap5="http://schemas.microsoft.com/appx/manifest/uap/windows10/5" xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10" IgnorableNamespaces="uap mp desktop rescap">
<Identity Name="53621FSApps.FluentTerminal" Publisher="CN=BDC72197-B634-438A-B863-16DCB7D90C85" Version="0.1.11.0" />
<Identity Name="53621FSApps.FluentTerminal" Publisher="CN=BDC72197-B634-438A-B863-16DCB7D90C85" Version="0.1.13.0" />
<mp:PhoneIdentity PhoneProductId="f7225014-f405-4342-ad2e-ebf6ac60ea2c" PhonePublisherId="00000000-0000-0000-0000-000000000000" />
<Properties>
<DisplayName>Fluent Terminal</DisplayName>
@@ -1,8 +1,7 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("FluentTerminal.App")]
@@ -17,11 +16,11 @@ using System.Runtime.InteropServices;
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
+2 -5
View File
@@ -6,7 +6,7 @@
Laufzeitdirektiven sind hier dokumentiert: https://go.microsoft.com/fwlink/?LinkID=391919
Vollständige Aktivierung der Reflektion von "App1.MyClass" und allen zugehörigen öffentlichen/privaten Membern:
<Type Name="App1.MyClass" Dynamic="Required All"/>
<Type Name="App1.MyClass" Dynamic="Required All" />
Aktivierung der dynamischen Erstellung der spezifischen Instanziierung von "AppClass<T>" über "System.Int32":
<TypeInstantiation Name="App1.AppClass" Arguments="System.Int32" Activate="Required Public" />
@@ -22,10 +22,7 @@
Anwendungspaket. Die Sternchen sind keine Platzhalter.
-->
<Assembly Name="*Application*" Dynamic="Required All" />
<!-- Fügen Sie hier Ihre anwendungsspezifischen Laufzeitdirektiven ein. -->
</Application>
</Directives>
@@ -23,4 +23,4 @@ namespace FluentTerminal.App.Services
Clipboard.SetContent(dataPackage);
}
}
}
}
@@ -1,5 +1,4 @@
using Newtonsoft.Json;
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
@@ -59,4 +58,4 @@ namespace FluentTerminal.App.Services
}
}
}
}
}
@@ -10,4 +10,4 @@ namespace FluentTerminal.App.Services
return CanvasTextFormat.GetSystemFontFamilies();
}
}
}
}
+65 -2
View File
@@ -65,6 +65,70 @@
</Setter>
</Style>
<Style x:Key="TitleBarRepeatButtonStyle" TargetType="RepeatButton">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" />
<Setter Property="UseSystemFocusVisuals" Value="True" />
<Setter Property="Width" Value="47" />
<Setter Property="Height" Value="32" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="RepeatButton">
<Grid x:Name="RootGrid" Background="{TemplateBinding Background}">
<ContentPresenter
x:Name="ContentPresenter"
Padding="{TemplateBinding Padding}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
AutomationProperties.AccessibilityView="Raw"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
ContentTransitions="{TemplateBinding ContentTransitions}" />
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="RootGrid" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="Transparent" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="PointerOver">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="RootGrid" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemListLowColor}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="RootGrid" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemListMediumColor}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Disabled">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlDisabledBaseLowBrush}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="ColorPreviewButtonStyle" TargetType="Button">
<Setter Property="Width" Value="24" />
<Setter Property="Height" Value="24" />
@@ -95,5 +159,4 @@
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
</ResourceDictionary>
@@ -46,4 +46,4 @@ namespace FluentTerminal.App.Utilities
}
}
}
}
}
@@ -48,10 +48,13 @@ namespace FluentTerminal.App.Utilities
{
case "SystemBaseHighColor":
return new Color { A = 0xFF, R = 0x00, G = 0x00, B = 0x00 };
case "SystemListLowColor":
return new Color { A = 0x19, R = 0x00, G = 0x00, B = 0x00 };
case "SystemListMediumColor":
return new Color { A = 0x33, R = 0x00, G = 0x00, B = 0x00 };
default:
return null;
}
@@ -62,14 +65,17 @@ namespace FluentTerminal.App.Utilities
{
case "SystemBaseHighColor":
return new Color { A = 0xFF, R = 0xFF, G = 0xFF, B = 0xFF };
case "SystemListLowColor":
return new Color { A = 0x19, R = 0xFF, G = 0xFF, B = 0xFF };
case "SystemListMediumColor":
return new Color { A = 0x33, R = 0xFF, G = 0xFF, B = 0xFF };
default:
return null;
}
}
}
}
}
}
@@ -92,4 +92,4 @@
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</UserControl>
</UserControl>
+52 -97
View File
@@ -2,13 +2,10 @@
x:Class="FluentTerminal.App.Views.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:behaviors="using:FluentTerminal.App.Behaviors"
xmlns:converters="using:FluentTerminal.App.Converters"
xmlns:core="using:Microsoft.Xaml.Interactions.Core"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewmodels="using:FluentTerminal.App.ViewModels"
xmlns:views="using:FluentTerminal.App.Views"
RequestedTheme="{x:Bind ViewModel.Background, Mode=OneWay, Converter={StaticResource BackgroundToApplicationThemeConverter}}"
mc:Ignorable="d">
<Page.Resources>
@@ -25,113 +22,64 @@
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Rectangle x:Name="TitleBar" Fill="Transparent" />
<Grid
Grid.Row="0"
x:Name="TitleBarContents"
Height="{Binding CoreTitleBarHeight, Mode=OneWay}"
Margin="{Binding CoreTitleBarPadding, Mode=OneWay}"
SizeChanged="Grid_SizeChanged">
Margin="{Binding CoreTitleBarPadding, Mode=OneWay}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="0"
IsHitTestVisible="False"
Style="{StaticResource WindowTitleTextBlockStyle}"
Text="{x:Bind ViewModel.Title, Mode=OneWay}"
Visibility="{x:Bind ViewModel.Terminals.Count, Mode=OneWay, Converter={StaticResource OneToVisibleConverter}}" />
<ListView
x:Name="Tabs"
Grid.Column="0"
AllowDrop="True"
CanDragItems="True"
CanReorderItems="True"
ItemsSource="{x:Bind ViewModel.Terminals}"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollMode="Auto"
SelectedItem="{x:Bind ViewModel.SelectedTerminal, Mode=TwoWay}"
Visibility="{x:Bind ViewModel.Terminals.Count, Mode=OneWay, Converter={StaticResource OneToCollapsedConverter}}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="viewmodels:TerminalViewModel">
<Grid
Height="32"
Margin="0"
VerticalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Interactivity:Interaction.Behaviors>
<behaviors:MiddleClickBehavior>
<behaviors:MiddleClickBehavior.Actions>
<core:InvokeCommandAction Command="{x:Bind CloseCommand}" />
</behaviors:MiddleClickBehavior.Actions>
</behaviors:MiddleClickBehavior>
</Interactivity:Interaction.Behaviors>
<Border
Grid.ColumnSpan="3"
Height="3"
VerticalAlignment="Bottom"
Background="{ThemeResource SystemAccentColor}"
Visibility="{x:Bind IsUnderlined, Mode=OneWay, Converter={StaticResource TrueToVisibleConverter}}" />
<TextBlock
Grid.Column="0"
Margin="12,8,6,0"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{x:Bind Title, Mode=OneWay}" />
<Button
Grid.Column="2"
Width="32"
Command="{x:Bind CloseCommand}"
Style="{StaticResource TitleBarButtonStyle}"
ToolTipService.ToolTip="Close">
<Viewbox Width="12" Height="12">
<SymbolIcon Symbol="Cancel" />
</Viewbox>
</Button>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="MinWidth" Value="24" />
<Setter Property="MinHeight" Value="32" />
<Setter Property="Padding" Value="0" />
<Setter Property="VerticalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<ItemsStackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
<Button
x:Name="Hamburger"
Height="32"
Style="{StaticResource TitleBarButtonStyle}">
<Button.Content>
<Viewbox Width="16" Height="16">
<FontIcon FontFamily="Segoe MDL2 Assets" Glyph="&#xE700;" />
</Viewbox>
</Button.Content>
<Button.Flyout>
<MenuFlyout>
<MenuFlyoutItem Command="{x:Bind ViewModel.ShowSettingsCommand}" Text="Settings">
<MenuFlyoutItem.Icon>
<SymbolIcon Symbol="Setting" />
</MenuFlyoutItem.Icon>
</MenuFlyoutItem>
</MenuFlyout>
</Button.Flyout>
</Button>
<StackPanel
x:Name="TitleBarButtons"
Grid.Column="2"
Orientation="Horizontal">
Grid.Column="1"
Orientation="Horizontal"
Visibility="{x:Bind ViewModel.Terminals.Count, Mode=OneWay, Converter={StaticResource OneToVisibleConverter}}">
<TextBlock
IsHitTestVisible="False"
Style="{StaticResource WindowTitleTextBlockStyle}"
Text="{x:Bind ViewModel.SelectedTerminal.Title, Mode=OneWay}" />
<Button
x:Name="Add"
Command="{x:Bind ViewModel.AddTerminalCommand}"
Style="{StaticResource TitleBarButtonStyle}"
ToolTipService.ToolTip="New Tab">
<Viewbox Width="12" Height="12">
<SymbolIcon Symbol="Add" />
</Viewbox>
</Button>
<Button
HorizontalAlignment="Right"
Command="{x:Bind ViewModel.ShowSettingsCommand}"
Style="{StaticResource TitleBarButtonStyle}"
ToolTipService.ToolTip="Settings">
<Viewbox Width="12" Height="12">
<SymbolIcon Symbol="Setting" />
</Viewbox>
ToolTipService.ToolTip="New tab">
<Button.Content>
<Viewbox Width="12" Height="12">
<SymbolIcon Symbol="Add" />
</Viewbox>
</Button.Content>
</Button>
</StackPanel>
<views:TabBar
Grid.Column="1"
HorizontalAlignment="Left"
AddCommand="{x:Bind ViewModel.AddTerminalCommand}"
ItemsSource="{x:Bind ViewModel.Terminals, Mode=OneWay}"
SelectedItem="{x:Bind ViewModel.SelectedTerminal, Mode=TwoWay}"
Visibility="{x:Bind ViewModel.TabsPosition, Mode=OneWay, Converter={StaticResource EnumValueToVisibiltyConverter}, ConverterParameter=Top}" />
</Grid>
<ContentControl
x:Name="TerminalContainer"
@@ -139,5 +87,12 @@
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
Content="{x:Bind ViewModel.SelectedTerminal, Mode=OneWay, Converter={StaticResource TerminalViewModelToViewConverter}}" />
<views:TabBar
Grid.Row="2"
HorizontalAlignment="Left"
AddCommand="{x:Bind ViewModel.AddTerminalCommand}"
ItemsSource="{x:Bind ViewModel.Terminals, Mode=OneWay}"
SelectedItem="{x:Bind ViewModel.SelectedTerminal, Mode=TwoWay}"
Visibility="{x:Bind ViewModel.TabsPosition, Mode=OneWay, Converter={StaticResource EnumValueToVisibiltyConverter}, ConverterParameter=Bottom}" />
</Grid>
</Page>
+1 -11
View File
@@ -2,7 +2,6 @@
using FluentTerminal.App.ViewModels;
using System.ComponentModel;
using Windows.ApplicationModel.Core;
using Windows.UI;
using Windows.UI.Core;
using Windows.UI.ViewManagement;
using Windows.UI.Xaml;
@@ -84,7 +83,6 @@ namespace FluentTerminal.App.Views
UpdateLayoutMetrics();
}
private void UpdateLayoutMetrics()
{
if (PropertyChanged != null)
@@ -93,13 +91,5 @@ namespace FluentTerminal.App.Views
PropertyChanged(this, new PropertyChangedEventArgs(nameof(CoreTitleBarPadding)));
}
}
private void Grid_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (sender is Grid grid)
{
Tabs.MaxWidth = grid.ActualWidth - grid.ColumnDefinitions[2].ActualWidth;
}
}
}
}
}
@@ -57,6 +57,14 @@
ToolTipService.ToolTip="Keybindings" />
</NavigationViewItem.Icon>
</NavigationViewItem>
<NavigationViewItem Content="Mouse" Tag="mouse">
<NavigationViewItem.Icon>
<FontIcon
FontFamily="/Assets/settingsicons.ttf#settingsicons"
Glyph="g"
ToolTipService.ToolTip="Keybindings" />
</NavigationViewItem.Icon>
</NavigationViewItem>
<NavigationViewItem Content="Themes" Tag="themes">
<NavigationViewItem.Icon>
<FontIcon
@@ -1,18 +1,15 @@
using FluentTerminal.App.ViewModels;
using FluentTerminal.App.Utilities;
using FluentTerminal.App.ViewModels;
using FluentTerminal.App.Views.SettingsPages;
using System.Linq;
using System;
using System.Threading.Tasks;
using Windows.ApplicationModel.Core;
using Windows.Foundation;
using Windows.UI;
using Windows.UI.Core;
using Windows.UI.Core.Preview;
using Windows.UI.ViewManagement;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
using FluentTerminal.App.Utilities;
namespace FluentTerminal.App.Views
{
@@ -106,6 +103,10 @@ namespace FluentTerminal.App.Views
case "keyBindings":
ContentFrame.Navigate(typeof(KeyBindingSettings), ViewModel.KeyBindings);
break;
case "mouse":
ContentFrame.Navigate(typeof(MouseSettings), ViewModel.Mouse);
break;
}
}
@@ -36,11 +36,25 @@
</Grid>
<StackPanel Grid.Row="2" Margin="24,0,0,0">
<TextBlock Margin="0,0,0,8" Text="Open new terminals as" />
<RadioButton Content="Tabs" IsChecked="{x:Bind ViewModel.TabIsSelected, Mode=TwoWay}" />
<RadioButton
Content="Tabs"
GroupName="NewTerminalLocation"
IsChecked="{x:Bind ViewModel.TabIsSelected, Mode=TwoWay}" />
<RadioButton
Margin="0,0,0,24"
Content="Windows"
GroupName="NewTerminalLocation"
IsChecked="{x:Bind ViewModel.WindowIsSelected, Mode=TwoWay}" />
<TextBlock Margin="0,0,0,8" Text="Show tabs on" />
<RadioButton
Content="Top"
GroupName="TabsPosition"
IsChecked="{x:Bind ViewModel.TopIsSelected, Mode=TwoWay}" />
<RadioButton
Margin="0,0,0,24"
Content="Bottom"
GroupName="TabsPosition"
IsChecked="{x:Bind ViewModel.BottomIsSelected, Mode=TwoWay}" />
<ToggleSwitch
Margin="0,0,0,24"
Header="Underline selected tab"
@@ -50,7 +64,6 @@
Header="Ask before closing windows"
IsOn="{x:Bind ViewModel.ConfirmClosingWindows, Mode=TwoWay}" />
<ToggleSwitch Header="Ask before closing tabs" IsOn="{x:Bind ViewModel.ConfirmClosingTabs, Mode=TwoWay}" />
</StackPanel>
</Grid>
</Page>
</Page>
@@ -21,4 +21,4 @@ namespace FluentTerminal.App.Views.SettingsPages
}
}
}
}
}
@@ -52,4 +52,4 @@
</ItemsControl>
</ScrollViewer>
</Grid>
</Page>
</Page>
@@ -35,4 +35,4 @@ namespace FluentTerminal.App.Views.SettingsPages
}
}
}
}
}
@@ -0,0 +1,74 @@
<Page
x:Class="FluentTerminal.App.Views.SettingsPages.MouseSettings"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
mc:Ignorable="d">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="32" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Grid.Row="1" Margin="24,00,0,24">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock
Margin="0,10,0,0"
VerticalAlignment="Center"
FontSize="28"
Style="{StaticResource TitleTextBlockStyle}"
Text="Mouse" />
<CommandBar
Grid.Column="1"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Background="{ThemeResource SystemControlBackgroundAltHighBrush}"
DefaultLabelPosition="Right">
<AppBarButton
Command="{x:Bind ViewModel.RestoreDefaultsCommand}"
Icon="Undo"
Label="Restore Defaults" />
</CommandBar>
</Grid>
<StackPanel Grid.Row="2" Margin="24,0,0,0">
<ToggleSwitch
Margin="0,0,0,24"
Header="Copy on select"
IsOn="{x:Bind ViewModel.CopyOnSelect, Mode=TwoWay}" />
<TextBlock Margin="0,0,0,8" Text="Middle button action" />
<RadioButton
Content="Nothing"
GroupName="MiddleButton"
IsChecked="{x:Bind ViewModel.MouseMiddleClickNoneIsSelected, Mode=TwoWay}" />
<RadioButton
Content="Context menu"
GroupName="MiddleButton"
IsChecked="{x:Bind ViewModel.MouseMiddleClickContextMenuIsSelected, Mode=TwoWay}" />
<RadioButton
Margin="0,0,0,24"
Content="Paste"
GroupName="MiddleButton"
IsChecked="{x:Bind ViewModel.MouseMiddleClickPasteIsSelected, Mode=TwoWay}" />
<TextBlock Margin="0,0,0,8" Text="Right button action" />
<RadioButton
Content="Nothing"
GroupName="RightButton"
IsChecked="{x:Bind ViewModel.MouseRightClickNoneIsSelected, Mode=TwoWay}" />
<RadioButton
Content="Context menu"
GroupName="RightButton"
IsChecked="{x:Bind ViewModel.MouseRightClickContextMenuIsSelected, Mode=TwoWay}" />
<RadioButton
Margin="0,0,0,24"
Content="Paste"
GroupName="RightButton"
IsChecked="{x:Bind ViewModel.MouseRightClickPasteIsSelected, Mode=TwoWay}" />
</StackPanel>
</Grid>
</Page>
@@ -0,0 +1,24 @@
using FluentTerminal.App.ViewModels.Settings;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
namespace FluentTerminal.App.Views.SettingsPages
{
public sealed partial class MouseSettings : Page
{
public MouseSettings()
{
this.InitializeComponent();
}
public MousePageViewModel ViewModel { get; private set; }
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (e.Parameter is MousePageViewModel viewModel)
{
ViewModel = viewModel;
}
}
}
}
@@ -175,7 +175,6 @@
IsEnabled="{x:Bind InEditMode, Mode=OneWay}"
IsSpellCheckEnabled="False"
Text="{x:Bind Arguments, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
<StackPanel Visibility="{x:Bind InEditMode, Mode=OneWay, Converter={StaticResource FalseToVisibleConverter}}">
<TextBlock Style="{StaticResource SubtitleTextBlockStyle}" Text="{x:Bind Name, Mode=OneWay}" />
@@ -192,10 +191,9 @@
Header="Arguments"
Text="{x:Bind Arguments, Mode=OneWay}" />
</StackPanel>
</Grid>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
</Grid>
</Page>
</Page>
@@ -21,4 +21,4 @@ namespace FluentTerminal.App.Views.SettingsPages
}
}
}
}
}
@@ -98,4 +98,4 @@
</StackPanel>
</ScrollViewer>
</Grid>
</Page>
</Page>
@@ -304,4 +304,4 @@
</ContentControl.ContentTemplate>
</ContentControl>
</Grid>
</Page>
</Page>
@@ -1,6 +1,5 @@
using FluentTerminal.App.Utilities;
using FluentTerminal.App.ViewModels.Settings;
using Microsoft.Toolkit.Uwp.Helpers;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
@@ -46,4 +45,4 @@ namespace FluentTerminal.App.Views.SettingsPages
ContrastHelper.SetTitleBarButtonsForTheme(theme);
}
}
}
}
+144
View File
@@ -0,0 +1,144 @@
<UserControl
x:Class="FluentTerminal.App.Views.TabBar"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:behaviors="using:FluentTerminal.App.Behaviors"
xmlns:core="using:Microsoft.Xaml.Interactions.Core"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewmodels="using:FluentTerminal.App.ViewModels"
d:DesignHeight="32"
d:DesignWidth="200"
mc:Ignorable="d">
<Grid x:Name="Tabs" Visibility="{x:Bind ItemsSource.Count, Mode=OneWay, Converter={StaticResource OneToCollapsedConverter}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<RepeatButton
x:Name="ScrollLeftButton"
Grid.Column="0"
Width="24"
Padding="0,0,0,0"
Click="OnScrollLeftButtonTapped"
Style="{StaticResource TitleBarRepeatButtonStyle}"
ToolTipService.ToolTip="Scroll left"
Visibility="Collapsed">
<RepeatButton.Content>
<Viewbox Width="12" Height="12">
<FontIcon FontFamily="Segoe MDL2 Assets" Glyph="&#xE76B;" />
</Viewbox>
</RepeatButton.Content>
</RepeatButton>
<ScrollViewer
x:Name="ScrollViewer"
Grid.Column="1"
HorizontalScrollBarVisibility="Hidden"
HorizontalScrollMode="Enabled"
VerticalScrollBarVisibility="Hidden"
VerticalScrollMode="Disabled">
<ListView
x:Name="ListView"
AllowDrop="True"
CanDragItems="True"
CanReorderItems="True"
ItemsSource="{x:Bind ItemsSource}"
SelectedItem="{x:Bind SelectedItem, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="viewmodels:TerminalViewModel">
<RelativePanel
Height="32"
Margin="0"
VerticalAlignment="Stretch">
<Interactivity:Interaction.Behaviors>
<behaviors:MiddleClickBehavior>
<behaviors:MiddleClickBehavior.Actions>
<core:InvokeCommandAction Command="{x:Bind CloseCommand}" />
</behaviors:MiddleClickBehavior.Actions>
</behaviors:MiddleClickBehavior>
</Interactivity:Interaction.Behaviors>
<Border
Grid.ColumnSpan="3"
Height="3"
VerticalAlignment="Bottom"
Background="{ThemeResource SystemAccentColor}"
RelativePanel.AlignBottomWithPanel="True"
RelativePanel.AlignLeftWithPanel="True"
RelativePanel.AlignRightWithPanel="True"
Visibility="{x:Bind IsUnderlined, Mode=OneWay, Converter={StaticResource TrueToVisibleConverter}}" />
<TextBlock
Grid.Column="0"
Margin="12,8,6,0"
RelativePanel.AlignLeftWithPanel="True"
RelativePanel.LeftOf="CloseButton"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{x:Bind Title, Mode=OneWay}" />
<Button
x:Name="CloseButton"
Grid.Column="2"
Width="32"
Command="{x:Bind CloseCommand}"
RelativePanel.AlignRightWithPanel="True"
Style="{StaticResource TitleBarButtonStyle}"
ToolTipService.ToolTip="Close">
<Viewbox Width="12" Height="12">
<SymbolIcon Symbol="Cancel" />
</Viewbox>
</Button>
</RelativePanel>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.Template>
<ControlTemplate>
<ItemsPresenter />
</ControlTemplate>
</ListView.Template>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="MinWidth" Value="100" />
<Setter Property="MinHeight" Value="32" />
<Setter Property="Padding" Value="0" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<ItemsStackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
</ScrollViewer>
<RepeatButton
x:Name="ScrollRightButton"
Grid.Column="2"
Width="24"
Padding="0,0,0,0"
Click="OnScrollRightButtonTapped"
Style="{StaticResource TitleBarRepeatButtonStyle}"
ToolTipService.ToolTip="Scroll right"
Visibility="Collapsed">
<RepeatButton.Content>
<Viewbox Width="12" Height="12">
<FontIcon FontFamily="Segoe MDL2 Assets" Glyph="&#xE76C;" />
</Viewbox>
</RepeatButton.Content>
</RepeatButton>
<Button
x:Name="Add"
Grid.Column="3"
Command="{x:Bind AddCommand}"
Style="{StaticResource TitleBarButtonStyle}"
ToolTipService.ToolTip="New tab">
<Button.Content>
<Viewbox Width="12" Height="12">
<SymbolIcon Symbol="Add" />
</Viewbox>
</Button.Content>
</Button>
</Grid>
</UserControl>
+116
View File
@@ -0,0 +1,116 @@
using FluentTerminal.App.ViewModels;
using GalaSoft.MvvmLight.Command;
using System;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace FluentTerminal.App.Views
{
public sealed partial class TabBar : UserControl
{
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register(nameof(ItemsSource), typeof(ObservableCollection<TerminalViewModel>), typeof(TabBar), new PropertyMetadata(null));
public static readonly DependencyProperty MyPropertyProperty =
DependencyProperty.Register(nameof(AddCommand), typeof(RelayCommand), typeof(TabBar), new PropertyMetadata(null));
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register(nameof(SelectedItem), typeof(object), typeof(TabBar), new PropertyMetadata(null));
public TabBar()
{
InitializeComponent();
ScrollViewer.RegisterPropertyChangedCallback(ScrollViewer.ScrollableWidthProperty, OnScrollableWidthChanged);
ListView.SelectionChanged += OnListViewSelectionChanged;
ScrollLeftButton.Tapped += OnScrollLeftButtonTapped;
ScrollRightButton.Tapped += OnScrollRightButtonTapped;
}
public RelayCommand AddCommand
{
get { return (RelayCommand)GetValue(MyPropertyProperty); }
set { SetValue(MyPropertyProperty, value); }
}
public ObservableCollection<TerminalViewModel> ItemsSource
{
get { return (ObservableCollection<TerminalViewModel>)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public object SelectedItem
{
get { return GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
private void OnListViewSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var item = ListView.SelectedItem;
if (item != null)
{
var container = ListView.ContainerFromItem(item);
if (container != null)
{
((UIElement)container).StartBringIntoView();
SetScrollButtonsEnabledState();
}
else
{
Task.Run(async () =>
{
do
{
await Task.Delay(50);
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => container = ListView.ContainerFromItem(item));
}
while (container == null);
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
((UIElement)container).StartBringIntoView();
SetScrollButtonsEnabledState();
});
});
}
}
}
private void OnScrollableWidthChanged(DependencyObject sender, DependencyProperty property)
{
if (ScrollViewer.ScrollableWidth > 0)
{
ScrollLeftButton.Visibility = Visibility.Visible;
ScrollRightButton.Visibility = Visibility.Visible;
}
else
{
ScrollLeftButton.Visibility = Visibility.Collapsed;
ScrollRightButton.Visibility = Visibility.Collapsed;
}
}
private void OnScrollLeftButtonTapped(object sender, RoutedEventArgs e)
{
var offset = ScrollViewer.HorizontalOffset - 10;
ScrollViewer.ChangeView(offset, null, null);
SetScrollButtonsEnabledState();
}
private void OnScrollRightButtonTapped(object sender, RoutedEventArgs e)
{
var offset = ScrollViewer.HorizontalOffset + 10;
ScrollViewer.ChangeView(offset, null, null);
SetScrollButtonsEnabledState();
}
private void SetScrollButtonsEnabledState()
{
ScrollLeftButton.IsEnabled = ScrollViewer.HorizontalOffset > 0;
ScrollRightButton.IsEnabled = ScrollViewer.HorizontalOffset < ScrollViewer.ScrollableWidth;
}
}
}
@@ -31,4 +31,4 @@
</Flyout>
</Button.Flyout>
</Button>
</UserControl>
</UserControl>
+1 -1
View File
@@ -70,4 +70,4 @@
</Button>
</RelativePanel>
</Grid>
</UserControl>
</UserControl>
+47 -17
View File
@@ -2,6 +2,7 @@
using FluentTerminal.App.ViewModels;
using FluentTerminal.Models;
using FluentTerminal.Models.Enums;
using FluentTerminal.RuntimeComponent.Enums;
using FluentTerminal.RuntimeComponent.Interfaces;
using FluentTerminal.RuntimeComponent.WebAllowedObjects;
using Newtonsoft.Json;
@@ -12,7 +13,6 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Windows.ApplicationModel.Core;
@@ -55,7 +55,9 @@ namespace FluentTerminal.App.Views
}
public event EventHandler<Command> KeyboardCommandReceived;
public event EventHandler<TerminalSize> TerminalSizeChanged;
public event EventHandler<string> TerminalTitleChanged;
public TerminalViewModel ViewModel { get; }
@@ -146,12 +148,6 @@ namespace FluentTerminal.App.Views
_dispatcherJobs.Add(() => TerminalTitleChanged?.Invoke(this, title));
}
public Task Write(string text)
{
text = Convert.ToBase64String(Encoding.UTF8.GetBytes(text));
return ExecuteScriptAsync($"paste('{text}')");
}
public Task FindNext(string searchText)
{
return ExecuteScriptAsync($"findNext('{searchText}')");
@@ -214,16 +210,6 @@ namespace FluentTerminal.App.Views
await ViewModel.OnViewIsReady(this).ConfigureAwait(true);
}
public void OnRightClick(int x, int y, bool hasSelection)
{
_dispatcherJobs.Add(() =>
{
var flyout = (MenuFlyout)_webView.ContextFlyout;
_copyMenuItem.IsEnabled = hasSelection;
flyout.ShowAt(_webView, new Point(x, y));
});
}
private void Paste_Click(object sender, RoutedEventArgs e)
{
OnKeyboardCommand(nameof(Command.Paste));
@@ -265,5 +251,49 @@ namespace FluentTerminal.App.Views
{
SearchTextBox.Focus(FocusState.Programmatic);
}
public void OnMouseClick(MouseButton mouseButton, int x, int y, bool hasSelection)
{
_dispatcherJobs.Add(() =>
{
if (mouseButton == MouseButton.Middle)
{
if (ViewModel.ApplicationSettings.MouseMiddleClickAction == MouseAction.ContextMenu)
{
ShowContextMenu(x, y, hasSelection);
}
else if (ViewModel.ApplicationSettings.MouseMiddleClickAction == MouseAction.Paste)
{
OnKeyboardCommand(nameof(Command.Paste));
}
}
else if (mouseButton == MouseButton.Right)
{
if (ViewModel.ApplicationSettings.MouseRightClickAction == MouseAction.ContextMenu)
{
ShowContextMenu(x, y, hasSelection);
}
else if (ViewModel.ApplicationSettings.MouseRightClickAction == MouseAction.Paste)
{
OnKeyboardCommand(nameof(Command.Paste));
}
}
});
}
public void OnSelectionChanged(string selection)
{
if (ViewModel.ApplicationSettings.CopyOnSelect)
{
_dispatcherJobs.Add(() => OnKeyboardCommand(nameof(Command.Copy)));
}
}
private void ShowContextMenu(int x, int y, bool terminalHasSelection)
{
var flyout = (MenuFlyout)_webView.ContextFlyout;
_copyMenuItem.IsEnabled = terminalHasSelection;
flyout.ShowAt(_webView, new Point(x, y));
}
}
}
+3 -3
View File
@@ -3013,9 +3013,9 @@
"dev": true
},
"xterm": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/xterm/-/xterm-3.4.0.tgz",
"integrity": "sha512-eHH9CuKZOdKscBoUPmBxE1nTAuaKCMavcTQilyQf2u6MqB366BJP7SbkGWz+HZj7V5OjldFLLDLzlliqUzoiHQ=="
"version": "3.5.1",
"resolved": "https://registry.npmjs.org/xterm/-/xterm-3.5.1.tgz",
"integrity": "sha512-syTMd35T+gDXKkDfwZz8CC685OsSnRujXMO+3lYcD9U+dG3FShc+Ydx71IEjfYg7TYqD+qv6SU9KAKMuiAIiAg=="
},
"y18n": {
"version": "3.2.1",
Loaded 100 of 147 files, more files were not shown because too many files have changed in this diff. Show more