Compare commits

...
111 Commits
Author SHA1 Message Date
FelixSe 0733b1ab02 Initial adaption of VtNetCore.UWP 2019-03-03 00:29:00 +01:00
FelixSe 4e54668092 fix TerminalTheme Equals() 2019-03-02 19:23:18 +01:00
FelixSe 74abd4f448 Encapsulate xterm.js implementation in XtermTerminalView 2019-03-02 19:23:05 +01:00
Eric Cornelissen b85be454ce Improve settings window (#213)
* Update settings window layout with consistent margins

Updated the layout of the different views of the settings window so that
there is always margin at the bottom. In some cases this was achieved
using <StackPanels> for clarity and extensibility, grouping e.g. a title
and some radio options together with one unified margin.

Also made some small consistency changes to the .xaml files.

* Add width to Themes text input

* Normalize margins in settings

* Add default display values in Terminal settings

... for Arguments and Working Directory

* Generalize TemplateSelector for empty strings

* Add default display value for profile keybindings

* Update margins and fontsize in theme preview

* Update 'loading...' message in About

... to "Checking for updates..." and a spinner as suggested by
@skipmeister123

* No popup when cancelling profile edit no changes

* Delete profile when new and cancelled

* Open new and cloned themes in edit mode

... and make it so canceling deletes the theme.

* No popup when cancelling theme edit no changes

* Add resources for margin definitions

* Check for null Author in TerminalTheme

* Add resource for header margin definition

* Moved createdBy text to code behind
2019-02-28 21:09:52 +01:00
FelixSe 1f3dd72214 Improved text wrapping on window title 2019-02-28 20:44:00 +01:00
Eric Cornelissen c4fcd986c0 Add keybinding for configurable new window (#216)
* Add keybind to open new configurable window

* Close new configurable window on cancel

* renamed event args
2019-02-28 19:24:18 +01:00
FelixSe ea21d37089 Make InputBuffer more threadsafe 2019-02-25 21:10:39 +01:00
Paul Galbraith 2f133720cc Chocolatey install instructions (#209) 2019-02-19 07:30:43 +01:00
Paul Galbraith 250c90fa02 install option to force context menu integration without user interaction (#204) 2019-02-15 19:22:38 +01:00
Mike 7e2c5d2f45 Line ending translation (#172)
* On paste, the TerminalViewModel uses the ShellProfile to translate line endings.

Default line ending translation style is set to not modify the pasted contents, Next up, adding the radio button to shell profile.

* Added UI elements for choosing line ending style.

Using a converter to map between an enum and radio buttons, and another converter to map from enum to text based on the description of the enum value.

* Added "Paste without newlines" command.

* Adding missing converters to project file.

* A few fixes after cherry-picks.

* Adding missing Command property to PasteWithoutNewlines keybinding.

* Cleaned up some unnecessary property settings.

* fix #114 scrolling issue on presicion touchpads (#171)

* fix #114 scrolling issue on presicion touchpads, bounce scroll & pinch zoom

* rephrased comment

* removed css that enforced autohiding

* simplified js code handling scrollbar size

* Press enter to close an Input dialog (#177)

... As if the primary button is pressed.

* Renew certificate

* Update README.md

* Add option to use bold text in terminals (#183)

* Add option to use bold text in terminals

... as per #182

* Add BoldText to setting defaults

* Remove AppVeyor files

* Update README.md

* Add keybinding to change the (current) tab title (#193)

* Add keybinding for change the (current) tab title

Default binding is Ctrl+Shift+N

* Update change tab title default keybind to Ctrl+Shift+R

* Experimental ConPTY support (#199)

* First steps of ConPty support

* Added resize functionality

* fix cwd, args, hide console window

* Handle shell process exited

* Make ConPTY optional

* Fix automerge errors

* Accepted many XAML formatting changes, and changed command handling to switch.

* Fixed spaxcng on the settings page.

* adapted radio button binding to be in line with the rest of the application
2019-02-13 22:42:23 +01:00
FelixSe 9fed3fb192 Fix automerge errors 2019-02-09 23:06:45 +01:00
Felix 0d85f933c7 Experimental ConPTY support (#199)
* First steps of ConPty support

* Added resize functionality

* fix cwd, args, hide console window

* Handle shell process exited

* Make ConPTY optional
2019-02-08 22:12:29 +01:00
Eric Cornelissen f235ddf0f1 Add keybinding to change the (current) tab title (#193)
* Add keybinding for change the (current) tab title

Default binding is Ctrl+Shift+N

* Update change tab title default keybind to Ctrl+Shift+R
2019-02-08 21:55:23 +01:00
Felix 887cd45b4c Update README.md 2019-01-30 18:32:32 +01:00
FelixSe aac2f220d6 Remove AppVeyor files 2019-01-28 22:00:33 +01:00
Eric Cornelissen 90f15cc1e3 Add option to use bold text in terminals (#183)
* Add option to use bold text in terminals

... as per #182

* Add BoldText to setting defaults
2019-01-28 21:57:36 +01:00
Felix 292b66af4d Update README.md 2019-01-28 19:26:48 +01:00
FelixSe b6d835984c Renew certificate 2019-01-26 12:29:32 +01:00
Eric Cornelissen 369b5ddacf Press enter to close an Input dialog (#177)
... As if the primary button is pressed.
2019-01-18 21:20:56 +01:00
Mike 6be29f49d7 fix #114 scrolling issue on presicion touchpads (#171)
* fix #114 scrolling issue on presicion touchpads, bounce scroll & pinch zoom

* rephrased comment

* removed css that enforced autohiding

* simplified js code handling scrollbar size
2019-01-14 21:47:35 +01:00
FelixSe 8131140cb0 Increase version 2019-01-08 19:51:54 +01:00
FelixSe 304314230c update xterm to 3.10.1 2019-01-08 19:27:47 +01:00
FelixSe 0ce605f122 Fix DialogService tests 2019-01-07 22:46:26 +01:00
FelixSe d4aadd7dd4 Added option to change the title of a tab 2019-01-07 22:35:14 +01:00
FelixSe 3cf3e0a330 Themes can now be cloned 2019-01-07 19:37:21 +01:00
FelixSe ff2cfc36b6 Fix problems with selectAll, clear and Ctrl+C 2019-01-04 18:15:24 +01:00
FelixSe b118c66959 Increase version 2019-01-02 22:15:02 +01:00
FelixSe 131e3536f1 Fix theme not updating for profiles without theme 2018-12-30 22:56:11 +01:00
FelixSe b47b544065 Added option to restore preinstalled profiles 2018-12-30 19:00:24 +01:00
FelixSe b13c40e7e8 Clone profiles 2018-12-30 16:20:56 +01:00
Felix 4e47fcef72 Added option to specify theme in profile (#158) 2018-12-30 15:12:04 +01:00
Mike 8acf618fd4 Per-shell-profile keyboard shortcuts (#85)
* Added new command wrapper, abstracts static and dynamic commands

- All uses of the `Command` are replaced with either `AppCommand`, for a command that is static to the scope of the application, or a reference to an ICommand (specifically, an `EnumCommand<AppCommand>()` instance), which provides the abstract wrapper.
- Functionality seems intact.
- Next step is to have the MainViewModel add the per-shell commands via SettingsService.CreateShellKeyboardShortcuts(), and have the SettingsService.ParseCommandString() function parse the command strings passed around properly.
- Finally, add the keyboard chord input to each shell's settings page.

* First kick at adding a new terminal keyboard command.

- It isn't presented anywhere, but references to keybindings should be using the ICommand key as their identifier for recognizing anything, and when xterm passes back a keyboard command event, the SettingsService is asked to convert the string into an ICommand.

* Renaming ICommand to Command.

It's not longer an interface, so this makes more sense.

* Renaming back to Command, and new class is AbstractCommand.

Going to simplify the PR diff considerably.

* Shortcuts for default shells work. Need to get the shell profile edit UI elements working.

* Added keyboard shortcut UI elements to shell profile pages.

* New and deleted shell profiles propagate to all terminal views.

* A bit of cleanup.

* Camel-casing command text to fix Ctrl+C and copy behaviour.

* Fixed failing test.

The new on disk store uses a .ToString(), not the Enum description, since the command isn't an Enum, it's a class now in general, and those semantics make more sense.

* Adding default value for null keybindings in shell profiles.

* Merge branch 'shell_commands' of https://github.com/Riebart/FluentTerminal into pr/85

Improved default value for keybinding property

* Renamed KeyBinding collection to KeyBindings

* Use strings as Command parameter of KeyBinding

* Added missing Commands to keyBindings of default shellProfiles

* fix build and unit tests after merge with master

* improve UI

* Split OnShellProfileCollectionChanged event
2018-12-29 13:20:24 +01:00
Eric Cornelissen 49e09f3f8b Add "Show in system tray" option (#142)
* Add "Show in system tray" option to the SettingsView

* Implement "Show in system tray" requiring restart

* Add warning to restart program when toggling tray

* Update warning text for toggling tray option
2018-12-26 16:31:28 +01:00
Fauzie 9bf8284d9f Add Padding setting (#155)
* add padding setting

* `=` instead of `:`

Co-Authored-By: fauzie811 <fauzie811@yahoo.com>

* call setPadding in createTerminal function

* Add "px" suffix to padding slider tooltip

Based on this tutorial: 
https://www.codeproject.com/Tips/715438/Slider-Thumb-Tooltip

* term.fit() after setPadding()

* move back term.fit() below term.open()

* Changed max value, added throttling, moved converter
2018-12-26 14:41:16 +01:00
FelixSe 0f8aac81de add throttle for background opacity binding 2018-12-26 14:18:54 +01:00
FelixSe abec73b868 update xterm to 3.9.1 2018-12-25 15:50:07 +01:00
Zed 86486c99f2 fixed open here dir param. (#145) 2018-12-04 07:30:32 +01:00
Abhishek Goenka d9b7052e87 Prerequisite added (#139) 2018-11-19 17:18:44 +01:00
Meir Blachman 0efe105321 added missing test for KeyboardCommandService (#124)
added missing test for KeyboardCommandService for Command.ToggleWindow
2018-11-12 21:15:57 +01:00
Eric Cornelissen ed0dad960d Fix display issue when settings window is too narrow (#115)
Closes #100
2018-11-07 07:05:16 +01:00
Eric Cornelissen 2f58ed63de Add About page to the settings window (#103)
* create "About" page in settings

* add content to About page

* Get latest version from GitHub

* Add NotificationService to servies

* Enable actions on notifications

* Get latest version asynchroniously

* Handle when latest version is not found and clean code

* Remove old UpdateService

* Remove old NotificationService

* Add icon for about tab

* Update About page styling

* Make About menu item a footer menu item

* Update About page layout

Based on suggestions by @felixse

* Instatiate temporary navigation item only once

This seems to solve display issues when opening the "About" page multiple times where the main navigation list is being rerendered for no apparent reason.

* Revert unintended changes

* Added shortcut to about page in hamburger menu

* Move About page properties to the AboutPageViewModel

* Make "loading..." and "Not found" italic to indicate non-default behaviour

* move update check back to systemtray

* Made IUpdateService async
2018-10-28 13:55:36 +01:00
FelixSe cc7e069f11 Increase version 2018-10-20 14:51:50 +02:00
FelixSe 94a0eac58f Fixed tab width, Added option to indicate new output on background tabs 2018-10-20 12:20:41 +02:00
FelixSe 9af0556d6c Update xterm.js to 3.8.0 2018-10-12 07:39:52 +02:00
A.J. Ruckman b14ca2c4ed update winpty dependencies from current winpty master code (#95) 2018-10-09 22:27:26 +02:00
FelixSe 89c1a5a614 ExtendedVirtualKey now supports all OEM keys, and uses the qwerty default for description 2018-10-09 22:20:05 +02:00
Justin Gallagher 3e32f611e8 Add automatic install script (#92) 2018-10-08 22:02:58 +02:00
Mike 07e65ce2bf Clarifying installing and dev environment setup instructions. (#83)
* Clarifying installing vs dev environment setup.

* Adding details on installing the app, as well as dev dependencies.
2018-10-05 10:55:41 +02:00
Mike f2a48487f1 Return an empty string when attempting to paste non-text clipboard buffer (#86)
* Return an empty stirng when attempting to paste non-text clipboard buffer.

Returning null Task reference was causing a crash when attempting to paste a non-text buffer.

* Simplifying returned empty string task.
2018-10-01 21:40:18 +02:00
Mike c9d0cc7368 Tab theme colours (#73) 2018-09-10 07:50:20 +02:00
Mike eecc2e8243 Development environment documentation, and npm package bump (#68)
* Updating documentation and npm packages

- The webpack 3.10 was too old, as webpack-cli and webpack-command, required dependencies for building, need 4.x.x or 4.4.0 respectively.
- `npm-check-updates` (`npm t -g npm-check-updates`) run with `ncu -u` auto-bumped xterm.js to 3.6.0, and webpack to 4.16.5. Unfortunately this also reordered the file, so those changes aren't obvious.
- I also added webpack-cli as an explicit dependency.
- Updates to the readme to detail putting together a build environment.

* Noting that the package should be for sideloading

For dev, and non-published packages, the sideloading option is best.

* Adding dev StoreAssociation XML file for dev building.

* Cleaning up the packaging instructions.

* Changed instruction from rename to copy.

- Copy will not try to clobber the working directory of the template
file.

* Bumping xterm to 3.6.0, adding notes on installing built packages
2018-08-18 20:27:53 +02:00
Mike 5c1896cd3b Added support for Ctrl+<N> to switch to tab N. (#70) 2018-08-18 10:37:51 +02:00
Jakob Hermann Preusker e0cde7fe0f Fixed Issue #63: No scrolling in some pages (#66)
* Fixed Issue #63

* Requested changes to PR #66 done

* Fixed scrollbar position in themes/profiles
2018-08-17 07:48:15 +02:00
FelixSe 2d4d0bc23a Update version 2018-08-01 22:25:38 +02:00
FelixSe 9a5bff3d37 Update screenshots 2018-08-01 21:40:15 +02:00
FelixSe 28f212b7d6 Pin language version for services dll 2018-07-31 21:29:07 +02:00
FelixSe bfad59fa7a Added option to start SystemTray on windows startup 2018-07-31 21:13:29 +02:00
FelixSe f0e8f01d30 Added option to always show tabs 2018-07-28 23:02:42 +02:00
FelixSe 3ffd94e99d Clear selection after selecting with 'select to copy' enabled 2018-07-28 22:28:55 +02:00
FelixSe 1be49d27ab Default profile is now saved in local settings 2018-07-28 22:10:41 +02:00
FelixSe 4f43f64783 Fixed a bug that preventend toggle window from hiding the terminal 2018-07-20 23:38:46 +02:00
FelixSe e7997a5272 increase version 2018-07-19 23:52:45 +02:00
FelixSe 507138364e Improved handling of missing profiles/themes 2018-07-19 23:28:57 +02:00
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
cxfksword def3aa3683 Fix toggle window exception (#49) 2018-06-21 22:27:12 +02:00
FelixSe 77d9a42c36 Adapted KeyBoardCommandServiceTests to not use ToggleWindow command 2018-06-21 22:04:08 +02:00
cxfksword d395085064 Add support grave accent as hotkey (#48) 2018-06-21 21:29:48 +02:00
Felix 56e5d60dae Update README.md 2018-06-17 15:53:37 +02:00
FelixSe 780cbf64b1 Added AppVeyor CI 2018-06-17 15:52:31 +02:00
FelixSe 6b25d78ce6 Remove unused nuget package 2018-06-17 14:49:22 +02:00
FelixSe 53726e0e68 Moved ViewModels into a separate asembly 2018-06-17 14:34:20 +02:00
FelixSe 5e11e5d58b Move existing tests to xUnit test project 2018-06-16 23:33:11 +02:00
FelixSe 3e0417ce47 Move services to a separate assembly 2018-06-16 22:50:27 +02:00
Felix 417bc843b0 Added missing '#' in ubuntu theme 2018-06-16 10:00:54 +02:00
Felix 6d6bdcca3e Merge pull request #41 from Linard11/master
Add WSL as default profile & terminal colors used in Ubuntu Bash
2018-06-15 11:17:46 +02:00
Linard Hug b4bfe39586 Fix typo and cursor selection color 2018-06-15 10:35:16 +02:00
Linard Hug 940cb6f38b Add 'Ubuntu Bash' as default theme 2018-06-15 02:23:29 +02:00
Linard Hug 59c6a39458 Add WSL as default profile 2018-06-15 02:23:04 +02:00
FelixSe 05ce19ec8c Increase version number 2018-06-12 22:59:05 +02:00
FelixSe 7b2bd59365 Improve clear and selectAll behaviour 2018-06-12 22:39:44 +02:00
FelixSe 6a67883777 Revert updating Win2D.uwp 2018-06-12 22:22:23 +02:00
FelixSe 847e1c045a Added option to set scrollback limit, comands to select all and clear buffer, cleaned up keyBindings UI code 2018-06-11 23:11:38 +02:00
FelixSe e918c5492e Update dependencies 2018-06-11 22:04:03 +02:00
FelixSe 1aba3d4926 Fixed text margin in tabs and added option to underline the selected tab 2018-06-10 20:54:51 +02:00
FelixSe 5ad51b6bf7 Added 'Tomorrow' as default theme to showcase light themes 2018-06-10 20:27:28 +02:00
FelixSe 72cfe1168f Export themes 2018-06-10 20:17:46 +02:00
FelixSe 5a96a0387b Set application theme based on the terminal's background color 2018-06-10 15:04:41 +02:00
FelixSe 00a5b6377d Fix selection color in imported themes 2018-06-10 12:14:25 +02:00
FelixSe f610aa8a0f Import iTerm themes 2018-06-09 19:34:32 +02:00
FelixSe c32342bca8 Remember window size and position 2018-06-08 21:42:12 +02:00
FelixSe 84af36f6bc Added fullscreen mode 2018-06-07 22:18:43 +02:00
FelixSe f56465f54a Improve storage of keyBindings 2018-06-07 21:57:45 +02:00
259 changed files with 16930 additions and 6947 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);
}
}
}
@@ -0,0 +1,96 @@
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
{
public class DialogServiceTests
{
private readonly Fixture _fixture;
public DialogServiceTests()
{
_fixture = new Fixture();
}
[Fact]
public void ShowCreateKeyBindingDialog_Default_UsesCreateKeyBindingDialog()
{
var createKeyBindingDialog = new Mock<ICreateKeyBindingDialog>();
var dialogService = new DialogService(Mock.Of<IShellProfileSelectionDialog>, Mock.Of<IMessageDialog>, () => createKeyBindingDialog.Object, Mock.Of<IInputDialog>);
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>, Mock.Of<IInputDialog>);
Func<Task<DialogButton>> showMessageDialogAsnyc = () => dialogService.ShowMessageDialogAsnyc(title, content, buttons.ElementAt(0), buttons.ElementAt(1));
showMessageDialogAsnyc.Should().Throw<ArgumentNullException>().And.ParamName.Should().Be("title");
}
[Fact]
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>, Mock.Of<IInputDialog>);
Func<Task<DialogButton>> showMessageDialogAsnyc = () => dialogService.ShowMessageDialogAsnyc(title, content, buttons.ElementAt(0), buttons.ElementAt(1));
showMessageDialogAsnyc.Should().Throw<ArgumentNullException>().And.ParamName.Should().Be("content");
}
[Fact]
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>, Mock.Of<IInputDialog>);
Func<Task<DialogButton>> showMessageDialogAsnyc = () => dialogService.ShowMessageDialogAsnyc(title, content);
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>, Mock.Of<IInputDialog>);
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>, Mock.Of<IInputDialog>);
dialogService.ShowProfileSelectionDialogAsync();
shellProfileSelectionDialog.Verify(x => x.SelectProfile(), Times.Once);
}
}
}
@@ -0,0 +1,34 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<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>
</Project>
@@ -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");
}
}
}
@@ -2,27 +2,25 @@
using FluentAssertions;
using FluentTerminal.App.Services.Implementation;
using FluentTerminal.Models.Enums;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using Xunit;
namespace FluentTerminal.App.Test.Services
namespace FluentTerminal.App.Services.Test
{
[TestClass]
public class KeyboardCommandServiceTests
{
private Fixture _fixture;
private readonly Fixture _fixture;
[TestInitialize]
public void TestInitialize()
public KeyboardCommandServiceTests()
{
_fixture = new Fixture();
}
[TestMethod]
[Fact]
public void RegisterCommandHandler_HandlerIsNull_ThrowsArgumentNullException()
{
var command = _fixture.Create<Command>();
var command = ((Command)new Random().Next(1, Enum.GetValues(typeof(Command)).Length)).ToString();
Action handler = null;
var keyboardCommandService = new KeyboardCommandService();
@@ -31,10 +29,10 @@ namespace FluentTerminal.App.Test.Services
invoke.Should().Throw<ArgumentNullException>().And.ParamName.Should().Be("handler");
}
[TestMethod]
[Fact]
public void RegisterCommandHandler_CommandAlreadyRegisted_ThrowsInvalidOperationException()
{
var command = _fixture.Create<Command>();
var command = ((Command)new Random().Next(1, Enum.GetValues(typeof(Command)).Length)).ToString();
var handler = _fixture.Create<Action>();
var keyboardCommandService = new KeyboardCommandService();
keyboardCommandService.RegisterCommandHandler(command, handler);
@@ -44,10 +42,10 @@ namespace FluentTerminal.App.Test.Services
invoke.Should().Throw<InvalidOperationException>();
}
[TestMethod]
[Fact]
public void SendCommand_CommandIsRegistered_HandlerGetsInvoked()
{
var command = _fixture.Create<Command>();
var command = ((Command)new Random().Next(1, Enum.GetValues(typeof(Command)).Length)).ToString();
var handlerCalled = false;
var keyboardCommandService = new KeyboardCommandService();
keyboardCommandService.RegisterCommandHandler(command, () => handlerCalled = true);
@@ -57,15 +55,26 @@ namespace FluentTerminal.App.Test.Services
handlerCalled.Should().BeTrue();
}
[TestMethod]
[Fact]
public void SendCommand_CommandIsNotRegisted_KeyNotFoundExceptionIsThrown()
{
var command = _fixture.Create<Command>();
var command = ((Command)new Random().Next(1, Enum.GetValues(typeof(Command)).Length)).ToString();
var keyboardCommandService = new KeyboardCommandService();
Action invoke = () => keyboardCommandService.SendCommand(command);
invoke.Should().Throw<KeyNotFoundException>();
}
[Fact]
public void SendCommand_CommandIsToggleWindow_ShouldNotThrow()
{
var command = nameof(Command.ToggleWindow);
var keyboardCommandService = new KeyboardCommandService();
Action invoke = () => keyboardCommandService.SendCommand(command);
invoke.Should().NotThrow();
}
}
}
@@ -0,0 +1,758 @@
using AutoFixture;
using FluentAssertions;
using FluentTerminal.App.Services.Implementation;
using FluentTerminal.App.Services.Utilities;
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 commands = _fixture.CreateMany<Command>(5);
var defaultValueProvider = Mock.Of<IDefaultValueProvider>();
var keyBindingsContainer = new Mock<IApplicationDataContainer>();
foreach (var command in commands)
{
keyBindingsContainer.Setup(x => x.ReadValueFromJson<Collection<KeyBinding>>(command.ToString(), null)).Returns(new Collection<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.GetCommandKeyBindings();
foreach (var command in commands)
{
keyBindingsContainer.Verify(x => x.ReadValueFromJson<Collection<KeyBinding>>(command.ToString(), null), Times.Once);
}
}
[Fact]
public void GetKeyBindings_KeyBindingsNotInKeyBindingsContainer_ReturnsKeyBindingsFromDefaultValueProvider()
{
var commands = _fixture.CreateMany<Command>(5);
var defaultValueProvider = new Mock<IDefaultValueProvider>();
var keyBindingsContainer = new Mock<IApplicationDataContainer>();
foreach (var command in commands)
{
keyBindingsContainer.Setup(x => x.ReadValueFromJson<Collection<KeyBinding>>(EnumHelper.GetEnumDescription(command), null)).Returns(new Collection<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.GetCommandKeyBindings();
foreach (Command command in Enum.GetValues(typeof(Command)))
{
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.ToString(), 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.ToString(), 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 localSettings = new Mock<IApplicationDataContainer>();
var defaultShellProfileId = (object)_fixture.Create<Guid>();
localSettings.Setup(x => x.TryGetValue(SettingsService.DefaultShellProfileKey, out defaultShellProfileId)).Returns(true);
var applicationDataContainers = new ApplicationDataContainers
{
LocalSettings = localSettings.Object,
KeyBindings = Mock.Of<IApplicationDataContainer>(),
RoamingSettings = 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);
localSettings.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 localSettings = new Mock<IApplicationDataContainer>();
var currentDefaultShellProfileId = (object)_fixture.Create<Guid>();
localSettings.Setup(x => x.TryGetValue(SettingsService.DefaultShellProfileKey, out currentDefaultShellProfileId)).Returns(false);
var applicationDataContainers = new ApplicationDataContainers
{
LocalSettings = localSettings.Object,
KeyBindings = Mock.Of<IApplicationDataContainer>(),
RoamingSettings = 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);
localSettings.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);
}
[Fact]
public void GetDefaultShellProfile_ProfileNotFound_DefaultShellProfileIdGetsResetToDefault()
{
var currentShellProfileId = (object)_fixture.Create<Guid>();
var defaultShellProfile = _fixture.Create<ShellProfile>();
var defaultValueProvider = new Mock<IDefaultValueProvider>();
defaultValueProvider.Setup(x => x.GetDefaultShellProfileId()).Returns(defaultShellProfile.Id);
var shellProfilesContainer = new Mock<IApplicationDataContainer>();
var localSettings = new Mock<IApplicationDataContainer>();
var applicationDataContainers = new ApplicationDataContainers
{
ShellProfiles = shellProfilesContainer.Object,
KeyBindings = Mock.Of<IApplicationDataContainer>(),
RoamingSettings = Mock.Of<IApplicationDataContainer>(),
LocalSettings = localSettings.Object,
Themes = Mock.Of<IApplicationDataContainer>()
};
shellProfilesContainer.Setup(x => x.ReadValueFromJson(currentShellProfileId.ToString(), default(ShellProfile))).Returns(value: null);
shellProfilesContainer.Setup(x => x.ReadValueFromJson(defaultShellProfile.Id.ToString(), default(ShellProfile))).Returns(defaultShellProfile);
localSettings.Setup(x => x.TryGetValue(SettingsService.DefaultShellProfileKey, out currentShellProfileId)).Returns(true);
var settingsService = new SettingsService(defaultValueProvider.Object, applicationDataContainers);
var result = settingsService.GetDefaultShellProfile();
result.Should().Be(defaultShellProfile);
localSettings.Verify(x => x.SetValue(SettingsService.DefaultShellProfileKey, defaultShellProfile.Id), Times.Once);
}
[Fact]
public void GetCurrentTheme_ThemeNotFound_CurrentThemeIdGetsResetToDefault()
{
var currentThemeId = (object)_fixture.Create<Guid>();
var defaultTheme = _fixture.Create<TerminalTheme>();
var defaultValueProvider = new Mock<IDefaultValueProvider>();
defaultValueProvider.Setup(x => x.GetDefaultThemeId()).Returns(defaultTheme.Id);
var themesContainer = new Mock<IApplicationDataContainer>();
var roamingSettings = new Mock<IApplicationDataContainer>();
var applicationDataContainers = new ApplicationDataContainers
{
ShellProfiles = Mock.Of<IApplicationDataContainer>(),
KeyBindings = Mock.Of<IApplicationDataContainer>(),
LocalSettings = Mock.Of<IApplicationDataContainer>(),
RoamingSettings = roamingSettings.Object,
Themes = themesContainer.Object
};
themesContainer.Setup(x => x.ReadValueFromJson(currentThemeId.ToString(), default(TerminalTheme))).Returns(value: null);
themesContainer.Setup(x => x.ReadValueFromJson(defaultTheme.Id.ToString(), default(TerminalTheme))).Returns(defaultTheme);
roamingSettings.Setup(x => x.TryGetValue(SettingsService.CurrentThemeKey, out currentThemeId)).Returns(true);
var settingsService = new SettingsService(defaultValueProvider.Object, applicationDataContainers);
var result = settingsService.GetCurrentTheme();
result.Should().Be(defaultTheme);
roamingSettings.Verify(x => x.SetValue(SettingsService.CurrentThemeKey, defaultTheme.Id), 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,184 @@
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.GetCommandKeyBindings()).Returns(new Dictionary<string, ICollection<KeyBinding>>
{
[nameof(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 sessionType = _fixture.Create<SessionType>();
var trayProcessCommunicationService = new TrayProcessCommunicationService(settingsService.Object);
trayProcessCommunicationService.Initialize(appServiceConnection.Object);
var result = await trayProcessCommunicationService.CreateTerminal(terminalSize, shellProfile, sessionType);
result.Should().BeEquivalentTo(response);
}
[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.GetCommandKeyBindings()).Returns(new Dictionary<string, ICollection<KeyBinding>>
{
[nameof(Command.ToggleWindow)] = keyBindings.ToList()
});
var appServiceConnection = new Mock<IAppServiceConnection>();
var trayProcessCommunicationService = new TrayProcessCommunicationService(settingsService.Object);
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.GetCommandKeyBindings()).Returns(new Dictionary<string, ICollection<KeyBinding>>
{
[nameof(Command.ToggleWindow)] = keyBindings.ToList()
});
var appServiceConnection = new Mock<IAppServiceConnection>();
var trayProcessCommunicationService = new TrayProcessCommunicationService(settingsService.Object);
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.GetCommandKeyBindings()).Returns(new Dictionary<string, ICollection<KeyBinding>>
{
[nameof(Command.ToggleWindow)] = keyBindings.ToList()
});
var appServiceConnection = new Mock<IAppServiceConnection>();
var trayProcessCommunicationService = new TrayProcessCommunicationService(settingsService.Object);
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 void 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.GetCommandKeyBindings()).Returns(new Dictionary<string, ICollection<KeyBinding>>
{
[nameof(Command.ToggleWindow)] = keyBindings.ToList()
});
var trayProcessCommunicationService = new TrayProcessCommunicationService(settingsService.Object);
var appServiceConnection = new Mock<IAppServiceConnection>();
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 void 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.GetCommandKeyBindings()).Returns(new Dictionary<string, ICollection<KeyBinding>>
{
[nameof(Command.ToggleWindow)] = keyBindings.ToList()
});
var trayProcessCommunicationService = new TrayProcessCommunicationService(settingsService.Object);
var appServiceConnection = new Mock<IAppServiceConnection>();
trayProcessCommunicationService.Initialize(appServiceConnection.Object);
trayProcessCommunicationService.SubscribeForTerminalOutput(terminalId, o =>
{
receivedOutput = o;
});
appServiceConnection.Raise(x => x.MessageReceived += null, null, message);
receivedOutput.Should().Be(output);
}
}
}
@@ -0,0 +1,57 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using Windows.Storage;
namespace FluentTerminal.App.Services.Adapters
{
public class ApplicationDataContainerAdapter : IApplicationDataContainer
{
private readonly ApplicationDataContainer _applicationDataContainer;
public ApplicationDataContainerAdapter(ApplicationDataContainer applicationDataContainer)
{
_applicationDataContainer = applicationDataContainer;
}
public void Delete(string key)
{
_applicationDataContainer.Values.Remove(key);
}
public IEnumerable<object> GetAll()
{
return _applicationDataContainer.Values.Select(x => x.Value);
}
public T ReadValueFromJson<T>(string name, T fallbackValue)
{
if (_applicationDataContainer.Values.TryGetValue(name, out object value))
{
if (EqualityComparer<T>.Default.Equals(fallbackValue, default))
{
fallbackValue = Activator.CreateInstance<T>();
}
JsonConvert.PopulateObject((string)value, fallbackValue);
}
return fallbackValue;
}
public void SetValue(string key, object value)
{
_applicationDataContainer.Values[key] = value;
}
public bool TryGetValue(string key, out object value)
{
return _applicationDataContainer.Values.TryGetValue(key, out value);
}
public void WriteValueAsJson<T>(string name, T value)
{
_applicationDataContainer.Values[name] = JsonConvert.SerializeObject(value);
}
}
}
@@ -0,0 +1,11 @@
namespace FluentTerminal.App.Services
{
public class ApplicationDataContainers
{
public IApplicationDataContainer LocalSettings { get; set; }
public IApplicationDataContainer RoamingSettings { get; set; }
public IApplicationDataContainer Themes { get; set; }
public IApplicationDataContainer KeyBindings { get; set; }
public IApplicationDataContainer ShellProfiles { get; set; }
}
}
+9
View File
@@ -0,0 +1,9 @@
namespace FluentTerminal.App.Services
{
public static class Constants
{
public const string ThemesContainerName = "Themes";
public const string KeyBindingsContainerName = "KeyBindings";
public const string ShellProfilesContainerName = "ShellProfiles";
}
}
@@ -0,0 +1,10 @@
using FluentTerminal.Models;
using System.Threading.Tasks;
namespace FluentTerminal.App.Services.Dialogs
{
public interface ICreateKeyBindingDialog
{
Task<KeyBinding> CreateKeyBinding();
}
}
@@ -0,0 +1,10 @@
using System.Threading.Tasks;
namespace FluentTerminal.App.Services.Dialogs
{
public interface IInputDialog
{
void SetTitle(string title);
Task<string> GetInput();
}
}
@@ -0,0 +1,14 @@
using System.Threading.Tasks;
namespace FluentTerminal.App.Services.Dialogs
{
public interface IMessageDialog
{
string Title { get; set; }
string Content { get; set; }
void AddButton(DialogButton button);
Task<DialogButton> ShowAsync();
}
}
@@ -0,0 +1,12 @@
using FluentTerminal.Models;
using System.Threading.Tasks;
namespace FluentTerminal.App.Services.Dialogs
{
public interface IShellProfileSelectionDialog
{
ShellProfile SelectedProfile { get; }
Task<ShellProfile> SelectProfile();
}
}
@@ -0,0 +1,7 @@
namespace FluentTerminal.App.Services.EventArgs
{
public class CancelableEventArgs : System.EventArgs
{
public bool Cancelled { get; set; }
}
}
@@ -0,0 +1,7 @@
namespace FluentTerminal.App.Services.EventArgs
{
public class NewWindowRequestedEventArgs : System.EventArgs
{
public bool ShowProfileSelection { get; set; }
}
}
@@ -0,0 +1,23 @@
using System;
namespace FluentTerminal.App.Services.Exceptions
{
public class ParseThemeException : Exception
{
public ParseThemeException() : base()
{
}
public ParseThemeException(string message) : base(message)
{
}
public ParseThemeException(string message, Exception innerException) : base(message, innerException)
{
}
protected ParseThemeException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context)
{
}
}
}
@@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>7.1</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CSharp" Version="4.5.0" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="PListNet" Version="2.0.3" />
<PackageReference Include="RestSharp" Version="106.5.4" />
<PackageReference Include="System.Runtime.InteropServices.WindowsRuntime" Version="4.3.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\FluentTerminal.Models\FluentTerminal.Models.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="Windows.Foundation.FoundationContract">
<HintPath>$(MSBuildProgramFiles32)\Windows Kits\10\References\10.0.16299.0\Windows.Foundation.FoundationContract\3.0.0.0\Windows.Foundation.FoundationContract.winmd</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Windows.Foundation.UniversalApiContract">
<HintPath>$(MSBuildProgramFiles32)\Windows Kits\10\References\10.0.16299.0\Windows.Foundation.UniversalApiContract\5.0.0.0\Windows.Foundation.UniversalApiContract.winmd</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
</Project>
@@ -0,0 +1,13 @@
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);
}
}
@@ -0,0 +1,19 @@
using System.Collections.Generic;
namespace FluentTerminal.App.Services
{
public interface IApplicationDataContainer
{
T ReadValueFromJson<T>(string name, T fallbackValue);
void WriteValueAsJson<T>(string name, T value);
bool TryGetValue(string key, out object value);
void SetValue(string key, object value);
void Delete(string key);
IEnumerable<object> GetAll();
}
}
@@ -0,0 +1,22 @@
using FluentTerminal.App.Services.EventArgs;
using System;
using System.Threading.Tasks;
namespace FluentTerminal.App.Services
{
public delegate Task CloseRequestedHandler(object sender, CancelableEventArgs args);
public interface IApplicationView
{
event CloseRequestedHandler CloseRequested;
string Title { get; set; }
Task RunOnDispatcherThread(Action action);
Task<bool> TryClose();
bool ToggleFullScreen();
bool IsApiContractPresent(string api, ushort version);
}
}
@@ -0,0 +1,11 @@
using System.Threading.Tasks;
namespace FluentTerminal.App.Services
{
public interface IClipboardService
{
Task<string> GetText();
void SetText(string text);
}
}
@@ -1,4 +1,5 @@
using FluentTerminal.Models;
using FluentTerminal.Models.Enums;
using System;
using System.Collections.Generic;
@@ -6,12 +7,20 @@ namespace FluentTerminal.App.Services
{
public interface IDefaultValueProvider
{
IEnumerable<ShellProfile> GetPreinstalledShellProfiles();
Guid GetDefaultShellProfileId();
TerminalOptions GetDefaultTerminalOptions();
KeyBindings GetDefaultKeyBindings();
ApplicationSettings GetDefaultApplicationSettings();
ICollection<KeyBinding> GetDefaultKeyBindings(Command command);
Guid GetDefaultShellProfileId();
IEnumerable<TabTheme> GetDefaultTabThemes();
TerminalOptions GetDefaultTerminalOptions();
Guid GetDefaultThemeId();
IEnumerable<ShellProfile> GetPreinstalledShellProfiles();
IEnumerable<TerminalTheme> GetPreInstalledThemes();
}
}
}
@@ -14,5 +14,8 @@ namespace FluentTerminal.App.Services
Task<DialogButton> ShowMessageDialogAsnyc(string title, string content, params DialogButton[] buttons);
Task<ShellProfile> ShowProfileSelectionDialogAsync();
Task<KeyBinding> ShowCreateKeyBindingDialog();
Task<string> ShowInputDialogAsync(string title);
}
}
}
@@ -0,0 +1,16 @@
using System;
namespace FluentTerminal.App.Services
{
public interface IDispatcherTimer
{
void Start();
void Stop();
TimeSpan Interval { get; set; }
bool IsEnabled { get; }
event EventHandler<object> Tick;
}
}
@@ -0,0 +1,15 @@
using FluentTerminal.Models;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace FluentTerminal.App.Services
{
public interface IFileSystemService
{
Task<File> OpenFile(IEnumerable<string> fileTypes);
Task<string> BrowseForDirectory();
Task SaveTextFile(string name, string fileTypeDescription, string fileType, string content);
}
}
@@ -0,0 +1,12 @@
using System;
namespace FluentTerminal.App.Services
{
public interface IKeyboardCommandService
{
void RegisterCommandHandler(string command, Action handler);
void SendCommand(string command);
void DeregisterCommandHandler(string command);
}
}
@@ -0,0 +1,7 @@
namespace FluentTerminal.App.Services
{
public interface INotificationService
{
void ShowNotification(string title, string content, string url = null);
}
}
@@ -6,35 +6,36 @@ namespace FluentTerminal.App.Services
{
public interface ISettingsService
{
event EventHandler<Guid> CurrentThemeChanged;
event EventHandler<TerminalOptions> TerminalOptionsChanged;
event EventHandler<ApplicationSettings> ApplicationSettingsChanged;
event EventHandler<KeyBindings> KeyBindingsChanged;
event EventHandler<Guid> CurrentThemeChanged;
event EventHandler KeyBindingsChanged;
event EventHandler<ShellProfile> ShellProfileAdded;
event EventHandler<Guid> ShellProfileDeleted;
event EventHandler<TerminalOptions> TerminalOptionsChanged;
event EventHandler<Guid> ThemeDeleted;
event EventHandler<TerminalTheme> ThemeAdded;
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);
void DeleteTheme(Guid id);
ApplicationSettings GetApplicationSettings();
void SaveApplicationSettings(ApplicationSettings applicationSettings);
KeyBindings GetKeyBindings();
void SaveKeyBindings(KeyBindings keyBindings);
TerminalTheme GetCurrentTheme();
Guid GetCurrentThemeId();
ShellProfile GetDefaultShellProfile();
Guid GetDefaultShellProfileId();
IDictionary<string, ICollection<KeyBinding>> GetCommandKeyBindings();
IEnumerable<ShellProfile> GetShellProfiles();
IEnumerable<TabTheme> GetTabThemes();
TerminalOptions GetTerminalOptions();
TerminalTheme GetTheme(Guid id);
void SaveCurrentThemeId(Guid id);
IEnumerable<TerminalTheme> GetThemes();
void SaveTheme(TerminalTheme theme);
void DeleteTheme(Guid id);
void ResetKeyBindings();
void SaveApplicationSettings(ApplicationSettings applicationSettings);
void SaveCurrentThemeId(Guid id);
void SaveDefaultShellProfileId(Guid id);
void SaveKeyBindings(string command, ICollection<KeyBinding> keyBindings);
void SaveShellProfile(ShellProfile shellProfile, bool newShell = false);
void SaveTerminalOptions(TerminalOptions terminalOptions);
void SaveTheme(TerminalTheme theme, bool newTheme = false);
ShellProfile GetShellProfile(Guid id);
}
}
}
@@ -0,0 +1,12 @@
using FluentTerminal.Models.Enums;
using System.Threading.Tasks;
namespace FluentTerminal.App.Services
{
public interface IStartupTaskService
{
Task DisableStartupTask();
Task<StartupTaskStatus> EnableStartupTask();
Task<StartupTaskStatus> GetStatus();
}
}
@@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace FluentTerminal.App.Services
{
public interface ISystemFontService
{
IEnumerable<string> GetSystemFontFamilies();
}
}
@@ -0,0 +1,14 @@
using FluentTerminal.Models;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
namespace FluentTerminal.App.Services
{
public interface IThemeParser
{
IEnumerable<string> SupportedFileTypes { get; }
Task<TerminalTheme> Parse(string fileName, Stream fileContent);
}
}
@@ -0,0 +1,11 @@
using System.Collections.Generic;
namespace FluentTerminal.App.Services
{
public interface IThemeParserFactory
{
IEnumerable<string> SupportedFileTypes { get; }
IThemeParser GetParser(string fileType);
}
}
@@ -0,0 +1,29 @@
using FluentTerminal.Models;
using FluentTerminal.Models.Enums;
using FluentTerminal.Models.Responses;
using System;
using System.Threading.Tasks;
namespace FluentTerminal.App.Services
{
public interface ITrayProcessCommunicationService
{
event EventHandler<int> TerminalExited;
void Initialize(IAppServiceConnection appServiceConnection);
Task<CreateTerminalResponse> CreateTerminal(int id, TerminalSize size, ShellProfile shellProfile, SessionType sessionType);
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);
Task<GetAvailablePortResponse> GetAvailablePort();
int GetNextTerminalId();
}
}
@@ -0,0 +1,12 @@
using System;
using System.Threading.Tasks;
namespace FluentTerminal.App.Services
{
public interface IUpdateService
{
Task CheckForUpdate(bool notifyNoUpdate = false);
Version GetCurrentVersion();
Task<Version> GetLatestVersionAsync();
}
}
@@ -0,0 +1,599 @@
using FluentTerminal.Models;
using FluentTerminal.Models.Enums;
using System;
using System.Collections.Generic;
namespace FluentTerminal.App.Services.Implementation
{
public class DefaultValueProvider : IDefaultValueProvider
{
public ApplicationSettings GetDefaultApplicationSettings()
{
return new ApplicationSettings
{
ConfirmClosingTabs = false,
ConfirmClosingWindows = false,
UnderlineSelectedTab = false,
InactiveTabColorMode = InactiveTabColorMode.Background,
NewTerminalLocation = NewTerminalLocation.Tab,
TabsPosition = TabsPosition.Top,
CopyOnSelect = false,
MouseMiddleClickAction = MouseAction.None,
MouseRightClickAction = MouseAction.ContextMenu,
AlwaysShowTabs = false,
AlwaysUseWinPty = true,
ShowNewOutputIndicator = false,
EnableTrayIcon = true
};
}
public ICollection<KeyBinding> GetDefaultKeyBindings(Command command)
{
switch (command)
{
case Command.ToggleWindow:
return new List<KeyBinding>
{
new KeyBinding
{
Command = nameof(Command.ToggleWindow),
Ctrl = false,
Alt = false,
Shift = false,
Key = (int)ExtendedVirtualKey.Scroll
}
};
case Command.NextTab:
return new List<KeyBinding>
{
new KeyBinding
{
Command = nameof(Command.NextTab),
Ctrl = true,
Alt = false,
Shift = false,
Key = (int)ExtendedVirtualKey.Tab
}
};
case Command.PreviousTab:
return new List<KeyBinding>
{
new KeyBinding
{
Command = nameof(Command.PreviousTab),
Ctrl = true,
Alt = false,
Shift = true,
Key = (int)ExtendedVirtualKey.Tab
}
};
case Command.SwitchToTerm1:
case Command.SwitchToTerm2:
case Command.SwitchToTerm3:
case Command.SwitchToTerm4:
case Command.SwitchToTerm5:
case Command.SwitchToTerm6:
case Command.SwitchToTerm7:
case Command.SwitchToTerm8:
case Command.SwitchToTerm9:
return new List<KeyBinding>
{
new KeyBinding
{
Command = command.ToString(),
Ctrl = true,
Alt = false,
Shift = false,
Key = (int)ExtendedVirtualKey.Number1 + (command - Command.SwitchToTerm1)
}
};
case Command.NewTab:
return new List<KeyBinding>
{
new KeyBinding
{
Command = nameof(Command.NewTab),
Ctrl = true,
Alt = false,
Shift = false,
Key = (int)ExtendedVirtualKey.T
}
};
case Command.ConfigurableNewTab:
return new List<KeyBinding>
{
new KeyBinding
{
Command = nameof(Command.ConfigurableNewTab),
Ctrl = true,
Alt = false,
Shift = true,
Key = (int)ExtendedVirtualKey.T
}
};
case Command.ChangeTabTitle:
return new List<KeyBinding>
{
new KeyBinding
{
Command = nameof(Command.ChangeTabTitle),
Ctrl = true,
Alt = false,
Shift = true,
Key = (int)ExtendedVirtualKey.R
}
};
case Command.CloseTab:
return new List<KeyBinding>
{
new KeyBinding
{
Command = nameof(Command.CloseTab),
Ctrl = true,
Alt = false,
Shift = false,
Key = (int)ExtendedVirtualKey.W
}
};
case Command.NewWindow:
return new List<KeyBinding>
{
new KeyBinding
{
Command = nameof(Command.NewWindow),
Ctrl = true,
Alt = false,
Shift = false,
Key = (int)ExtendedVirtualKey.N
}
};
case Command.ConfigurableNewWindow:
return new List<KeyBinding>
{
new KeyBinding
{
Command = nameof(Command.ConfigurableNewWindow),
Ctrl = true,
Alt = false,
Shift = true,
Key = (int)ExtendedVirtualKey.N
}
};
case Command.ShowSettings:
return new List<KeyBinding>
{
new KeyBinding
{
Command = nameof(Command.ShowSettings),
Ctrl = true,
Alt = false,
Shift = false,
Key = (int)ExtendedVirtualKey.Comma
}
};
case Command.Copy:
return new List<KeyBinding>
{
new KeyBinding
{
Command = nameof(Command.Copy),
Ctrl = true,
Alt = false,
Shift = false,
Key = (int)ExtendedVirtualKey.C
}
};
case Command.Paste:
return new List<KeyBinding>
{
new KeyBinding
{
Command = nameof(Command.Paste),
Ctrl = true,
Alt = false,
Shift = false,
Key = (int)ExtendedVirtualKey.V
}
};
case Command.PasteWithoutNewlines:
return new List<KeyBinding>
{
new KeyBinding
{
Command = nameof(Command.PasteWithoutNewlines),
Ctrl = true,
Alt = false,
Shift = true,
Key = (int)ExtendedVirtualKey.V
}
};
case Command.Search:
return new List<KeyBinding>
{
new KeyBinding
{
Command = nameof(Command.Search),
Ctrl = true,
Alt = false,
Shift = false,
Key = (int)ExtendedVirtualKey.F
}
};
case Command.ToggleFullScreen:
return new List<KeyBinding>
{
new KeyBinding
{
Command = nameof(Command.ToggleFullScreen),
Ctrl = false,
Alt = true,
Shift = false,
Key = (int)ExtendedVirtualKey.Enter
}
};
case Command.SelectAll:
return new List<KeyBinding>
{
new KeyBinding
{
Command = nameof(Command.SelectAll),
Ctrl = true,
Alt = false,
Shift = false,
Key = (int)ExtendedVirtualKey.A
}
};
case Command.Clear:
return new List<KeyBinding>
{
new KeyBinding
{
Command = nameof(Command.Clear),
Ctrl = true,
Alt = false,
Shift = false,
Key = (int)ExtendedVirtualKey.L
}
};
}
return null;
}
public Guid GetDefaultShellProfileId()
{
return Guid.Parse("813f2298-210a-481a-bdbf-c17bc637a3e2");
}
public IEnumerable<TabTheme> GetDefaultTabThemes()
{
return new[]
{
new TabTheme
{
Id = 0,
Name = "None",
BackgroundOpacity = double.NaN,
BackgroundPointerOverOpacity = double.NaN,
BackgroundPressedOpacity = double.NaN,
BackgroundSelectedOpacity = double.NaN,
BackgroundSelectedPointerOverOpacity = double.NaN,
BackgroundSelectedPressedOpacity = double.NaN
},
new TabTheme
{
Id = 1,
Name = "Red",
Color = "#E81123"
},
new TabTheme
{
Id = 2,
Name = "Green",
Color = "#10893E"
},
new TabTheme
{
Id = 3,
Name = "Blue",
Color = "#0078D7"
},
new TabTheme
{
Id = 4,
Name = "Purple",
Color = "#881798"
},
new TabTheme
{
Id = 5,
Name = "Orange",
Color = "#FF8C00"
},
new TabTheme
{
Id = 6,
Name = "Teal",
Color = "#00B7C3"
}
};
}
public TerminalOptions GetDefaultTerminalOptions()
{
return new TerminalOptions
{
BellStyle = BellStyle.None,
CursorBlink = true,
CursorStyle = CursorStyle.Block,
ScrollBarStyle = ScrollBarStyle.Hidden,
FontFamily = "Consolas",
FontSize = 13,
BoldText = false,
BackgroundOpacity = 0.8,
Padding = 12,
ScrollBackLimit = 1000
};
}
public Guid GetDefaultThemeId()
{
return Guid.Parse("281e4352-bb50-47b7-a691-2b13830df95e");
}
public IEnumerable<ShellProfile> GetPreinstalledShellProfiles()
{
return new[]
{
new ShellProfile
{
Id = GetDefaultShellProfileId(),
Name = "Powershell",
Arguments = string.Empty,
Location = @"C:\windows\system32\WindowsPowerShell\v1.0\powershell.exe",
PreInstalled = true,
WorkingDirectory = string.Empty,
LineEndingTranslation = LineEndingStyle.DoNotModify,
KeyBindings = new []
{
new KeyBinding
{
Command = GetDefaultShellProfileId().ToString(),
Ctrl=true,
Alt=true,
Shift=false,
Meta=false,
Key=(int)ExtendedVirtualKey.Number1
}
}
},
new ShellProfile
{
Id = Guid.Parse("ab942a61-7673-4755-9bd8-765aff91d9a3"),
Name = "CMD",
Arguments = string.Empty,
Location = @"C:\Windows\System32\cmd.exe",
PreInstalled = true,
WorkingDirectory = string.Empty,
LineEndingTranslation = LineEndingStyle.DoNotModify,
KeyBindings = new []
{
new KeyBinding
{
Command = "ab942a61-7673-4755-9bd8-765aff91d9a3",
Ctrl=true,
Alt=true,
Shift=false,
Meta=false,
Key=(int)ExtendedVirtualKey.Number2
}
}
},
new ShellProfile
{
Id= Guid.Parse("e5785ad6-584f-40cb-bdcd-d5b3b3953e7f"),
Name = "WSL",
Arguments = string.Empty,
Location = @"C:\windows\system32\wsl.exe",
PreInstalled = true,
WorkingDirectory = string.Empty,
LineEndingTranslation = LineEndingStyle.DoNotModify,
KeyBindings = new []
{
new KeyBinding
{
Command = "e5785ad6-584f-40cb-bdcd-d5b3b3953e7f",
Ctrl=true,
Alt=true,
Shift=false,
Meta=false,
Key=(int)ExtendedVirtualKey.Number3
}
}
}
};
}
public IEnumerable<TerminalTheme> GetPreInstalledThemes()
{
var defaultXterm = new TerminalTheme
{
Id = GetDefaultThemeId(),
Author = "xterm.js",
Name = "Xterm.js Default",
PreInstalled = true,
Colors = new TerminalColors
{
Black = "#2e3436",
Red = "#cc0000",
Green = "#4e9a06",
Yellow = "#c4a000",
Blue = "#3465a4",
Magenta = "#75507b",
Cyan = "#06989a",
White = "#d3d7cf",
BrightBlack = "#555753",
BrightRed = "#ef2929",
BrightGreen = "#8ae234",
BrightYellow = "#fce94f",
BrightBlue = "#729fcf",
BrightMagenta = "#ad7fa8",
BrightCyan = "#34e2e2",
BrightWhite = "#eeeeec",
Foreground = "#ffffff",
Background = "#000000",
Cursor = "#ffffff",
CursorAccent = "#000000",
Selection = "rgba(255, 255, 255, 0.3)"
}
};
var powerShell = new TerminalTheme
{
Id = Guid.Parse("3571ce1b-31ce-4cf7-ae15-e0bff70c3eea"),
Author = "Microsoft",
Name = "PowerShell",
PreInstalled = true,
Colors = new TerminalColors
{
Black = "#000000",
Red = "#800000",
Green = "#008000",
Yellow = "#EEEDF0",
Blue = "#000080",
Magenta = "#012456",
Cyan = "#008080",
White = "#c0c0c0",
BrightBlack = "#808080",
BrightRed = "#ff0000",
BrightGreen = "#00ff00",
BrightYellow = "#ffff00",
BrightBlue = "#0000ff",
BrightMagenta = "#ff00ff",
BrightCyan = "#00ffff",
BrightWhite = "#ffffff",
Foreground = "#ffffff",
Background = "#012456",
Cursor = "#fedba9",
CursorAccent = "#000000",
Selection = "rgba(254, 219, 169, 0.3)"
}
};
var homebrew = new TerminalTheme
{
Id = Guid.Parse("5f034210-067b-40e2-b9c9-6a25eb6fb8e6"),
Author = "Hans Kokx",
Name = "Homebrew",
PreInstalled = true,
Colors = new TerminalColors
{
Black = "#000000",
Red = "#A0160B",
Green = "#00AF21",
Yellow = "#A1A222",
Blue = "#192AB7",
Magenta = "#AA2FAE",
Cyan = "#12B1BC",
White = "#BBB5AF",
BrightBlack = "#747876",
BrightRed = "#E52213",
BrightGreen = "#00D92B",
BrightYellow = "#E6E435",
BrightBlue = "#283EF9",
BrightMagenta = "#EB43E6",
BrightCyan = "#15E7E8",
BrightWhite = "#E9E9E9",
Foreground = "#00D92B",
Background = "#000000",
Cursor = "#00D92B",
CursorAccent = "#000000",
Selection = "#4CFFFFFF"
}
};
var tomorrow = new TerminalTheme
{
Id = Guid.Parse("0cbbf805-fa86-4be5-ac51-75f3054dcc3a"),
Author = "Chris Kempson",
Name = "Tomorrow",
PreInstalled = true,
Colors = new TerminalColors
{
Foreground = "#4D4D4C",
Background = "#FFFFFF",
Cursor = "#4D4D4C",
CursorAccent = "#FFFFFF",
Selection = "rgba(214, 214, 214, 0.5)",
Black = "#000000",
Red = "#C82829",
Green = "#718C00",
Yellow = "#EAB700",
Blue = "#4271AE",
Magenta = "#8959A8",
Cyan = "#3E999F",
White = "#FFFFFF",
BrightBlack = "#000000",
BrightRed = "#C82829",
BrightGreen = "#718C00",
BrightYellow = "#EAB700",
BrightBlue = "#4271AE",
BrightMagenta = "#8959A8",
BrightCyan = "#3E999F",
BrightWhite = "#FFFFFF"
}
};
var ubuntu = new TerminalTheme
{
Id = Guid.Parse("32519543-bf94-4db6-92a1-7bcec3966c82"),
Author = "Canonical",
Name = "Ubuntu Bash",
PreInstalled = true,
Colors = new TerminalColors
{
Foreground = "#EEEEEE",
Background = "#300A24",
Cursor = "#CFF5DB",
CursorAccent = "#000000",
Selection = "#4DABEDC1",
Black = "#300A24",
Red = "#CC0000",
Green = "#4E9A06",
Yellow = "#06989A",
Blue = "#3465A4",
Magenta = "#75507B",
Cyan = "#C4A000",
White = "#D3D7CF",
BrightBlack = "#554E53",
BrightRed = "#EF2929",
BrightGreen = "#8AE234",
BrightYellow = "#34E2E2",
BrightBlue = "#729FCF",
BrightMagenta = "#AD7FA8",
BrightCyan = "#FCE94F",
BrightWhite = "#EEEEEE"
}
};
return new[] { defaultXterm, powerShell, homebrew, tomorrow, ubuntu };
}
}
}
@@ -0,0 +1,74 @@
using FluentTerminal.App.Services.Dialogs;
using FluentTerminal.Models;
using System;
using System.Threading.Tasks;
namespace FluentTerminal.App.Services.Implementation
{
public class DialogService : IDialogService
{
private readonly Func<IShellProfileSelectionDialog> _shellProfileSelectionDialogFactory;
private readonly Func<IMessageDialog> _messageDialogFactory;
private readonly Func<ICreateKeyBindingDialog> _createKeyBindingDialogFactory;
private readonly Func<IInputDialog> _inputDialogFactory;
public DialogService(Func<IShellProfileSelectionDialog> shellProfileSelectionDialogFactory, Func<IMessageDialog> messageDialogFactory, Func<ICreateKeyBindingDialog> createKeyBindingDialogFactory, Func<IInputDialog> inputDialogFactory)
{
_shellProfileSelectionDialogFactory = shellProfileSelectionDialogFactory;
_messageDialogFactory = messageDialogFactory;
_createKeyBindingDialogFactory = createKeyBindingDialogFactory;
_inputDialogFactory = inputDialogFactory;
}
public Task<KeyBinding> ShowCreateKeyBindingDialog()
{
var dialog = _createKeyBindingDialogFactory();
return dialog.CreateKeyBinding();
}
public Task<DialogButton> ShowMessageDialogAsnyc(string title, string content, params DialogButton[] buttons)
{
if (string.IsNullOrWhiteSpace(title))
{
throw new ArgumentNullException(nameof(title));
}
if (string.IsNullOrWhiteSpace(content))
{
throw new ArgumentNullException(nameof(content));
}
if (buttons.Length == 0)
{
throw new ArgumentException("Must not be empty", nameof(buttons));
}
var dialog = _messageDialogFactory();
dialog.Content = content;
dialog.Title = title;
foreach (var button in buttons)
{
dialog.AddButton(button);
}
return dialog.ShowAsync();
}
public Task<string> ShowInputDialogAsync(string title)
{
var dialog = _inputDialogFactory.Invoke();
dialog.SetTitle(title);
return dialog.GetInput();
}
public Task<ShellProfile> ShowProfileSelectionDialogAsync()
{
var dialog = _shellProfileSelectionDialogFactory();
return dialog.SelectProfile();
}
}
}
@@ -0,0 +1,38 @@
using FluentTerminal.Models;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
namespace FluentTerminal.App.Services.Implementation
{
public class FluentTerminalThemeParser : IThemeParser
{
public IEnumerable<string> SupportedFileTypes { get; } = new string[] { ".flutecolors" };
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();
var theme = JsonConvert.DeserializeObject<TerminalTheme>(content);
theme.PreInstalled = false;
theme.Id = Guid.NewGuid();
return theme;
}
}
}
}
@@ -0,0 +1,137 @@
using FluentTerminal.App.Services.Exceptions;
using FluentTerminal.Models;
using PListNet;
using PListNet.Nodes;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Threading.Tasks;
namespace FluentTerminal.App.Services.Implementation
{
public class ITermThemeParser : IThemeParser
{
private const byte Opacity30Percent = 77;
public IEnumerable<string> SupportedFileTypes { get; } = new string[] { ".itermcolors" };
private static class ITermThemeKeys
{
public const string Ansi0Color = "Ansi 0 Color";
public const string Ansi1Color = "Ansi 1 Color";
public const string Ansi2Color = "Ansi 2 Color";
public const string Ansi3Color = "Ansi 3 Color";
public const string Ansi4Color = "Ansi 4 Color";
public const string Ansi5Color = "Ansi 5 Color";
public const string Ansi6Color = "Ansi 6 Color";
public const string Ansi7Color = "Ansi 7 Color";
public const string Ansi8Color = "Ansi 8 Color";
public const string Ansi9Color = "Ansi 9 Color";
public const string Ansi10Color = "Ansi 10 Color";
public const string Ansi11Color = "Ansi 11 Color";
public const string Ansi12Color = "Ansi 12 Color";
public const string Ansi13Color = "Ansi 13 Color";
public const string Ansi14Color = "Ansi 14 Color";
public const string Ansi15Color = "Ansi 15 Color";
public const string BackgroundColor = "Background Color";
public const string BoldColor = "Bold Color";
public const string CursorColor = "Cursor Color";
public const string CursorTextColor = "Cursor Text Color";
public const string ForegroundColor = "Foreground Color";
public const string SelectedTextColor = "Selected Text Color";
public const string SelectionColor = "Selection Color";
}
private static class ITermThemeColorKeys
{
public const string BlueComponent = "Blue Component";
public const string GreenComponent = "Green Component";
public const string RedComponent = "Red Component";
}
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
{
Name = Path.GetFileNameWithoutExtension(fileName),
Colors = GetColors(node),
Id = Guid.NewGuid(),
PreInstalled = false
};
}
private TerminalColors GetColors(DictionaryNode themeDictionary)
{
return new TerminalColors
{
Background = GetColorString(themeDictionary[ITermThemeKeys.BackgroundColor]),
Foreground = GetColorString(themeDictionary[ITermThemeKeys.ForegroundColor]),
Cursor = GetColorString(themeDictionary[ITermThemeKeys.CursorColor]),
CursorAccent = GetColorString(themeDictionary[ITermThemeKeys.CursorTextColor]),
Selection = GetColorString(themeDictionary[ITermThemeKeys.SelectionColor], Opacity30Percent),
Black = GetColorString(themeDictionary[ITermThemeKeys.Ansi0Color]),
Red = GetColorString(themeDictionary[ITermThemeKeys.Ansi1Color]),
Green = GetColorString(themeDictionary[ITermThemeKeys.Ansi2Color]),
Yellow = GetColorString(themeDictionary[ITermThemeKeys.Ansi3Color]),
Blue = GetColorString(themeDictionary[ITermThemeKeys.Ansi4Color]),
Magenta = GetColorString(themeDictionary[ITermThemeKeys.Ansi5Color]),
Cyan = GetColorString(themeDictionary[ITermThemeKeys.Ansi6Color]),
White = GetColorString(themeDictionary[ITermThemeKeys.Ansi7Color]),
BrightBlack = GetColorString(themeDictionary[ITermThemeKeys.Ansi8Color]),
BrightRed = GetColorString(themeDictionary[ITermThemeKeys.Ansi9Color]),
BrightGreen = GetColorString(themeDictionary[ITermThemeKeys.Ansi10Color]),
BrightYellow = GetColorString(themeDictionary[ITermThemeKeys.Ansi11Color]),
BrightBlue = GetColorString(themeDictionary[ITermThemeKeys.Ansi12Color]),
BrightMagenta = GetColorString(themeDictionary[ITermThemeKeys.Ansi13Color]),
BrightCyan = GetColorString(themeDictionary[ITermThemeKeys.Ansi14Color]),
BrightWhite = GetColorString(themeDictionary[ITermThemeKeys.Ansi15Color]),
};
}
private string GetColorString(PNode colorNode, byte alpha = Byte.MaxValue)
{
var dictionaryNode = colorNode as DictionaryNode ?? throw new ParseThemeException("Color node was not a dictionary");
var red = dictionaryNode[ITermThemeColorKeys.RedComponent] as RealNode ?? throw new ParseThemeException("Red node value was not a real number");
var green = dictionaryNode[ITermThemeColorKeys.GreenComponent] as RealNode ?? throw new ParseThemeException("Green node value was not a real number");
var blue = dictionaryNode[ITermThemeColorKeys.BlueComponent] as RealNode ?? throw new ParseThemeException("Blue node value was not a real number");
var color = $"#{alpha:X2}{GetByteValue(red):X2}{GetByteValue(green):X2}{GetByteValue(blue):X2}";
if (alpha == byte.MaxValue)
{
return $"#{GetByteValue(red):X2}{GetByteValue(green):X2}{GetByteValue(blue):X2}";
}
else
{
return $"rgba({GetByteValue(red):G}, {GetByteValue(green):G}, {GetByteValue(blue):G}, {ToDoubleString(alpha)})";
}
}
private byte GetByteValue(RealNode node)
{
var doubleValue = node.Value * Byte.MaxValue;
return Convert.ToByte(doubleValue);
}
private static string ToDoubleString(byte alpha)
{
return (alpha / 256.0).ToString(CultureInfo.InvariantCulture);
}
}
}
@@ -1,31 +1,46 @@
using System;
using System.Collections.Generic;
using FluentTerminal.Models.Enums;
namespace FluentTerminal.App.Services.Implementation
{
public class KeyboardCommandService : IKeyboardCommandService
{
private readonly Dictionary<Command, Action> _commandHandlers = new Dictionary<Command, Action>();
public void RegisterCommandHandler(Command command, Action handler)
{
if (_commandHandlers.ContainsKey(command))
{
throw new InvalidOperationException("command already registered");
}
_commandHandlers[command] = handler ?? throw new ArgumentNullException(nameof(handler));
}
public void SendCommand(Command command)
{
if (_commandHandlers.TryGetValue(command, out Action handler))
{
handler.Invoke();
return;
}
throw new KeyNotFoundException($"No handler registered for command: {command}");
}
}
}
using FluentTerminal.Models.Enums;
using System;
using System.Collections.Generic;
namespace FluentTerminal.App.Services.Implementation
{
public class KeyboardCommandService : IKeyboardCommandService
{
private readonly Dictionary<string, Action> _commandHandlers = new Dictionary<string, Action>();
public void RegisterCommandHandler(string command, Action handler)
{
if (_commandHandlers.ContainsKey(command))
{
throw new InvalidOperationException("command already registered");
}
_commandHandlers[command] = handler ?? throw new ArgumentNullException(nameof(handler));
}
public void DeregisterCommandHandler(string command)
{
if (_commandHandlers.ContainsKey(command))
{
_commandHandlers.Remove(command);
}
}
public void SendCommand(string command)
{
if (_commandHandlers.TryGetValue(command, out Action handler))
{
handler.Invoke();
return;
}
// already registered by SystemTray's ToggleWindowService
if (command == nameof(Command.ToggleWindow))
{
return;
}
throw new KeyNotFoundException($"No handler registered for command: {command}");
}
}
}
@@ -1,11 +1,12 @@
using Windows.Data.Xml.Dom;
using System.Diagnostics;
using Windows.Data.Xml.Dom;
using Windows.UI.Notifications;
namespace FluentTerminal.SystemTray.Services
namespace FluentTerminal.App.Services.Implementation
{
public class NotificationService
public class NotificationService : INotificationService
{
public void ShowNotification(string title, string content)
public void ShowNotification(string title, string content, string url = null)
{
string xml = $@"<toast>
<visual>
@@ -20,7 +21,11 @@ namespace FluentTerminal.SystemTray.Services
doc.LoadXml(xml);
var toast = new ToastNotification(doc);
if (url != null)
{
toast.Activated += (n, o) => Process.Start(url);
}
ToastNotificationManager.CreateToastNotifier().Show(toast);
}
}
}
}
@@ -0,0 +1,248 @@
using FluentTerminal.Models;
using FluentTerminal.Models.Enums;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
namespace FluentTerminal.App.Services.Implementation
{
public class SettingsService : ISettingsService
{
public const string CurrentThemeKey = "CurrentTheme";
public const string DefaultShellProfileKey = "DefaultShellProfile";
private readonly IDefaultValueProvider _defaultValueProvider;
private readonly IApplicationDataContainer _keyBindings;
private readonly IApplicationDataContainer _localSettings;
private readonly IApplicationDataContainer _roamingSettings;
private readonly IApplicationDataContainer _shellProfiles;
private readonly IApplicationDataContainer _themes;
public SettingsService(IDefaultValueProvider defaultValueProvider, ApplicationDataContainers containers)
{
_defaultValueProvider = defaultValueProvider;
_localSettings = containers.LocalSettings;
_roamingSettings = containers.RoamingSettings;
_themes = containers.Themes;
_keyBindings = containers.KeyBindings;
_shellProfiles = containers.ShellProfiles;
foreach (var theme in _defaultValueProvider.GetPreInstalledThemes())
{
if (GetTheme(theme.Id) == null)
{
_themes.WriteValueAsJson(theme.Id.ToString(), theme);
}
}
foreach (var shellProfile in _defaultValueProvider.GetPreinstalledShellProfiles())
{
if (GetShellProfile(shellProfile.Id) == null)
{
_shellProfiles.WriteValueAsJson(shellProfile.Id.ToString(), shellProfile);
}
}
}
public event EventHandler<ApplicationSettings> ApplicationSettingsChanged;
public event EventHandler<TerminalTheme> ThemeAdded;
public event EventHandler<Guid> CurrentThemeChanged;
public event EventHandler<Guid> ThemeDeleted;
public event EventHandler KeyBindingsChanged;
public event EventHandler<ShellProfile> ShellProfileAdded;
public event EventHandler<Guid> ShellProfileDeleted;
public event EventHandler<TerminalOptions> TerminalOptionsChanged;
public void DeleteShellProfile(Guid id)
{
_shellProfiles.Delete(id.ToString());
ShellProfileDeleted?.Invoke(this, id);
}
public void DeleteTheme(Guid id)
{
_themes.Delete(id.ToString());
foreach (var profile in GetShellProfiles())
{
if (profile.TerminalThemeId == id)
{
profile.TerminalThemeId = Guid.Empty;
SaveShellProfile(profile);
}
}
ThemeDeleted?.Invoke(this, id);
}
public ApplicationSettings GetApplicationSettings()
{
return _roamingSettings.ReadValueFromJson(nameof(ApplicationSettings), _defaultValueProvider.GetDefaultApplicationSettings());
}
public TerminalTheme GetCurrentTheme()
{
var id = GetCurrentThemeId();
var theme = GetTheme(id);
if (theme == null)
{
id = _defaultValueProvider.GetDefaultThemeId();
SaveCurrentThemeId(id);
theme = GetTheme(id);
}
return theme;
}
public Guid GetCurrentThemeId()
{
if (_roamingSettings.TryGetValue(CurrentThemeKey, out object value))
{
return (Guid)value;
}
return _defaultValueProvider.GetDefaultThemeId();
}
public ShellProfile GetDefaultShellProfile()
{
var id = GetDefaultShellProfileId();
var profile = _shellProfiles.ReadValueFromJson(id.ToString(), default(ShellProfile));
if (profile == null)
{
id = _defaultValueProvider.GetDefaultShellProfileId();
SaveDefaultShellProfileId(id);
profile = _shellProfiles.ReadValueFromJson(id.ToString(), default(ShellProfile));
}
return profile;
}
public ShellProfile GetShellProfile(Guid id)
{
return _shellProfiles.ReadValueFromJson(id.ToString(), default(ShellProfile));
}
public Guid GetDefaultShellProfileId()
{
if (_localSettings.TryGetValue(DefaultShellProfileKey, out object value))
{
return (Guid)value;
}
return _defaultValueProvider.GetDefaultShellProfileId();
}
public IDictionary<string, ICollection<KeyBinding>> GetCommandKeyBindings()
{
var keyBindings = new Dictionary<string, ICollection<KeyBinding>>();
foreach (Command command in Enum.GetValues(typeof(Command)))
{
keyBindings.Add(command.ToString(), _keyBindings.ReadValueFromJson<Collection<KeyBinding>>(command.ToString(), null) ?? _defaultValueProvider.GetDefaultKeyBindings(command));
}
return keyBindings;
}
public IEnumerable<ShellProfile> GetShellProfiles()
{
return _shellProfiles.GetAll().Select(x => JsonConvert.DeserializeObject<ShellProfile>((string)x)).ToList();
}
public IEnumerable<TabTheme> GetTabThemes()
{
return _defaultValueProvider.GetDefaultTabThemes();
}
public TerminalOptions GetTerminalOptions()
{
return _roamingSettings.ReadValueFromJson(nameof(TerminalOptions), _defaultValueProvider.GetDefaultTerminalOptions());
}
public TerminalTheme GetTheme(Guid id)
{
return _themes.ReadValueFromJson(id.ToString(), default(TerminalTheme));
}
public IEnumerable<TerminalTheme> GetThemes()
{
return _themes.GetAll().Select(x => JsonConvert.DeserializeObject<TerminalTheme>((string)x)).ToList();
}
public void ResetKeyBindings()
{
foreach (Command command in Enum.GetValues(typeof(Command)))
{
_keyBindings.WriteValueAsJson(command.ToString(), _defaultValueProvider.GetDefaultKeyBindings(command));
}
KeyBindingsChanged?.Invoke(this, System.EventArgs.Empty);
}
public void SaveApplicationSettings(ApplicationSettings applicationSettings)
{
_roamingSettings.WriteValueAsJson(nameof(ApplicationSettings), applicationSettings);
ApplicationSettingsChanged?.Invoke(this, applicationSettings);
}
public void SaveCurrentThemeId(Guid id)
{
_roamingSettings.SetValue(CurrentThemeKey, id);
CurrentThemeChanged?.Invoke(this, id);
}
public void SaveDefaultShellProfileId(Guid id)
{
_localSettings.SetValue(DefaultShellProfileKey, id);
}
public void SaveKeyBindings(string command, ICollection<KeyBinding> keyBindings)
{
if (!Enum.IsDefined(typeof(Command), command))
{
throw new InvalidOperationException();
}
_keyBindings.WriteValueAsJson(command.ToString(), keyBindings);
KeyBindingsChanged?.Invoke(this, System.EventArgs.Empty);
}
public void SaveShellProfile(ShellProfile shellProfile, bool newShell = false)
{
_shellProfiles.WriteValueAsJson(shellProfile.Id.ToString(), shellProfile);
// When saving the shell profile, we also need to update keybindings for everywhere.
KeyBindingsChanged?.Invoke(this, System.EventArgs.Empty);
if (newShell)
{
ShellProfileAdded?.Invoke(this, shellProfile);
}
}
public void SaveTerminalOptions(TerminalOptions terminalOptions)
{
_roamingSettings.WriteValueAsJson(nameof(TerminalOptions), terminalOptions);
TerminalOptionsChanged?.Invoke(this, terminalOptions);
}
public void SaveTheme(TerminalTheme theme, bool newTheme = false)
{
_themes.WriteValueAsJson(theme.Id.ToString(), theme);
if (theme.Id == GetCurrentThemeId())
{
CurrentThemeChanged?.Invoke(this, theme.Id);
}
if (newTheme)
{
ThemeAdded?.Invoke(this, theme);
}
}
}
}
@@ -0,0 +1,23 @@
using System.Collections.Generic;
using System.Linq;
namespace FluentTerminal.App.Services.Implementation
{
public class ThemeParserFactory : IThemeParserFactory
{
private IEnumerable<IThemeParser> _parsers;
public IEnumerable<string> SupportedFileTypes { get; }
public ThemeParserFactory(IEnumerable<IThemeParser> parsers)
{
_parsers = parsers;
SupportedFileTypes = _parsers.SelectMany(p => p.SupportedFileTypes);
}
public IThemeParser GetParser(string fileType)
{
return _parsers.FirstOrDefault(p => p.SupportedFileTypes.Contains(fileType));
}
}
}
@@ -0,0 +1,147 @@
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
{
public class TrayProcessCommunicationService : ITrayProcessCommunicationService
{
private readonly ISettingsService _settingsService;
private IAppServiceConnection _appServiceConnection;
private readonly Dictionary<int, Action<string>> _terminalOutputHandlers;
private int _nextTerminalId = 0;
public event EventHandler<int> TerminalExited;
public TrayProcessCommunicationService(ISettingsService settingsService)
{
_settingsService = settingsService;
_terminalOutputHandlers = new Dictionary<int, Action<string>>();
}
public int GetNextTerminalId()
{
return _nextTerminalId++;
}
public async Task<GetAvailablePortResponse> GetAvailablePort()
{
var request = new GetAvailablePortRequest();
var responseMessage = await _appServiceConnection.SendMessageAsync(CreateMessage(request));
return JsonConvert.DeserializeObject<GetAvailablePortResponse>(responseMessage[MessageKeys.Content]);
}
public async Task<CreateTerminalResponse> CreateTerminal(int id, TerminalSize size, ShellProfile shellProfile, SessionType sessionType)
{
var request = new CreateTerminalRequest
{
Id = id,
Size = size,
Profile = shellProfile,
SessionType = sessionType
};
var responseMessage = await _appServiceConnection.SendMessageAsync(CreateMessage(request));
return JsonConvert.DeserializeObject<CreateTerminalResponse>(responseMessage[MessageKeys.Content]);
}
public void Initialize(IAppServiceConnection appServiceConnection)
{
_appServiceConnection = appServiceConnection;
_appServiceConnection.MessageReceived += OnMessageReceived;
}
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
{
TerminalId = id,
NewSize = size
};
return _appServiceConnection.SendMessageAsync(CreateMessage(request));
}
public void SubscribeForTerminalOutput(int terminalId, Action<string> callback)
{
_terminalOutputHandlers[terminalId] = callback;
}
public Task UpdateToggleWindowKeyBindings()
{
var keyBindings = _settingsService.GetCommandKeyBindings()[nameof(Command.ToggleWindow)];
var request = new SetToggleWindowKeyBindingsRequest
{
KeyBindings = keyBindings
};
return _appServiceConnection.SendMessageAsync(CreateMessage(request));
}
public Task WriteText(int id, string text)
{
var request = new WriteTextRequest
{
TerminalId = id,
Text = text
};
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)
};
}
}
}
@@ -0,0 +1,56 @@
using System;
using Windows.ApplicationModel;
using Newtonsoft.Json;
using RestSharp;
using System.Threading.Tasks;
namespace FluentTerminal.App.Services.Implementation
{
public class UpdateService : IUpdateService
{
private const string apiEndpoint = "https://api.github.com";
private readonly INotificationService _notificationService;
public UpdateService(INotificationService notificationService)
{
_notificationService = notificationService;
}
public async Task CheckForUpdate(bool notifyNoUpdate = false)
{
var latest = await GetLatestVersionAsync();
if (latest > GetCurrentVersion())
{
_notificationService.ShowNotification("Update available",
"Click to open the releases page.", "https://github.com/felixse/FluentTerminal/releases");
}
else if (notifyNoUpdate)
{
_notificationService.ShowNotification("No update available", "You're up to date!");
}
}
public Version GetCurrentVersion()
{
var currentVersion = Package.Current.Id.Version;
return new Version(currentVersion.Major, currentVersion.Minor, currentVersion.Build, currentVersion.Revision);
}
public async Task<Version> GetLatestVersionAsync()
{
var restClient = new RestClient(apiEndpoint);
var restRequest = new RestRequest("/repos/felixse/fluentterminal/releases", Method.GET);
var restResponse = await restClient.ExecuteTaskAsync(restRequest);
if (restResponse.IsSuccessful)
{
dynamic restResponseData = JsonConvert.DeserializeObject(restResponse.Content);
string tag = restResponseData[0].tag_name;
var latestVersion = new Version(tag);
return new Version(latestVersion.Major, latestVersion.Minor, latestVersion.Build, latestVersion.Revision);
}
return new Version(0, 0, 0, 0);
}
}
}
+126
View File
@@ -0,0 +1,126 @@
using FluentTerminal.Models;
using FluentTerminal.Models.Enums;
using System;
using System.Threading.Tasks;
namespace FluentTerminal.App.Services
{
public class Terminal
{
private readonly ITrayProcessCommunicationService _trayProcessCommunicationService;
private Func<Task<string>> _selectedTextCallback;
public Terminal(ITrayProcessCommunicationService trayProcessCommunicationService)
{
_trayProcessCommunicationService = trayProcessCommunicationService;
Id = _trayProcessCommunicationService.GetNextTerminalId();
}
/// <summary>
/// To be observed by both view and viewmodel
/// </summary>
public event EventHandler Closed;
/// <summary>
/// to be observed by viewmodel
/// </summary>
public event EventHandler<string> KeyboardCommandReceived;
/// <summary>
/// to be observed by view
/// </summary>
public event EventHandler<string> OutputReceived;
/// <summary>
/// to be observed by viewmodel
/// </summary>
public event EventHandler<TerminalSize> SizeChanged;
/// <summary>
/// to be observed by viewmodel
/// </summary>
public event EventHandler<string> TitleChanged;
public string FallbackTitle { get; private set; }
public int Id { get; }
/// <summary>
/// To be called by either view or viewmodel
/// </summary>
public async Task Close()
{
await _trayProcessCommunicationService.CloseTerminal(Id).ConfigureAwait(true);
Closed?.Invoke(this, System.EventArgs.Empty);
}
/// <summary>
/// to be called by viewmodel
/// </summary>
/// <returns></returns>
public Task<string> GetSelectedText()
{
return _selectedTextCallback();
}
/// <summary>
/// to be called by view
/// </summary>
public void ProcessKeyboardCommand(string command)
{
KeyboardCommandReceived?.Invoke(this, command);
}
/// <summary>
/// To be set by view
/// </summary>
public void RegisterSelectedTextCallback(Func<Task<string>> selectedTextCallback)
{
_selectedTextCallback = selectedTextCallback;
}
/// <summary>
/// To be called by view
/// </summary>
public async Task SetSize(TerminalSize size)
{
await _trayProcessCommunicationService.ResizeTerminal(Id, size);
SizeChanged?.Invoke(this, size);
}
/// <summary>
/// to be called by view
/// </summary>
public void SetTitle(string title)
{
if (string.IsNullOrWhiteSpace(title))
{
title = FallbackTitle;
}
TitleChanged?.Invoke(this, title);
}
/// <summary>
/// to be called by view when ready
/// </summary>
/// <param name="shellProfile"></param>
/// <param name="size"></param>
/// <param name="sessionType"></param>
public async Task StartShellProcess(ShellProfile shellProfile, TerminalSize size, SessionType sessionType)
{
_trayProcessCommunicationService.SubscribeForTerminalOutput(Id, t => OutputReceived?.Invoke(this, t));
var response = await _trayProcessCommunicationService.CreateTerminal(Id, size, shellProfile, sessionType).ConfigureAwait(true);
if (response.Success)
{
FallbackTitle = response.ShellExecutableName;
SetTitle(FallbackTitle);
}
}
public Task Write(string text)
{
return _trayProcessCommunicationService.WriteText(Id, text);
}
}
}
@@ -0,0 +1,21 @@
using System;
using System.ComponentModel;
namespace FluentTerminal.App.Services.Utilities
{
public static class EnumHelper
{
public static string GetEnumDescription(Enum value)
{
var fieldInfo = value.GetType().GetField(value.ToString());
var attributes = (DescriptionAttribute[])fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attributes?.Length > 0)
{
return attributes[0].Description;
}
return value.ToString();
}
}
}
@@ -0,0 +1,27 @@
using FluentTerminal.Models;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System.Reflection;
namespace FluentTerminal.App.Services.Utilities
{
public class TerminalThemeContractResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty property = base.CreateProperty(member, memberSerialization);
if (property.DeclaringType == typeof(TerminalTheme) && property.PropertyName == nameof(TerminalTheme.PreInstalled))
{
property.ShouldSerialize = (_) => false;
}
if (property.DeclaringType == typeof(TerminalTheme) && property.PropertyName == nameof(TerminalTheme.Id))
{
property.ShouldSerialize = (_) => false;
}
return property;
}
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

@@ -1,171 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
<ProjectGuid>{812A09E4-3A05-41AB-8E9A-DC4389B5D73E}</ProjectGuid>
<OutputType>AppContainerExe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>FluentTerminal.App.Test</RootNamespace>
<AssemblyName>FluentTerminal.App.Test</AssemblyName>
<DefaultLanguage>de-DE</DefaultLanguage>
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
<TargetPlatformVersion>10.0.16299.0</TargetPlatformVersion>
<TargetPlatformMinVersion>10.0.16299.0</TargetPlatformMinVersion>
<MinimumVisualStudioVersion>14</MinimumVisualStudioVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<PackageCertificateKeyFile>FluentTerminal.App.Test_TemporaryKey.pfx</PackageCertificateKeyFile>
<UnitTestPlatformVersion Condition="'$(UnitTestPlatformVersion)' == ''">$(VisualStudioVersion)</UnitTestPlatformVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x86\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<NoWarn>;2008</NoWarn>
<DebugType>full</DebugType>
<PlatformTarget>x86</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
<OutputPath>bin\x86\Release\</OutputPath>
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<Optimize>true</Optimize>
<NoWarn>;2008</NoWarn>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x86</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|ARM'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\ARM\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<NoWarn>;2008</NoWarn>
<DebugType>full</DebugType>
<PlatformTarget>ARM</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|ARM'">
<OutputPath>bin\ARM\Release\</OutputPath>
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<Optimize>true</Optimize>
<NoWarn>;2008</NoWarn>
<DebugType>pdbonly</DebugType>
<PlatformTarget>ARM</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<NoWarn>;2008</NoWarn>
<DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath>
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<Optimize>true</Optimize>
<NoWarn>;2008</NoWarn>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
</PropertyGroup>
<PropertyGroup>
<RestoreProjectStyle>PackageReference</RestoreProjectStyle>
</PropertyGroup>
<ItemGroup>
<SDKReference Include="TestPlatform.Universal, Version=$(UnitTestPlatformVersion)" />
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Services\DialogServiceTests.cs" />
<Compile Include="Services\KeyboardCommandServiceTests.cs" />
<Compile Include="UnitTestApp.xaml.cs">
<DependentUpon>UnitTestApp.xaml</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<ApplicationDefinition Include="UnitTestApp.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</ApplicationDefinition>
</ItemGroup>
<ItemGroup>
<AppxManifest Include="Package.appxmanifest">
<SubType>Designer</SubType>
</AppxManifest>
<None Include="FluentTerminal.App.Test_TemporaryKey.pfx" />
</ItemGroup>
<ItemGroup>
<Content Include="Properties\UnitTestApp.rd.xml" />
<Content Include="Assets\LockScreenLogo.scale-200.png" />
<Content Include="Assets\SplashScreen.scale-200.png" />
<Content Include="Assets\Square150x150Logo.scale-200.png" />
<Content Include="Assets\Square44x44Logo.scale-200.png" />
<Content Include="Assets\Square44x44Logo.targetsize-24_altform-unplated.png" />
<Content Include="Assets\StoreLogo.png" />
<Content Include="Assets\Wide310x150Logo.scale-200.png" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="AutoFixture">
<Version>4.2.1</Version>
</PackageReference>
<PackageReference Include="FluentAssertions">
<Version>5.3.0</Version>
</PackageReference>
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
<Version>6.1.2</Version>
</PackageReference>
<PackageReference Include="Moq">
<Version>4.8.2</Version>
</PackageReference>
<PackageReference Include="MSTest.TestAdapter">
<Version>1.2.1</Version>
</PackageReference>
<PackageReference Include="MSTest.TestFramework">
<Version>1.2.1</Version>
</PackageReference>
</ItemGroup>
<ItemGroup>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\FluentTerminal.App\FluentTerminal.App.csproj">
<Project>{41020100-3414-4e0a-9a8e-92d3c4179df8}</Project>
<Name>FluentTerminal.App</Name>
</ProjectReference>
<ProjectReference Include="..\FluentTerminal.Models\FluentTerminal.Models.csproj">
<Project>{5EA40453-A0AA-449E-8406-D6EA51D7E70E}</Project>
<Name>FluentTerminal.Models</Name>
</ProjectReference>
</ItemGroup>
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &lt; '14.0' ">
<VisualStudioVersion>14.0</VisualStudioVersion>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
@@ -1,45 +0,0 @@
<?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"
IgnorableNamespaces="uap mp">
<Identity Name="63c2a56f-7075-408a-a37d-24bb7fb21665"
Publisher="CN=Felix"
Version="1.0.0.0" />
<mp:PhoneIdentity PhoneProductId="63c2a56f-7075-408a-a37d-24bb7fb21665" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
<Properties>
<DisplayName>FluentTerminal.App.Test</DisplayName>
<PublisherDisplayName>Felix</PublisherDisplayName>
<Logo>Assets\StoreLogo.png</Logo>
</Properties>
<Dependencies>
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.0.0" MaxVersionTested="10.0.0.0" />
</Dependencies>
<Resources>
<Resource Language="x-generate" />
</Resources>
<Applications>
<Application Id="vstest.executionengine.universal.App"
Executable="$targetnametoken$.exe"
EntryPoint="FluentTerminal.App.Test.App">
<uap:VisualElements
DisplayName="FluentTerminal.App.Test"
Square150x150Logo="Assets\Square150x150Logo.png"
Square44x44Logo="Assets\Square44x44Logo.png"
Description="FluentTerminal.App.Test"
BackgroundColor="transparent">
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png"/>
<uap:SplashScreen Image="Assets\SplashScreen.png" />
</uap:VisualElements>
</Application>
</Applications>
<Capabilities>
<Capability Name="internetClientServer" />
</Capabilities>
</Package>
@@ -1,18 +0,0 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
[assembly: AssemblyTitle("FluentTerminal.App.Test")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("FluentTerminal.App.Test")]
[assembly: AssemblyCopyright("Copyright © 2018")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: AssemblyMetadata("TargetPlatform","UAP")]
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: ComVisible(false)]
@@ -1,29 +0,0 @@
<!--
This file contains Runtime Directives used by .NET Native. The defaults here are suitable for most
developers. However, you can modify these parameters to modify the behavior of the .NET Native
optimizer.
Runtime Directives are documented at http://go.microsoft.com/fwlink/?LinkID=391919
To fully enable reflection for App1.MyClass and all of its public/private members
<Type Name="App1.MyClass" Dynamic="Required All"/>
To enable dynamic creation of the specific instantiation of AppClass<T> over System.Int32
<TypeInstantiation Name="App1.AppClass" Arguments="System.Int32" Activate="Required Public" />
Using the Namespace directive to apply reflection policy to all the types in a particular namespace
<Namespace Name="DataClasses.ViewModels" Seralize="All" />
-->
<Directives xmlns="http://schemas.microsoft.com/netfx/2013/01/metadata">
<Application>
<!--
An Assembly element with Name="*Application*" applies to all assemblies in
the application package. The asterisks are not wildcards.
-->
<Assembly Name="*Application*" Dynamic="Required All" />
<!-- Add your application specific runtime directives here. -->
</Application>
</Directives>
@@ -1,62 +0,0 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using AutoFixture;
using FluentAssertions;
using FluentTerminal.App.Services;
using FluentTerminal.App.Services.Implementation;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
namespace FluentTerminal.App.Test.Services
{
[TestClass]
public class DialogServiceTests
{
private Fixture _fixture;
[TestInitialize]
public void TestInitialize()
{
_fixture = new Fixture();
}
[TestMethod]
public void ShowDialogAsync_TitleIsEmpty_ThrowsArgumentNullException()
{
var title = string.Empty;
var content = _fixture.Create<string>();
var buttons = _fixture.CreateMany<DialogButton>(2);
var dialogService = new DialogService(Mock.Of<ISettingsService>());
Func<Task<DialogButton>> invoke = () => dialogService.ShowMessageDialogAsnyc(title, content, buttons.ElementAt(0), buttons.ElementAt(1));
invoke.Should().Throw<ArgumentNullException>().And.ParamName.Should().Be("title");
}
[TestMethod]
public void ShowDialogAsync_ContentIsEmpty_ThrowsArgumentNullException()
{
var title = _fixture.Create<string>();
var content = string.Empty;
var buttons = _fixture.CreateMany<DialogButton>(2);
var dialogService = new DialogService(Mock.Of<ISettingsService>());
Func<Task<DialogButton>> invoke = () => dialogService.ShowMessageDialogAsnyc(title, content, buttons.ElementAt(0), buttons.ElementAt(1));
invoke.Should().Throw<ArgumentNullException>().And.ParamName.Should().Be("content");
}
[TestMethod]
public void ShowDialogAsync_NoButtonsPassed_ThrowsArgumentException()
{
var title = _fixture.Create<string>();
var content = _fixture.Create<string>();
var dialogService = new DialogService(Mock.Of<ISettingsService>());
Func<Task<DialogButton>> invoke = () => dialogService.ShowMessageDialogAsnyc(title, content);
invoke.Should().Throw<ArgumentException>().And.ParamName.Should().Be("buttons");
}
}
}
-8
View File
@@ -1,8 +0,0 @@
<Application
x:Class="FluentTerminal.App.Test.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:FluentTerminal.App.Test"
RequestedTheme="Light">
</Application>
-102
View File
@@ -1,102 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.ApplicationModel;
using Windows.ApplicationModel.Activation;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
namespace FluentTerminal.App.Test
{
/// <summary>
/// Provides application-specific behavior to supplement the default Application class.
/// </summary>
sealed partial class App : Application
{
/// <summary>
/// Initializes the singleton application object. This is the first line of authored code
/// executed, and as such is the logical equivalent of main() or WinMain().
/// </summary>
public App()
{
this.InitializeComponent();
this.Suspending += OnSuspending;
}
/// <summary>
/// Invoked when the application is launched normally by the end user. Other entry points
/// will be used such as when the application is launched to open a specific file.
/// </summary>
/// <param name="e">Details about the launch request and process.</param>
protected override void OnLaunched(LaunchActivatedEventArgs e)
{
#if DEBUG
if (System.Diagnostics.Debugger.IsAttached)
{
this.DebugSettings.EnableFrameRateCounter = true;
}
#endif
Frame rootFrame = Window.Current.Content as Frame;
// Do not repeat app initialization when the Window already has content,
// just ensure that the window is active
if (rootFrame == null)
{
// Create a Frame to act as the navigation context and navigate to the first page
rootFrame = new Frame();
rootFrame.NavigationFailed += OnNavigationFailed;
if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
{
//TODO: Load state from previously suspended application
}
// Place the frame in the current Window
Window.Current.Content = rootFrame;
}
Microsoft.VisualStudio.TestPlatform.TestExecutor.UnitTestClient.CreateDefaultUI();
// Ensure the current window is active
Window.Current.Activate();
Microsoft.VisualStudio.TestPlatform.TestExecutor.UnitTestClient.Run(e.Arguments);
}
/// <summary>
/// Invoked when Navigation to a certain page fails
/// </summary>
/// <param name="sender">The Frame which failed navigation</param>
/// <param name="e">Details about the navigation failure</param>
void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
{
throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
}
/// <summary>
/// Invoked when application execution is being suspended. Application state is saved
/// without knowing whether the application will be terminated or resumed with the contents
/// of memory still intact.
/// </summary>
/// <param name="sender">The source of the suspend request.</param>
/// <param name="e">Details about the suspend request.</param>
private void OnSuspending(object sender, SuspendingEventArgs e)
{
var deferral = e.SuspendingOperation.GetDeferral();
//TODO: Save application state and stop any background activity
deferral.Complete();
}
}
}
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Fleck" Version="1.0.3" />
<PackageReference Include="MvvmLightLibs" Version="5.4.1" />
<PackageReference Include="ReactiveUI" Version="9.4.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\FluentTerminal.App.Services\FluentTerminal.App.Services.csproj" />
<ProjectReference Include="..\FluentTerminal.Models\FluentTerminal.Models.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,124 @@
using FluentTerminal.App.ViewModels.Utilities;
using System;
using System.Threading.Tasks;
using System.Windows.Input;
namespace FluentTerminal.App.ViewModels.Infrastructure
{
public class AsyncCommand : IAsyncCommand
{
public event EventHandler CanExecuteChanged;
private bool _isExecuting;
private readonly Func<Task> _execute;
private readonly Func<bool> _canExecute;
private readonly IErrorHandler _errorHandler;
public AsyncCommand(
Func<Task> execute,
Func<bool> canExecute = null,
IErrorHandler errorHandler = null)
{
_execute = execute;
_canExecute = canExecute;
_errorHandler = errorHandler;
}
public bool CanExecute()
{
return !_isExecuting && (_canExecute?.Invoke() ?? true);
}
public async Task ExecuteAsync()
{
if (CanExecute())
{
try
{
_isExecuting = true;
await _execute();
}
finally
{
_isExecuting = false;
}
}
RaiseCanExecuteChanged();
}
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
#region Explicit implementations
bool ICommand.CanExecute(object parameter)
{
return CanExecute();
}
void ICommand.Execute(object parameter)
{
ExecuteAsync().FireAndForgetSafeAsync(_errorHandler);
}
#endregion
}
public class AsyncCommand<T> : IAsyncCommand<T>
{
public event EventHandler CanExecuteChanged;
private bool _isExecuting;
private readonly Func<T, Task> _execute;
private readonly Func<T, bool> _canExecute;
private readonly IErrorHandler _errorHandler;
public AsyncCommand(Func<T, Task> execute, Func<T, bool> canExecute = null, IErrorHandler errorHandler = null)
{
_execute = execute;
_canExecute = canExecute;
_errorHandler = errorHandler;
}
public bool CanExecute(T parameter)
{
return !_isExecuting && (_canExecute?.Invoke(parameter) ?? true);
}
public async Task ExecuteAsync(T parameter)
{
if (CanExecute(parameter))
{
try
{
_isExecuting = true;
await _execute(parameter);
}
finally
{
_isExecuting = false;
}
}
RaiseCanExecuteChanged();
}
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
#region Explicit implementations
bool ICommand.CanExecute(object parameter)
{
return CanExecute((T)parameter);
}
void ICommand.Execute(object parameter)
{
ExecuteAsync((T)parameter).FireAndForgetSafeAsync(_errorHandler);
}
#endregion
}
}
@@ -0,0 +1,20 @@
using System.Threading.Tasks;
using System.Windows.Input;
namespace FluentTerminal.App.ViewModels.Infrastructure
{
/// <summary>
/// based on: https://github.com/johnthiriet/AsyncVoid
/// </summary>
public interface IAsyncCommand : ICommand
{
Task ExecuteAsync();
bool CanExecute();
}
public interface IAsyncCommand<T> : ICommand
{
Task ExecuteAsync(T parameter);
bool CanExecute(T parameter);
}
}
@@ -0,0 +1,9 @@
using System;
namespace FluentTerminal.App.ViewModels.Infrastructure
{
public interface IErrorHandler
{
void HandleError(Exception ex);
}
}
@@ -0,0 +1,342 @@
using FluentTerminal.App.Services;
using FluentTerminal.App.Services.EventArgs;
using FluentTerminal.Models;
using FluentTerminal.Models.Enums;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using System;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Threading.Tasks;
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 TerminalViewModel _selectedTerminal;
private TabsPosition _tabsPosition;
public MainViewModel(ISettingsService settingsService, ITrayProcessCommunicationService trayProcessCommunicationService, IDialogService dialogService, IKeyboardCommandService keyboardCommandService,
IApplicationView applicationView, IDispatcherTimer dispatcherTimer, IClipboardService clipboardService)
{
_settingsService = settingsService;
_settingsService.CurrentThemeChanged += OnCurrentThemeChanged;
_settingsService.ApplicationSettingsChanged += OnApplicationSettingsChanged;
_settingsService.TerminalOptionsChanged += OnTerminalOptionsChanged;
_settingsService.ShellProfileAdded += OnShellProfileAdded;
_settingsService.ShellProfileDeleted += OnShellProfileDeleted;
_trayProcessCommunicationService = trayProcessCommunicationService;
_dialogService = dialogService;
_applicationView = applicationView;
_dispatcherTimer = dispatcherTimer;
_clipboardService = clipboardService;
_keyboardCommandService = keyboardCommandService;
_keyboardCommandService.RegisterCommandHandler(nameof(Command.NewTab), () => AddTerminal(null, false, Guid.Empty));
_keyboardCommandService.RegisterCommandHandler(nameof(Command.ConfigurableNewTab), () => AddTerminal(null, true, Guid.Empty));
_keyboardCommandService.RegisterCommandHandler(nameof(Command.ChangeTabTitle), () => SelectedTerminal.EditTitle());
_keyboardCommandService.RegisterCommandHandler(nameof(Command.CloseTab), CloseCurrentTab);
// Add all of the commands for switching to a tab of a given ID, if there's one open there
for (int i = 0; i < 9; i++)
{
var switchCmd = Command.SwitchToTerm1 + i;
int tabNumber = i;
void handler() => SelectTabNumber(tabNumber);
_keyboardCommandService.RegisterCommandHandler(switchCmd.ToString(), handler);
}
_keyboardCommandService.RegisterCommandHandler(nameof(Command.NextTab), SelectNextTab);
_keyboardCommandService.RegisterCommandHandler(nameof(Command.PreviousTab), SelectPreviousTab);
_keyboardCommandService.RegisterCommandHandler(nameof(Command.NewWindow), () => NewWindow(false));
_keyboardCommandService.RegisterCommandHandler(nameof(Command.ConfigurableNewWindow), () => NewWindow(true));
_keyboardCommandService.RegisterCommandHandler(nameof(Command.ShowSettings), ShowSettings);
_keyboardCommandService.RegisterCommandHandler(nameof(Command.ToggleFullScreen), ToggleFullScreen);
foreach (ShellProfile profile in _settingsService.GetShellProfiles())
{
_keyboardCommandService.RegisterCommandHandler(profile.Id.ToString(), () => AddTerminal(profile.WorkingDirectory, false, profile.Id));
}
var currentTheme = _settingsService.GetCurrentTheme();
var options = _settingsService.GetTerminalOptions();
Background = currentTheme.Colors.Background;
BackgroundOpacity = options.BackgroundOpacity;
_applicationSettings = _settingsService.GetApplicationSettings();
TabsPosition = _applicationSettings.TabsPosition;
AddTerminalCommand = new RelayCommand(() => AddTerminal(null, false, Guid.Empty));
ShowAboutCommand = new RelayCommand(ShowAbout);
ShowSettingsCommand = new RelayCommand(ShowSettings);
_applicationView.CloseRequested += OnCloseRequest;
Terminals.CollectionChanged += OnTerminalsCollectionChanged;
}
private void OnShellProfileDeleted(object sender, Guid e)
{
_keyboardCommandService.DeregisterCommandHandler(e.ToString());
}
private void OnShellProfileAdded(object sender, ShellProfile e)
{
_keyboardCommandService.RegisterCommandHandler(e.Id.ToString(), () => AddTerminal(e.WorkingDirectory, false, e.Id));
}
private void OnTerminalsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
RaisePropertyChanged(nameof(ShowTabsOnTop));
RaisePropertyChanged(nameof(ShowTabsOnBottom));
}
public event EventHandler Closed;
public event EventHandler<NewWindowRequestedEventArgs> NewWindowRequested;
public event EventHandler ShowSettingsRequested;
public event EventHandler ShowAboutRequested;
public RelayCommand AddTerminalCommand { get; }
public bool ShowTabsOnTop
{
get => TabsPosition == TabsPosition.Top && (Terminals.Count > 1 || _applicationSettings.AlwaysShowTabs);
}
public bool ShowTabsOnBottom
{
get => TabsPosition == TabsPosition.Bottom && (Terminals.Count > 1 || _applicationSettings.AlwaysShowTabs);
}
public string Background
{
get => _background;
set => Set(ref _background, value);
}
public double BackgroundOpacity
{
get => _backgroundOpacity;
set => Set(ref _backgroundOpacity, value);
}
public TerminalViewModel SelectedTerminal
{
get => _selectedTerminal;
set
{
if (SelectedTerminal != null)
{
_selectedTerminal.IsSelected = false;
}
if (Set(ref _selectedTerminal, value))
{
SelectedTerminal?.FocusTerminal();
if (SelectedTerminal != null)
{
SelectedTerminal.IsSelected = true;
}
}
}
}
public RelayCommand ShowAboutCommand { get; }
public RelayCommand ShowSettingsCommand { get; }
public TabsPosition TabsPosition
{
get => _tabsPosition;
set => Set(ref _tabsPosition, value);
}
public ObservableCollection<TerminalViewModel> Terminals { get; } = new ObservableCollection<TerminalViewModel>();
/// <summary>
/// Add a new terminal window, either with a specified terminal profile, with the default, or by showing the profile selection dialog.
/// </summary>
/// <returns></returns>
public Task AddTerminal(string startupDirectory, bool showProfileSelection, Guid profileId)
{
return _applicationView.RunOnDispatcherThread(async () =>
{
ShellProfile profile = null;
if (showProfileSelection)
{
profile = await _dialogService.ShowProfileSelectionDialogAsync();
if (profile == null)
{
if (Terminals.Count == 0)
{
Closed?.Invoke(this, EventArgs.Empty);
_applicationView.TryClose();
}
return;
}
}
else if (profileId == Guid.Empty)
{
profile = _settingsService.GetDefaultShellProfile();
}
else
{
profile = _settingsService.GetShellProfile(profileId);
}
var terminal = new TerminalViewModel(_settingsService, _trayProcessCommunicationService, _dialogService, _keyboardCommandService,
_applicationSettings, startupDirectory, profile, _applicationView, _dispatcherTimer, _clipboardService);
terminal.Closed += OnTerminalCloseRequested;
Terminals.Add(terminal);
SelectedTerminal = terminal;
});
}
public void CloseAllTerminals()
{
foreach (var terminal in Terminals)
{
terminal.Close();
}
}
private void CloseCurrentTab()
{
SelectedTerminal?.CloseCommand.Execute(null);
}
private void NewWindow(bool showProfileSelection)
{
var args = new NewWindowRequestedEventArgs
{
ShowProfileSelection = showProfileSelection
};
NewWindowRequested?.Invoke(this, args);
}
private async void OnApplicationSettingsChanged(object sender, ApplicationSettings e)
{
await _applicationView.RunOnDispatcherThread(() =>
{
_applicationSettings = e;
TabsPosition = e.TabsPosition;
RaisePropertyChanged(nameof(ShowTabsOnTop));
RaisePropertyChanged(nameof(ShowTabsOnBottom));
});
}
private async Task OnCloseRequest(object sender, CancelableEventArgs e)
{
if (_applicationSettings.ConfirmClosingWindows)
{
var result = await _dialogService.ShowMessageDialogAsnyc("Please confirm", "Are you sure you want to close this window?", DialogButton.OK, DialogButton.Cancel).ConfigureAwait(false);
if (result == DialogButton.OK)
{
CloseAllTerminals();
Closed?.Invoke(this, EventArgs.Empty);
}
else
{
e.Cancelled = true;
}
}
else
{
CloseAllTerminals();
Closed?.Invoke(this, EventArgs.Empty);
}
}
private async void OnCurrentThemeChanged(object sender, Guid e)
{
await _applicationView.RunOnDispatcherThread(() =>
{
var currentTheme = _settingsService.GetTheme(e);
Background = currentTheme.Colors.Background;
});
}
private void OnTerminalCloseRequested(object sender, EventArgs e)
{
if (sender is TerminalViewModel terminal)
{
terminal.Close();
if (SelectedTerminal == terminal)
{
SelectedTerminal = Terminals.LastOrDefault(t => t != terminal);
}
Terminals.Remove(terminal);
if (Terminals.Count == 0)
{
Closed?.Invoke(this, EventArgs.Empty);
_applicationView.TryClose();
}
}
}
private async void OnTerminalOptionsChanged(object sender, TerminalOptions e)
{
await _applicationView.RunOnDispatcherThread(() =>
{
BackgroundOpacity = e.BackgroundOpacity;
});
}
private void SelectTabNumber(int tabNumber)
{
if (tabNumber < Terminals.Count)
{
SelectedTerminal = Terminals[tabNumber];
}
}
private void SelectNextTab()
{
var currentIndex = Terminals.IndexOf(SelectedTerminal);
var nextIndex = (currentIndex + 1) % Terminals.Count;
SelectedTerminal = Terminals[nextIndex];
}
private void SelectPreviousTab()
{
var currentIndex = Terminals.IndexOf(SelectedTerminal);
var previousIndex = (currentIndex - 1 + Terminals.Count) % Terminals.Count;
SelectedTerminal = Terminals[previousIndex];
}
private void ShowAbout()
{
ShowAboutRequested?.Invoke(this, EventArgs.Empty);
}
private void ShowSettings()
{
ShowSettingsRequested?.Invoke(this, EventArgs.Empty);
}
private void ToggleFullScreen()
{
_applicationView.ToggleFullScreen();
}
}
}
@@ -0,0 +1,79 @@
using FluentTerminal.App.Services;
using FluentTerminal.App.ViewModels.Infrastructure;
using GalaSoft.MvvmLight;
using System;
using System.Threading.Tasks;
namespace FluentTerminal.App.ViewModels.Settings
{
public class AboutPageViewModel : ViewModelBase
{
private const string BaseUrl = "https://github.com/felixse/FluentTerminal/releases/tag/";
private readonly ISettingsService _settingsService;
private readonly IUpdateService _updateService;
private string _latestVersion;
private readonly IApplicationView _applicationView;
public AboutPageViewModel(ISettingsService settingsService, IUpdateService updateService, IApplicationView applicationView)
{
_settingsService = settingsService;
_updateService = updateService;
_applicationView = applicationView;
CheckForUpdatesCommand = new AsyncCommand(() => CheckForUpdate(true));
}
public IAsyncCommand CheckForUpdatesCommand { get; }
public string CurrentVersion
{
get
{
var version = _updateService.GetCurrentVersion();
return ConvertVersionToString(version);
}
}
public string CurrentVersionReleaseNotesURL => BaseUrl + CurrentVersion;
public string LatestVersion
{
get => _latestVersion;
set
{
if (Set(ref _latestVersion, value))
{
RaisePropertyChanged(nameof(LatestVersionFound));
RaisePropertyChanged(nameof(LatestVersionLoading));
RaisePropertyChanged(nameof(LatestVersionNotFound));
RaisePropertyChanged(nameof(LatestVersionReleaseNotesURL));
}
}
}
public bool LatestVersionFound => !LatestVersionNotFound && !LatestVersionLoading;
public bool LatestVersionLoading => LatestVersion == null;
public bool LatestVersionNotFound => LatestVersion == "0.0.0.0";
public string LatestVersionReleaseNotesURL => BaseUrl + LatestVersion;
public Task OnNavigatedTo()
{
return CheckForUpdate(false);
}
public async Task CheckForUpdate(bool notifyNoUpdate)
{
var version = ConvertVersionToString(await _updateService.GetLatestVersionAsync());
await _applicationView.RunOnDispatcherThread(() => LatestVersion = version);
await _updateService.CheckForUpdate(notifyNoUpdate);
}
private string ConvertVersionToString(Version version)
{
return string.Format("{0}.{1}.{2}.{3}", version.Major, version.Minor, version.Build, version.Revision);
}
}
}
@@ -0,0 +1,332 @@
using FluentTerminal.App.Services;
using FluentTerminal.Models;
using FluentTerminal.Models.Enums;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using System.Threading.Tasks;
namespace FluentTerminal.App.ViewModels.Settings
{
public class GeneralPageViewModel : ViewModelBase
{
private readonly ApplicationSettings _applicationSettings;
private readonly IDefaultValueProvider _defaultValueProvider;
private readonly IDialogService _dialogService;
private readonly ISettingsService _settingsService;
private readonly IStartupTaskService _startupTaskService;
private bool _canEnableStartupTask;
private bool _editingNewTerminalLocation;
private bool _editingTabsPosition;
private bool _editingInactiveTabColorMode;
private bool _startupTaskEnabled;
private bool _shouldRestartForTrayMessage;
private string _startupTaskErrorMessage;
public GeneralPageViewModel(ISettingsService settingsService, IDialogService dialogService, IDefaultValueProvider defaultValueProvider, IStartupTaskService startupTaskService)
{
_settingsService = settingsService;
_dialogService = dialogService;
_defaultValueProvider = defaultValueProvider;
_startupTaskService = startupTaskService;
_applicationSettings = _settingsService.GetApplicationSettings();
RestoreDefaultsCommand = new RelayCommand(async () => await RestoreDefaults().ConfigureAwait(false));
}
public async Task OnNavigatedTo()
{
var startupTaskStatus = await _startupTaskService.GetStatus();
SetStartupTaskPropertiesForStatus(startupTaskStatus);
}
public bool AlwaysShowTabs
{
get => _applicationSettings.AlwaysShowTabs;
set
{
if (_applicationSettings.AlwaysShowTabs != value)
{
_applicationSettings.AlwaysShowTabs = value;
_settingsService.SaveApplicationSettings(_applicationSettings);
RaisePropertyChanged();
}
}
}
public bool AlwaysUseWinPty
{
get => _applicationSettings.AlwaysUseWinPty;
set
{
if (_applicationSettings.AlwaysUseWinPty != value)
{
_applicationSettings.AlwaysUseWinPty = value;
_settingsService.SaveApplicationSettings(_applicationSettings);
RaisePropertyChanged();
}
}
}
public bool EnableTrayIcon
{
get => _applicationSettings.EnableTrayIcon;
set
{
if (_applicationSettings.EnableTrayIcon != value)
{
_applicationSettings.EnableTrayIcon = value;
_settingsService.SaveApplicationSettings(_applicationSettings);
RaisePropertyChanged();
// Toggle message telling user to restart the program
ShouldRestartForTrayMessage = !ShouldRestartForTrayMessage;
}
}
}
public bool ShouldRestartForTrayMessage
{
get => _shouldRestartForTrayMessage;
set => Set(ref _shouldRestartForTrayMessage, value);
}
public bool ShowNewOutputIndicator
{
get => _applicationSettings.ShowNewOutputIndicator;
set
{
if (_applicationSettings.ShowNewOutputIndicator != value)
{
_applicationSettings.ShowNewOutputIndicator = value;
_settingsService.SaveApplicationSettings(_applicationSettings);
RaisePropertyChanged();
}
}
}
public bool BottomIsSelected
{
get => TabsPosition == TabsPosition.Bottom;
set => TabsPosition = TabsPosition.Bottom;
}
public bool CanEnableStartupTask
{
get => _canEnableStartupTask;
set => Set(ref _canEnableStartupTask, value);
}
public bool ConfirmClosingTabs
{
get => _applicationSettings.ConfirmClosingTabs;
set
{
if (_applicationSettings.ConfirmClosingTabs != value)
{
_applicationSettings.ConfirmClosingTabs = value;
_settingsService.SaveApplicationSettings(_applicationSettings);
RaisePropertyChanged();
}
}
}
public bool ConfirmClosingWindows
{
get => _applicationSettings.ConfirmClosingWindows;
set
{
if (_applicationSettings.ConfirmClosingWindows != value)
{
_applicationSettings.ConfirmClosingWindows = value;
_settingsService.SaveApplicationSettings(_applicationSettings);
RaisePropertyChanged();
}
}
}
public NewTerminalLocation NewTerminalLocation
{
get => _applicationSettings.NewTerminalLocation;
set
{
if (_applicationSettings.NewTerminalLocation != value && !_editingNewTerminalLocation)
{
_editingNewTerminalLocation = true;
_applicationSettings.NewTerminalLocation = value;
_settingsService.SaveApplicationSettings(_applicationSettings);
RaisePropertyChanged();
RaisePropertyChanged(nameof(WindowIsSelected));
RaisePropertyChanged(nameof(TabIsSelected));
_editingNewTerminalLocation = false;
}
}
}
public RelayCommand RestoreDefaultsCommand { get; }
public bool StartupTaskEnabled
{
get => _startupTaskEnabled;
set
{
if (_startupTaskEnabled != value)
{
_startupTaskEnabled = value;
RaisePropertyChanged(nameof(StartupTaskEnabled));
SetStartupTaskState(value);
}
}
}
public string StartupTaskErrorMessage
{
get => _startupTaskErrorMessage;
set => Set(ref _startupTaskErrorMessage, value);
}
public bool TabIsSelected
{
get => NewTerminalLocation == NewTerminalLocation.Tab;
set => NewTerminalLocation = NewTerminalLocation.Tab;
}
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 UnderlineSelectedTab
{
get => _applicationSettings.UnderlineSelectedTab;
set
{
if (_applicationSettings.UnderlineSelectedTab != value)
{
_applicationSettings.UnderlineSelectedTab = value;
_settingsService.SaveApplicationSettings(_applicationSettings);
RaisePropertyChanged();
}
}
}
public bool BackgroundIsSelected
{
get => InactiveTabColorMode == InactiveTabColorMode.Background;
set => InactiveTabColorMode = InactiveTabColorMode.Background;
}
public bool UnderlinedIsSelected
{
get => InactiveTabColorMode == InactiveTabColorMode.Underlined;
set => InactiveTabColorMode = InactiveTabColorMode.Underlined;
}
public InactiveTabColorMode InactiveTabColorMode
{
get => _applicationSettings.InactiveTabColorMode;
set
{
if (_applicationSettings.InactiveTabColorMode != value && !_editingInactiveTabColorMode)
{
_editingInactiveTabColorMode = true;
_applicationSettings.InactiveTabColorMode = value;
_settingsService.SaveApplicationSettings(_applicationSettings);
RaisePropertyChanged();
RaisePropertyChanged(nameof(BackgroundIsSelected));
RaisePropertyChanged(nameof(UnderlinedIsSelected));
_editingInactiveTabColorMode = false;
}
}
}
public bool WindowIsSelected
{
get => NewTerminalLocation == NewTerminalLocation.Window;
set => NewTerminalLocation = NewTerminalLocation.Window;
}
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(true);
if (result == DialogButton.OK)
{
var defaults = _defaultValueProvider.GetDefaultApplicationSettings();
ConfirmClosingWindows = defaults.ConfirmClosingWindows;
ConfirmClosingTabs = defaults.ConfirmClosingTabs;
UnderlineSelectedTab = defaults.UnderlineSelectedTab;
InactiveTabColorMode = defaults.InactiveTabColorMode;
NewTerminalLocation = defaults.NewTerminalLocation;
AlwaysShowTabs = defaults.AlwaysShowTabs;
AlwaysUseWinPty = defaults.AlwaysUseWinPty;
ShowNewOutputIndicator = defaults.ShowNewOutputIndicator;
EnableTrayIcon = defaults.EnableTrayIcon;
}
}
private void SetStartupTaskPropertiesForStatus(StartupTaskStatus startupTaskStatus)
{
switch (startupTaskStatus)
{
case StartupTaskStatus.Enabled:
StartupTaskEnabled = true;
StartupTaskErrorMessage = string.Empty;
CanEnableStartupTask = true;
break;
case StartupTaskStatus.Disabled:
StartupTaskEnabled = false;
StartupTaskErrorMessage = string.Empty;
CanEnableStartupTask = true;
break;
case StartupTaskStatus.DisabledByUser:
StartupTaskEnabled = false;
StartupTaskErrorMessage = "Disabled by user. Please reactivate it in the Startup tab of the Task Manager.";
CanEnableStartupTask = false;
break;
case StartupTaskStatus.DisabledByPolicy:
StartupTaskEnabled = false;
StartupTaskErrorMessage = "Disabled by policy.";
CanEnableStartupTask = false;
break;
}
}
private async Task SetStartupTaskState(bool enabled)
{
StartupTaskStatus status;
if (enabled)
{
status = await _startupTaskService.EnableStartupTask();
}
else
{
_startupTaskService.DisableStartupTask();
status = await _startupTaskService.GetStatus();
}
SetStartupTaskPropertiesForStatus(status);
}
}
}
@@ -1,11 +1,9 @@
using FluentTerminal.App.Services;
using FluentTerminal.App.Views;
using FluentTerminal.App.ViewModels.Infrastructure;
using FluentTerminal.Models;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using System;
using System.Threading.Tasks;
using Windows.UI.Xaml.Controls;
namespace FluentTerminal.App.ViewModels.Settings
{
@@ -13,18 +11,21 @@ namespace FluentTerminal.App.ViewModels.Settings
{
private readonly IDialogService _dialogService;
public KeyBindingViewModel(KeyBinding keyBinding, IDialogService dialogService)
public KeyBindingViewModel(KeyBinding keyBinding, IDialogService dialogService, KeyBindingsViewModel parent)
{
Model = keyBinding;
Parent = parent;
_dialogService = dialogService;
EditCommand = new RelayCommand(async () => await Edit().ConfigureAwait(false));
DeleteCommand = new RelayCommand(async () => await Delete().ConfigureAwait(false));
EditCommand = new AsyncCommand(Edit);
DeleteCommand = new AsyncCommand(Delete);
}
public event EventHandler Deleted;
public event EventHandler Edited;
public KeyBindingsViewModel Parent { get; }
public bool Meta
{
get => Model.Meta;
@@ -64,8 +65,8 @@ namespace FluentTerminal.App.ViewModels.Settings
}
}
public RelayCommand DeleteCommand { get; }
public RelayCommand EditCommand { get; }
public IAsyncCommand DeleteCommand { get; }
public IAsyncCommand EditCommand { get; }
public int Key
{
@@ -97,16 +98,15 @@ namespace FluentTerminal.App.ViewModels.Settings
public async Task<bool> Edit()
{
var dialog = new CreateKeyBindingDialog(_dialogService);
var result = await dialog.ShowAsync();
var keyBinding = await _dialogService.ShowCreateKeyBindingDialog().ConfigureAwait(true);
if (result == ContentDialogResult.Primary)
if (keyBinding != null)
{
Alt = dialog.KeyBinding.Alt;
Ctrl = dialog.KeyBinding.Ctrl;
Shift = dialog.KeyBinding.Shift;
Meta = dialog.KeyBinding.Meta;
Key = dialog.KeyBinding.Key;
Alt = keyBinding.Alt;
Ctrl = keyBinding.Ctrl;
Shift = keyBinding.Shift;
Meta = keyBinding.Meta;
Key = keyBinding.Key;
Edited?.Invoke(this, EventArgs.Empty);
@@ -0,0 +1,91 @@
using FluentTerminal.App.Services;
using FluentTerminal.App.Services.Utilities;
using FluentTerminal.Models;
using FluentTerminal.Models.Enums;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
namespace FluentTerminal.App.ViewModels.Settings
{
public class KeyBindingsPageViewModel : ViewModelBase
{
private readonly IDefaultValueProvider _defaultValueProvider;
private readonly IDialogService _dialogService;
private readonly ISettingsService _settingsService;
private readonly ITrayProcessCommunicationService _trayProcessCommunicationService;
private IDictionary<string, ICollection<KeyBinding>> _keyBindings;
public KeyBindingsPageViewModel(ISettingsService settingsService, IDialogService dialogService, IDefaultValueProvider defaultValueProvider, ITrayProcessCommunicationService trayProcessCommunicationService)
{
_settingsService = settingsService;
_dialogService = dialogService;
_defaultValueProvider = defaultValueProvider;
_trayProcessCommunicationService = trayProcessCommunicationService;
RestoreDefaultsCommand = new RelayCommand(async () => await RestoreDefaults().ConfigureAwait(false));
AddCommand = new RelayCommand<string>(async command => await Add(command).ConfigureAwait(false));
Initialize(_settingsService.GetCommandKeyBindings());
}
public RelayCommand<string> AddCommand { get; }
public ObservableCollection<KeyBindingsViewModel> KeyBindings { get; } = new ObservableCollection<KeyBindingsViewModel>();
public RelayCommand RestoreDefaultsCommand { get; }
private async Task Add(string command)
{
var keyBinding = KeyBindings.FirstOrDefault(k => k.Command == command);
await keyBinding?.ShowAddKeyBindingDialog();
}
private void ClearKeyBindings()
{
foreach (var keyBinding in KeyBindings)
{
keyBinding.Edited -= OnEdited;
}
KeyBindings.Clear();
}
private void Initialize(IDictionary<string, ICollection<KeyBinding>> keyBindings)
{
_keyBindings = keyBindings;
ClearKeyBindings();
foreach (var value in Enum.GetValues(typeof(Command)))
{
var command = (Command)value;
var viewModel = new KeyBindingsViewModel(command.ToString(), _dialogService, EnumHelper.GetEnumDescription(command), true);
foreach (var keyBinding in _keyBindings[command.ToString()])
{
viewModel.Add(keyBinding);
}
viewModel.Edited += OnEdited;
KeyBindings.Add(viewModel);
}
}
private void OnEdited(string command, ICollection<KeyBinding> keyBindings)
{
_settingsService.SaveKeyBindings(command, keyBindings);
_trayProcessCommunicationService.UpdateToggleWindowKeyBindings();
}
private async Task RestoreDefaults()
{
var result = await _dialogService.ShowMessageDialogAsnyc("Please confirm", "Are you sure you want to restore the default keybindings?", DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
if (result == DialogButton.OK)
{
_settingsService.ResetKeyBindings();
Initialize(_settingsService.GetCommandKeyBindings());
}
}
}
}
@@ -0,0 +1,84 @@
using FluentTerminal.App.Services;
using FluentTerminal.Models;
using GalaSoft.MvvmLight;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
namespace FluentTerminal.App.ViewModels.Settings
{
public class KeyBindingsViewModel : ViewModelBase
{
private readonly IDialogService _dialogService;
private bool _editable;
public KeyBindingsViewModel(string command, IDialogService dialogService, string commandName, bool editable)
{
_dialogService = dialogService;
Command = command;
CommandName = commandName;
Editable = editable;
}
public delegate void EditedEvent(string command, ICollection<KeyBinding> keyBindings);
public event EditedEvent Edited;
public string Command { get; }
public string CommandName { get; }
/// <summary>
/// Whether or not the "Edit" and "Delete" links appear next to the key binding list.
/// </summary>
public bool Editable
{
get => _editable;
set => Set(ref _editable, value);
}
public ObservableCollection<KeyBindingViewModel> KeyBindings { get; } = new ObservableCollection<KeyBindingViewModel>();
public async Task ShowAddKeyBindingDialog()
{
var newKeyBinding = new KeyBindingViewModel(new KeyBinding { Command = Command }, _dialogService, this);
if (await newKeyBinding.Edit().ConfigureAwait(true))
{
Add(newKeyBinding.Model);
}
}
public void Clear()
{
KeyBindings.Clear();
}
public void Add(KeyBinding keyBinding)
{
var viewModel = new KeyBindingViewModel(keyBinding, _dialogService, this);
viewModel.Deleted += ViewModel_Deleted;
viewModel.Edited += ViewModel_Edited;
KeyBindings.Add(viewModel);
Edited?.Invoke(Command, KeyBindings.Select(x => x.Model).ToList());
}
private void ViewModel_Deleted(object sender, EventArgs e)
{
if (sender is KeyBindingViewModel keyBinding)
{
KeyBindings.Remove(keyBinding);
Edited?.Invoke(Command, KeyBindings.Select(x => x.Model).ToList());
}
}
private void ViewModel_Edited(object sender, EventArgs e)
{
Edited?.Invoke(Command, KeyBindings.Select(x => x.Model).ToList());
}
}
}
@@ -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;
}
}
}
}
@@ -4,6 +4,7 @@ using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using System;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Linq;
namespace FluentTerminal.App.ViewModels.Settings
@@ -13,20 +14,25 @@ namespace FluentTerminal.App.ViewModels.Settings
private readonly IDefaultValueProvider _defaultValueProvider;
private readonly IDialogService _dialogService;
private readonly ISettingsService _settingsService;
private readonly IFileSystemService _fileSystemService;
private ShellProfileViewModel _selectedShellProfile;
private readonly IApplicationView _applicationView;
public ProfilesPageViewModel(ISettingsService settingsService, IDialogService dialogService, IDefaultValueProvider defaultValueProvider)
public ProfilesPageViewModel(ISettingsService settingsService, IDialogService dialogService, IDefaultValueProvider defaultValueProvider, IFileSystemService fileSystemService, IApplicationView applicationView)
{
_settingsService = settingsService;
_dialogService = dialogService;
_defaultValueProvider = defaultValueProvider;
_fileSystemService = fileSystemService;
_applicationView = applicationView;
CreateShellProfileCommand = new RelayCommand(CreateShellProfile);
CloneCommand = new RelayCommand<ShellProfileViewModel>(Clone);
var defaultShellProfileId = _settingsService.GetDefaultShellProfileId();
foreach (var shellProfile in _settingsService.GetShellProfiles())
{
var viewModel = new ShellProfileViewModel(shellProfile, settingsService, dialogService);
var viewModel = new ShellProfileViewModel(shellProfile, settingsService, dialogService, fileSystemService, applicationView, defaultValueProvider, false);
viewModel.Deleted += OnShellProfileDeleted;
viewModel.SetAsDefault += OnShellProfileSetAsDefault;
@@ -38,6 +44,7 @@ namespace FluentTerminal.App.ViewModels.Settings
}
SelectedShellProfile = ShellProfiles.First(p => p.IsDefault);
}
private void OnShellProfileSetAsDefault(object sender, EventArgs e)
@@ -74,18 +81,38 @@ namespace FluentTerminal.App.ViewModels.Settings
public RelayCommand CreateShellProfileCommand { get; }
public RelayCommand<ShellProfileViewModel> CloneCommand { get; }
private void CreateShellProfile()
{
var shellProfile = new ShellProfile
{
Id = Guid.NewGuid(),
PreInstalled = false,
Name = "New profile"
Name = "New profile",
KeyBindings = new List<KeyBinding>()
};
_settingsService.SaveShellProfile(shellProfile);
AddShellProfile(shellProfile);
}
var viewModel = new ShellProfileViewModel(shellProfile, _settingsService, _dialogService);
private void Clone(ShellProfileViewModel shellProfile)
{
var cloned = new ShellProfile(shellProfile.Model)
{
Id = Guid.NewGuid(),
PreInstalled = false,
Name = $"Copy of {shellProfile.Name}"
};
AddShellProfile(cloned);
}
private void AddShellProfile(ShellProfile shellProfile)
{
_settingsService.SaveShellProfile(shellProfile, true);
var viewModel = new ShellProfileViewModel(shellProfile, _settingsService, _dialogService, _fileSystemService, _applicationView, _defaultValueProvider, true);
viewModel.EditCommand.Execute(null);
viewModel.SetAsDefault += OnShellProfileSetAsDefault;
viewModel.Deleted += OnShellProfileDeleted;
@@ -1,16 +1,17 @@
using FluentTerminal.App.Services;
using FluentTerminal.Models;
using FluentTerminal.Models.Enums;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using Microsoft.Graphics.Canvas.Text;
using ReactiveUI;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
namespace FluentTerminal.App.ViewModels.Settings
{
public class TerminalPageViewModel : ViewModelBase
public class TerminalPageViewModel : ReactiveObject
{
private readonly TerminalOptions _terminalOptions;
private readonly ISettingsService _settingsService;
@@ -64,7 +65,7 @@ namespace FluentTerminal.App.ViewModels.Settings
{
_terminalOptions.CursorBlink = value;
_settingsService.SaveTerminalOptions(_terminalOptions);
RaisePropertyChanged();
this.RaisePropertyChanged();
}
}
}
@@ -78,7 +79,7 @@ namespace FluentTerminal.App.ViewModels.Settings
{
_terminalOptions.FontFamily = value;
_settingsService.SaveTerminalOptions(_terminalOptions);
RaisePropertyChanged();
this.RaisePropertyChanged();
}
}
}
@@ -91,8 +92,60 @@ namespace FluentTerminal.App.ViewModels.Settings
if (_terminalOptions.BackgroundOpacity != value)
{
_terminalOptions.BackgroundOpacity = value;
this.RaisePropertyChanged();
}
}
}
public int Padding
{
get => _terminalOptions.Padding;
set
{
if (_terminalOptions.Padding != value)
{
_terminalOptions.Padding = value;
this.RaisePropertyChanged();
}
}
}
public string ScrollBackLimit
{
get => _terminalOptions.ScrollBackLimit.ToString();
set
{
var oldValue = _terminalOptions.ScrollBackLimit;
if (uint.TryParse(value, out uint intValue))
{
if (intValue == uint.MinValue)
{
intValue = uint.MaxValue;
}
if (_terminalOptions.ScrollBackLimit != intValue)
{
_terminalOptions.ScrollBackLimit = intValue;
_settingsService.SaveTerminalOptions(_terminalOptions);
this.RaisePropertyChanged();
}
}
else
{
this.RaisePropertyChanged();
}
}
}
public bool BoldText
{
get => _terminalOptions.BoldText;
set
{
if (_terminalOptions.BoldText != value)
{
_terminalOptions.BoldText = value;
_settingsService.SaveTerminalOptions(_terminalOptions);
RaisePropertyChanged();
this.RaisePropertyChanged();
}
}
}
@@ -108,7 +161,7 @@ namespace FluentTerminal.App.ViewModels.Settings
{
_terminalOptions.FontSize = value;
_settingsService.SaveTerminalOptions(_terminalOptions);
RaisePropertyChanged();
this.RaisePropertyChanged();
}
}
}
@@ -127,10 +180,10 @@ namespace FluentTerminal.App.ViewModels.Settings
_isEditingCursorStyle = true;
_terminalOptions.CursorStyle = value;
_settingsService.SaveTerminalOptions(_terminalOptions);
RaisePropertyChanged();
RaisePropertyChanged(nameof(BlockIsSelected));
RaisePropertyChanged(nameof(BarIsSelected));
RaisePropertyChanged(nameof(UnderlineIsSelected));
this.RaisePropertyChanged();
this.RaisePropertyChanged(nameof(BlockIsSelected));
this.RaisePropertyChanged(nameof(BarIsSelected));
this.RaisePropertyChanged(nameof(UnderlineIsSelected));
_isEditingCursorStyle = false;
}
}
@@ -146,10 +199,10 @@ namespace FluentTerminal.App.ViewModels.Settings
_isEditingScrollBarStyle = true;
_terminalOptions.ScrollBarStyle = value;
_settingsService.SaveTerminalOptions(_terminalOptions);
RaisePropertyChanged();
RaisePropertyChanged(nameof(HiddenIsSelected));
RaisePropertyChanged(nameof(AutoHidingIsSelected));
RaisePropertyChanged(nameof(VisibleIsSelected));
this.RaisePropertyChanged();
this.RaisePropertyChanged(nameof(HiddenIsSelected));
this.RaisePropertyChanged(nameof(AutoHidingIsSelected));
this.RaisePropertyChanged(nameof(VisibleIsSelected));
_isEditingScrollBarStyle = false;
}
}
@@ -167,11 +220,13 @@ namespace FluentTerminal.App.ViewModels.Settings
ScrollBarStyle = defaults.ScrollBarStyle;
FontFamily = defaults.FontFamily;
FontSize = defaults.FontSize;
BoldText = defaults.BoldText;
BackgroundOpacity = defaults.BackgroundOpacity;
ScrollBackLimit = defaults.ScrollBackLimit.ToString();
}
}
public TerminalPageViewModel(ISettingsService settingsService, IDialogService dialogService, IDefaultValueProvider defaultValueProvider)
public TerminalPageViewModel(ISettingsService settingsService, IDialogService dialogService, IDefaultValueProvider defaultValueProvider, ISystemFontService systemFontService)
{
_settingsService = settingsService;
_dialogService = dialogService;
@@ -179,10 +234,19 @@ namespace FluentTerminal.App.ViewModels.Settings
RestoreDefaultsCommand = new RelayCommand(async () => await RestoreDefaults().ConfigureAwait(false));
Fonts = CanvasTextFormat.GetSystemFontFamilies().OrderBy(s => s);
Fonts = systemFontService.GetSystemFontFamilies().OrderBy(s => s);
Sizes = Enumerable.Range(1, 72);
_terminalOptions = _settingsService.GetTerminalOptions();
this.ObservableForProperty(x => x.BackgroundOpacity).Throttle(TimeSpan.FromMilliseconds(800)).ObserveOn(RxApp.MainThreadScheduler).Subscribe(x =>
{
_settingsService.SaveTerminalOptions(_terminalOptions);
});
this.ObservableForProperty(x => x.Padding).Throttle(TimeSpan.FromMilliseconds(800)).ObserveOn(RxApp.MainThreadScheduler).Subscribe(x =>
{
_settingsService.SaveTerminalOptions(_terminalOptions);
});
}
}
}
}
@@ -15,14 +15,23 @@ namespace FluentTerminal.App.ViewModels.Settings
private readonly ISettingsService _settingsService;
private ThemeViewModel _selectedTheme;
private double _backgroundOpacity;
private readonly IThemeParserFactory _themeParserFactory;
private readonly IFileSystemService _fileSystemService;
public ThemesPageViewModel(ISettingsService settingsService, IDialogService dialogService, IDefaultValueProvider defaultValueProvider)
public event EventHandler<string> SelectedThemeBackgroundColorChanged;
public ThemesPageViewModel(ISettingsService settingsService, IDialogService dialogService, IDefaultValueProvider defaultValueProvider,
IThemeParserFactory themeParserFactory, IFileSystemService fileSystemService)
{
_settingsService = settingsService;
_dialogService = dialogService;
_defaultValueProvider = defaultValueProvider;
_themeParserFactory = themeParserFactory;
_fileSystemService = fileSystemService;
CreateThemeCommand = new RelayCommand(CreateTheme);
ImportThemeCommand = new RelayCommand(ImportTheme);
CloneCommand = new RelayCommand<ThemeViewModel>(CloneTheme);
_settingsService.TerminalOptionsChanged += OnTerminalOptionsChanged;
@@ -31,7 +40,7 @@ namespace FluentTerminal.App.ViewModels.Settings
var activeThemeId = _settingsService.GetCurrentThemeId();
foreach (var theme in _settingsService.GetThemes())
{
var viewModel = new ThemeViewModel(theme, _settingsService, _dialogService);
var viewModel = new ThemeViewModel(theme, _settingsService, _dialogService, fileSystemService, false);
viewModel.Activated += OnThemeActivated;
viewModel.Deleted += OnThemeDeleted;
@@ -45,12 +54,9 @@ namespace FluentTerminal.App.ViewModels.Settings
SelectedTheme = Themes.First(t => t.IsActive);
}
private void OnTerminalOptionsChanged(object sender, TerminalOptions e)
{
BackgroundOpacity = e.BackgroundOpacity;
}
public RelayCommand CreateThemeCommand { get; }
public RelayCommand ImportThemeCommand { get; }
public RelayCommand<ThemeViewModel> CloneCommand { get; set; }
public double BackgroundOpacity
{
@@ -61,11 +67,34 @@ namespace FluentTerminal.App.ViewModels.Settings
public ThemeViewModel SelectedTheme
{
get => _selectedTheme;
set => Set(ref _selectedTheme, value);
set
{
if (_selectedTheme != null)
{
_selectedTheme.BackgroundChanged -= OnSelectedThemeBackgroundChanged;
}
Set(ref _selectedTheme, value);
if (value != null)
{
value.BackgroundChanged += OnSelectedThemeBackgroundChanged;
}
}
}
public ObservableCollection<ThemeViewModel> Themes { get; } = new ObservableCollection<ThemeViewModel>();
private void CloneTheme(ThemeViewModel theme)
{
var cloned = new TerminalTheme(theme.Model)
{
Id = Guid.NewGuid(),
PreInstalled = false,
Name = $"Copy of {theme.Name}"
};
AddTheme(cloned);
}
private void CreateTheme()
{
var defaultTheme = _settingsService.GetTheme(_defaultValueProvider.GetDefaultThemeId());
@@ -77,9 +106,41 @@ namespace FluentTerminal.App.ViewModels.Settings
Colors = new TerminalColors(defaultTheme.Colors)
};
_settingsService.SaveTheme(theme);
AddTheme(theme);
}
var viewModel = new ThemeViewModel(theme, _settingsService, _dialogService);
private async void ImportTheme()
{
var file = await _fileSystemService.OpenFile(_themeParserFactory.SupportedFileTypes).ConfigureAwait(true);
if (file != null)
{
var parser = _themeParserFactory.GetParser(file.FileType);
if (parser == null)
{
await _dialogService.ShowMessageDialogAsnyc("Import theme failed", "No suitable parser found", DialogButton.OK).ConfigureAwait(false);
return;
}
try
{
var theme = await parser.Parse(file.Name, file.Content).ConfigureAwait(true);
AddTheme(theme);
}
catch (Exception exception)
{
await _dialogService.ShowMessageDialogAsnyc("Import theme failed", exception.Message, DialogButton.OK).ConfigureAwait(false);
}
}
}
private void AddTheme(TerminalTheme theme)
{
_settingsService.SaveTheme(theme, true);
var viewModel = new ThemeViewModel(theme, _settingsService, _dialogService, _fileSystemService, true);
viewModel.EditCommand.Execute(null);
viewModel.Activated += OnThemeActivated;
viewModel.Deleted += OnThemeDeleted;
Themes.Add(viewModel);
@@ -117,5 +178,15 @@ namespace FluentTerminal.App.ViewModels.Settings
_settingsService.DeleteTheme(theme.Id);
}
}
private void OnTerminalOptionsChanged(object sender, TerminalOptions e)
{
BackgroundOpacity = e.BackgroundOpacity;
}
private void OnSelectedThemeBackgroundChanged(object sender, string e)
{
SelectedThemeBackgroundColorChanged?.Invoke(this, e);
}
}
}
@@ -7,22 +7,34 @@ namespace FluentTerminal.App.ViewModels
{
public class SettingsViewModel : ViewModelBase
{
public SettingsViewModel(ISettingsService settingsService, IDefaultValueProvider defaultValueProvider, IDialogService dialogService, ITrayProcessCommunicationService trayProcessCommunicationService)
public SettingsViewModel(ISettingsService settingsService, IDefaultValueProvider defaultValueProvider, IDialogService dialogService,
ITrayProcessCommunicationService trayProcessCommunicationService, IThemeParserFactory themeParserFactory, ISystemFontService systemFontService,
IFileSystemService fileSystemService, IStartupTaskService startupTaskService, IUpdateService updateService, IApplicationView applicationView)
{
About = new AboutPageViewModel(settingsService, updateService, applicationView);
KeyBindings = new KeyBindingsPageViewModel(settingsService, dialogService, defaultValueProvider, trayProcessCommunicationService);
General = new GeneralPageViewModel(settingsService, dialogService, defaultValueProvider);
Shell = new ProfilesPageViewModel(settingsService, dialogService, defaultValueProvider);
Terminal = new TerminalPageViewModel(settingsService, dialogService, defaultValueProvider);
Themes = new ThemesPageViewModel(settingsService, dialogService, defaultValueProvider);
General = new GeneralPageViewModel(settingsService, dialogService, defaultValueProvider, startupTaskService);
Shell = new ProfilesPageViewModel(settingsService, dialogService, defaultValueProvider, fileSystemService, applicationView);
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;
public event EventHandler AboutPageRequested;
public GeneralPageViewModel General { get; }
public KeyBindingsPageViewModel KeyBindings { get; }
public ProfilesPageViewModel Shell { get; }
public TerminalPageViewModel Terminal { get; }
public ThemesPageViewModel Themes { get; }
public MousePageViewModel Mouse { get; }
public AboutPageViewModel About { get; }
public void NavigateToAboutPage()
{
AboutPageRequested?.Invoke(this, EventArgs.Empty);
}
public void Close()
{
@@ -0,0 +1,382 @@
using FluentTerminal.App.Services;
using FluentTerminal.App.ViewModels.Infrastructure;
using FluentTerminal.App.ViewModels.Settings;
using FluentTerminal.Models;
using FluentTerminal.Models.Enums;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
namespace FluentTerminal.App.ViewModels
{
public class ShellProfileViewModel : ViewModelBase
{
private readonly IDialogService _dialogService;
private readonly IFileSystemService _fileSystemService;
private readonly ISettingsService _settingsService;
private string _arguments;
private ShellProfile _fallbackProfile;
private bool _inEditMode;
private bool _isDefault;
private string _location;
private LineEndingStyle _lineEndingStyle;
private string _name;
private bool _isNew;
private TabTheme _selectedTabTheme;
private TerminalTheme _selectedTerminalTheme;
private string _workingDirectory;
private readonly IApplicationView _applicationView;
private readonly IDefaultValueProvider _defaultValueProvider;
private bool _editingLineEndingStyle;
public ShellProfileViewModel(ShellProfile shellProfile, ISettingsService settingsService, IDialogService dialogService, IFileSystemService fileSystemService, IApplicationView applicationView, IDefaultValueProvider defaultValueProvider, Boolean isNew)
{
Model = shellProfile;
_settingsService = settingsService;
_dialogService = dialogService;
_fileSystemService = fileSystemService;
_applicationView = applicationView;
_defaultValueProvider = defaultValueProvider;
_isNew = isNew;
_settingsService.ThemeAdded += OnThemeAdded;
_settingsService.ThemeDeleted += OnThemeDeleted;
TabThemes = new ObservableCollection<TabTheme>(settingsService.GetTabThemes());
TerminalThemes = new ObservableCollection<TerminalTheme>();
TerminalThemes.Add(new TerminalTheme
{
Id = Guid.Empty,
Name = "Default"
});
foreach (var theme in _settingsService.GetThemes())
{
TerminalThemes.Add(theme);
}
KeyBindings = new KeyBindingsViewModel(shellProfile.Id.ToString(), _dialogService, string.Empty, false);
InitializeViewModelProperties(shellProfile);
SetDefaultCommand = new RelayCommand(SetDefault);
DeleteCommand = new AsyncCommand(Delete, CanDelete);
EditCommand = new RelayCommand(Edit);
CancelEditCommand = new AsyncCommand(CancelEdit);
SaveChangesCommand = new RelayCommand(SaveChanges);
AddKeyboardShortcutCommand = new AsyncCommand(AddKeyboardShortcut);
BrowseForCustomShellCommand = new AsyncCommand(BrowseForCustomShell);
BrowseForWorkingDirectoryCommand = new AsyncCommand(BrowseForWorkingDirectory);
RestoreDefaultsCommand = new AsyncCommand(RestoreDefaults);
}
private void InitializeViewModelProperties(ShellProfile shellProfile)
{
SelectedTerminalTheme = TerminalThemes.FirstOrDefault(t => t.Id == shellProfile.TerminalThemeId);
Id = shellProfile.Id;
Name = shellProfile.Name;
Arguments = shellProfile.Arguments;
Location = shellProfile.Location;
WorkingDirectory = shellProfile.WorkingDirectory;
SelectedTabTheme = TabThemes.FirstOrDefault(t => t.Id == shellProfile.TabThemeId);
PreInstalled = shellProfile.PreInstalled;
LineEndingStyle = shellProfile.LineEndingTranslation;
KeyBindings.Clear();
foreach (var keyBinding in shellProfile.KeyBindings.Select(x => new KeyBinding(x)).ToList())
{
KeyBindings.Add(keyBinding);
}
}
private void OnThemeAdded(object sender, TerminalTheme e)
{
_applicationView.RunOnDispatcherThread(() =>
{
TerminalThemes.Add(e);
});
}
private void OnThemeDeleted(object sender, Guid e)
{
if (SelectedTerminalTheme.Id == e)
{
_applicationView.RunOnDispatcherThread(() =>
{
SelectedTerminalTheme = TerminalThemes.FirstOrDefault(x => x.Id == Guid.Empty);
Model.TerminalThemeId = Guid.Empty;
if (_fallbackProfile != null)
{
_fallbackProfile.TerminalThemeId = Guid.Empty;
}
});
}
}
public event EventHandler Deleted;
public event EventHandler SetAsDefault;
public IAsyncCommand BrowseForCustomShellCommand { get; }
public IAsyncCommand BrowseForWorkingDirectoryCommand { get; }
public IAsyncCommand CancelEditCommand { get; }
public IAsyncCommand DeleteCommand { get; }
public RelayCommand EditCommand { get; }
public RelayCommand SaveChangesCommand { get; }
public RelayCommand SetDefaultCommand { get; }
public IAsyncCommand RestoreDefaultsCommand { get; }
public IAsyncCommand AddKeyboardShortcutCommand { get; }
public ObservableCollection<TabTheme> TabThemes { get; }
public KeyBindingsViewModel KeyBindings { get; }
public ObservableCollection<TerminalTheme> TerminalThemes { get; }
public ShellProfile Model { get; private set; }
public bool PreInstalled { get; private set; }
public string Arguments
{
get => _arguments;
set => Set(ref _arguments, value);
}
public Guid Id { get; private set; }
public bool InEditMode
{
get => _inEditMode;
set => Set(ref _inEditMode, value);
}
public bool IsDefault
{
get => _isDefault;
set => Set(ref _isDefault, value);
}
public string Location
{
get => _location;
set => Set(ref _location, value);
}
public string Name
{
get => _name;
set => Set(ref _name, value);
}
public TabTheme SelectedTabTheme
{
get => _selectedTabTheme;
set
{
if (value != null)
{
Set(ref _selectedTabTheme, value);
}
}
}
public TerminalTheme SelectedTerminalTheme
{
get => _selectedTerminalTheme;
set
{
if (value != null)
{
Set(ref _selectedTerminalTheme, value);
}
}
}
public string WorkingDirectory
{
get => _workingDirectory;
set => Set(ref _workingDirectory, value);
}
public LineEndingStyle LineEndingStyle
{
get => _lineEndingStyle;
set
{
if (_lineEndingStyle != value && !_editingLineEndingStyle)
{
_editingLineEndingStyle = true;
_lineEndingStyle = value;
RaisePropertyChanged();
RaisePropertyChanged(nameof(DoNotModifyIsSelected));
RaisePropertyChanged(nameof(ToCRLFIsSelected));
RaisePropertyChanged(nameof(ToLFIsSelected));
_editingLineEndingStyle = false;
}
}
}
public bool DoNotModifyIsSelected
{
get => LineEndingStyle == LineEndingStyle.DoNotModify;
set => LineEndingStyle = LineEndingStyle.DoNotModify;
}
public bool ToCRLFIsSelected
{
get => LineEndingStyle == LineEndingStyle.ToCRLF;
set => LineEndingStyle = LineEndingStyle.ToCRLF;
}
public bool ToLFIsSelected
{
get => LineEndingStyle == LineEndingStyle.ToLF;
set => LineEndingStyle = LineEndingStyle.ToLF;
}
public void SaveChanges()
{
Model.Arguments = Arguments;
Model.Location = Location;
Model.Name = Name;
Model.WorkingDirectory = WorkingDirectory;
Model.TabThemeId = SelectedTabTheme.Id;
Model.TerminalThemeId = SelectedTerminalTheme.Id;
Model.LineEndingTranslation = _lineEndingStyle;
Model.KeyBindings = KeyBindings.KeyBindings.Select(x => x.Model).ToList();
_settingsService.SaveShellProfile(Model);
KeyBindings.Editable = false;
InEditMode = false;
_isNew = false;
}
private async Task RestoreDefaults()
{
if (InEditMode || !PreInstalled)
{
throw new InvalidOperationException();
}
var result = await _dialogService.ShowMessageDialogAsnyc("Please confirm", "Are you sure you want to restore this profile?", DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
if (result == DialogButton.OK)
{
Model = _defaultValueProvider.GetPreinstalledShellProfiles().FirstOrDefault(x => x.Id == Model.Id);
InitializeViewModelProperties(Model);
_settingsService.SaveShellProfile(Model);
}
}
public Task AddKeyboardShortcut()
{
return KeyBindings.ShowAddKeyBindingDialog();
}
private async Task BrowseForCustomShell()
{
var file = await _fileSystemService.OpenFile(new[] { ".exe" }).ConfigureAwait(true);
if (file != null)
{
Location = file.Path;
}
}
private async Task BrowseForWorkingDirectory()
{
var directory = await _fileSystemService.BrowseForDirectory().ConfigureAwait(true);
if (directory != null)
{
WorkingDirectory = directory;
}
}
private async Task CancelEdit()
{
if (_isNew)
{
await Delete();
}
else
{
ShellProfile changedProfile = new ShellProfile
{
Arguments = Arguments,
Location = Location,
Name = Name,
WorkingDirectory = WorkingDirectory,
TabThemeId = SelectedTabTheme.Id,
TerminalThemeId = SelectedTerminalTheme.Id,
LineEndingTranslation = _lineEndingStyle,
KeyBindings = KeyBindings.KeyBindings.Select(x => x.Model).ToList()
};
if (!_fallbackProfile.Equals(changedProfile))
{
var result = await _dialogService.ShowMessageDialogAsnyc("Please confirm", "Are you sure you want to discard all changes?", DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
if (result == DialogButton.OK)
{
Arguments = _fallbackProfile.Arguments;
Location = _fallbackProfile.Location;
Name = _fallbackProfile.Name;
WorkingDirectory = _fallbackProfile.WorkingDirectory;
LineEndingStyle = _fallbackProfile.LineEndingTranslation;
SelectedTerminalTheme = TerminalThemes.FirstOrDefault(t => t.Id == _fallbackProfile.TerminalThemeId);
SelectedTabTheme = TabThemes.FirstOrDefault(t => t.Id == _fallbackProfile.TabThemeId);
KeyBindings.KeyBindings.Clear();
foreach (var keyBinding in Model.KeyBindings.Select(x => new KeyBinding(x)).ToList())
{
KeyBindings.Add(keyBinding);
}
KeyBindings.Editable = false;
InEditMode = false;
}
}
else
{
KeyBindings.Editable = false;
InEditMode = false;
}
}
}
private bool CanDelete()
{
return !Model.PreInstalled;
}
private async Task Delete()
{
var result = await _dialogService.ShowMessageDialogAsnyc("Please confirm", "Are you sure you want to delete this theme?", DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
if (result == DialogButton.OK)
{
Deleted?.Invoke(this, EventArgs.Empty);
}
}
private void Edit()
{
// todo write copy ctor
_fallbackProfile = new ShellProfile
{
Arguments = Model.Arguments,
Location = Model.Location,
Name = Model.Name,
WorkingDirectory = Model.WorkingDirectory,
TabThemeId = Model.TabThemeId,
TerminalThemeId = Model.TerminalThemeId
};
KeyBindings.Editable = true;
InEditMode = true;
}
private void SetDefault()
{
SetAsDefault?.Invoke(this, EventArgs.Empty);
}
}
}
@@ -0,0 +1,390 @@
using FluentTerminal.App.Services;
using FluentTerminal.App.ViewModels.Infrastructure;
using FluentTerminal.Models;
using FluentTerminal.Models.Enums;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
namespace FluentTerminal.App.ViewModels
{
public class TerminalViewModel : ViewModelBase
{
private readonly IKeyboardCommandService _keyboardCommandService;
private readonly IDispatcherTimer _resizeOverlayTimer;
private bool _isSelected;
private bool _newOutput;
private string _resizeOverlayContent;
private string _searchText;
private bool _showResizeOverlay;
private bool _showSearchPanel;
private TabTheme _tabTheme;
private TerminalTheme _terminalTheme;
private string _title;
public TerminalViewModel(ISettingsService settingsService, ITrayProcessCommunicationService trayProcessCommunicationService, IDialogService dialogService,
IKeyboardCommandService keyboardCommandService, ApplicationSettings applicationSettings, string startupDirectory, ShellProfile shellProfile,
IApplicationView applicationView, IDispatcherTimer dispatcherTimer, IClipboardService clipboardService)
{
SettingsService = settingsService;
SettingsService.CurrentThemeChanged += OnCurrentThemeChanged;
SettingsService.TerminalOptionsChanged += OnTerminalOptionsChanged;
SettingsService.ApplicationSettingsChanged += OnApplicationSettingsChanged;
SettingsService.KeyBindingsChanged += OnKeyBindingsChanged;
TrayProcessCommunicationService = trayProcessCommunicationService;
DialogService = dialogService;
_keyboardCommandService = keyboardCommandService;
ApplicationSettings = applicationSettings;
StartupDirectory = startupDirectory;
ApplicationView = applicationView;
ClipboardService = clipboardService;
ShellProfile = shellProfile;
TerminalTheme = shellProfile.TerminalThemeId == Guid.Empty ? SettingsService.GetCurrentTheme() : SettingsService.GetTheme(shellProfile.TerminalThemeId);
TabThemes = new ObservableCollection<TabTheme>(SettingsService.GetTabThemes());
TabTheme = TabThemes.FirstOrDefault(t => t.Id == ShellProfile.TabThemeId);
_resizeOverlayTimer = dispatcherTimer;
_resizeOverlayTimer.Interval = new TimeSpan(0, 0, 2);
_resizeOverlayTimer.Tick += OnResizeOverlayTimerFinished;
CloseCommand = new RelayCommand(async () => await TryClose().ConfigureAwait(false));
FindNextCommand = new RelayCommand(FindNext);
FindPreviousCommand = new RelayCommand(FindPrevious);
CloseSearchPanelCommand = new RelayCommand(CloseSearchPanel);
SelectTabThemeCommand = new RelayCommand<string>(SelectTabTheme);
EditTitleCommand = new AsyncCommand(EditTitle);
if (!string.IsNullOrWhiteSpace(StartupDirectory))
{
ShellProfile.WorkingDirectory = StartupDirectory;
}
Terminal = new Terminal(TrayProcessCommunicationService);
Terminal.KeyboardCommandReceived += Terminal_KeyboardCommandReceived;
Terminal.OutputReceived += Terminal_OutputReceived;
Terminal.SizeChanged += Terminal_SizeChanged;
Terminal.TitleChanged += Terminal_TitleChanged;
Terminal.Closed += Terminal_Closed;
}
public event EventHandler Activated;
public event EventHandler Closed;
public event EventHandler<string> FindNextRequested;
public event EventHandler<string> FindPreviousRequested;
public event EventHandler KeyBindingsChanged;
public event EventHandler<TerminalOptions> OptionsChanged;
public event EventHandler SearchStarted;
public event EventHandler<TerminalTheme> ThemeChanged;
public ApplicationSettings ApplicationSettings { get; private set; }
public IApplicationView ApplicationView { get; }
public TabTheme BackgroundTabTheme
{
// The effective background theme depends on whether it is selected (use the theme), or if it is inactive
// (if we're set to underline inactive tabs, use the null theme).
get => IsSelected || (!IsSelected && ApplicationSettings.InactiveTabColorMode == InactiveTabColorMode.Background) ?
_tabTheme :
SettingsService.GetTabThemes().FirstOrDefault(t => t.Color == null);
}
public IClipboardService ClipboardService { get; }
public RelayCommand CloseCommand { get; }
public RelayCommand CloseSearchPanelCommand { get; }
public IDialogService DialogService { get; }
public IAsyncCommand EditTitleCommand { get; }
public RelayCommand FindNextCommand { get; }
public RelayCommand FindPreviousCommand { get; }
public bool IsSelected
{
get => _isSelected;
set
{
if (Set(ref _isSelected, value))
{
if (IsSelected)
{
ApplicationView.Title = Title ?? string.Empty;
NewOutput = false;
}
RaisePropertyChanged(nameof(IsUnderlined));
RaisePropertyChanged(nameof(BackgroundTabTheme));
}
}
}
public bool IsUnderlined => (IsSelected && ApplicationSettings.UnderlineSelectedTab) ||
(!IsSelected && ApplicationSettings.InactiveTabColorMode == InactiveTabColorMode.Underlined && TabTheme.Color != null);
public bool NewOutput
{
get => _newOutput;
set => Set(ref _newOutput, value);
}
public string ResizeOverlayContent
{
get => _resizeOverlayContent;
set => Set(ref _resizeOverlayContent, value);
}
public string SearchText
{
get => _searchText;
set => Set(ref _searchText, value);
}
public RelayCommand<string> SelectTabThemeCommand { get; }
public ISettingsService SettingsService { get; }
public ShellProfile ShellProfile { get; }
public bool ShowResizeOverlay
{
get => _showResizeOverlay;
set
{
Set(ref _showResizeOverlay, value);
if (value)
{
if (_resizeOverlayTimer.IsEnabled)
{
_resizeOverlayTimer.Stop();
}
_resizeOverlayTimer.Start();
}
}
}
public bool ShowSearchPanel
{
get => _showSearchPanel;
set => Set(ref _showSearchPanel, value);
}
public string StartupDirectory { get; }
public TabTheme TabTheme
{
get => _tabTheme;
set
{
Set(ref _tabTheme, value);
RaisePropertyChanged(nameof(IsUnderlined));
RaisePropertyChanged(nameof(BackgroundTabTheme));
}
}
public ObservableCollection<TabTheme> TabThemes { get; }
public Terminal Terminal { get; private set; }
public TerminalTheme TerminalTheme
{
get => _terminalTheme;
set => Set(ref _terminalTheme, value);
}
public string Title
{
get => _title;
set
{
if (Set(ref _title, value) && IsSelected)
{
ApplicationView.Title = Title;
}
}
}
public ITrayProcessCommunicationService TrayProcessCommunicationService { get; }
public Task Close()
{
return Terminal.Close();
}
public void CopyText(string text)
{
ClipboardService.SetText(text);
}
public async Task EditTitle()
{
var result = await DialogService.ShowInputDialogAsync("Edit Title");
if (result != null)
{
Title = result;
}
}
public void FocusTerminal()
{
Activated?.Invoke(this, EventArgs.Empty);
}
private void CloseSearchPanel()
{
SearchText = string.Empty;
ShowSearchPanel = false;
FocusTerminal();
}
private void FindNext()
{
FindNextRequested?.Invoke(this, SearchText);
}
private void FindPrevious()
{
FindPreviousRequested?.Invoke(this, SearchText);
}
private async void OnApplicationSettingsChanged(object sender, ApplicationSettings e)
{
await ApplicationView.RunOnDispatcherThread(() =>
{
ApplicationSettings = e;
RaisePropertyChanged(nameof(IsUnderlined));
RaisePropertyChanged(nameof(BackgroundTabTheme));
});
}
private async void OnCurrentThemeChanged(object sender, Guid e)
{
await ApplicationView.RunOnDispatcherThread(() =>
{
// only change theme if not overwritten by profile
if (ShellProfile.TerminalThemeId == Guid.Empty)
{
var currentTheme = SettingsService.GetTheme(e);
TerminalTheme = currentTheme;
ThemeChanged?.Invoke(this, currentTheme);
}
});
}
private void OnKeyBindingsChanged(object sender, EventArgs e)
{
KeyBindingsChanged?.Invoke(this, EventArgs.Empty);
}
private void OnResizeOverlayTimerFinished(object sender, object e)
{
_resizeOverlayTimer.Stop();
ShowResizeOverlay = false;
}
private void OnTerminalOptionsChanged(object sender, TerminalOptions e)
{
OptionsChanged?.Invoke(this, e);
}
private void SelectTabTheme(string name)
{
TabTheme = TabThemes.FirstOrDefault(t => t.Name == name);
}
private void Terminal_Closed(object sender, EventArgs e)
{
Closed?.Invoke(this, EventArgs.Empty);
}
private async void Terminal_KeyboardCommandReceived(object sender, string e)
{
switch (e)
{
case nameof(Command.Copy):
{
var selection = await Terminal.GetSelectedText().ConfigureAwait(true);
ClipboardService.SetText(selection);
break;
}
case nameof(Command.Paste):
{
string content = await ClipboardService.GetText().ConfigureAwait(true);
if (content != null)
{
content = ShellProfile.TranslateLineEndings(content);
await Terminal.Write(content).ConfigureAwait(true);
}
break;
}
case nameof(Command.PasteWithoutNewlines):
{
string content = await ClipboardService.GetText().ConfigureAwait(true);
if (content != null)
{
content = ShellProfile.NewlinePattern.Replace(content, string.Empty);
await Terminal.Write(content).ConfigureAwait(true);
}
break;
}
case nameof(Command.Search):
{
ShowSearchPanel = !ShowSearchPanel;
if (ShowSearchPanel)
{
SearchStarted?.Invoke(this, EventArgs.Empty);
}
break;
}
default:
{
_keyboardCommandService.SendCommand(e);
break;
}
}
}
private void Terminal_OutputReceived(object sender, string e)
{
if (!IsSelected && ApplicationSettings.ShowNewOutputIndicator)
{
NewOutput = true;
}
}
private void Terminal_SizeChanged(object sender, TerminalSize e)
{
ResizeOverlayContent = $"{e.Columns} x {e.Rows}";
ShowResizeOverlay = true;
}
private void Terminal_TitleChanged(object sender, string e)
{
Title = e;
}
private async Task TryClose()
{
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);
if (result == DialogButton.Cancel)
{
return;
}
}
await Close().ConfigureAwait(true);
}
}
}
@@ -0,0 +1,420 @@
using FluentTerminal.App.Services;
using FluentTerminal.App.Services.Utilities;
using FluentTerminal.Models;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using Newtonsoft.Json;
using System;
using System.Threading.Tasks;
namespace FluentTerminal.App.ViewModels
{
public class ThemeViewModel : ViewModelBase
{
private readonly IDialogService _dialogService;
private readonly ISettingsService _settingsService;
private string _author;
private string _background;
private double _backgroundOpacity;
private string _black;
private string _blue;
private string _brightBlack;
private string _brightBlue;
private string _brightCyan;
private string _brightGreen;
private string _brightMagenta;
private string _brightRed;
private string _brightWhite;
private string _brightYellow;
private string _cursor;
private string _cursorAccent;
private string _cyan;
private TerminalTheme _fallbackTheme;
private string _foreground;
private string _green;
private bool _inEditMode;
private bool _isActive;
private string _magenta;
private string _name;
private bool _isNew;
private string _red;
private string _selection;
private string _white;
private string _yellow;
private readonly IFileSystemService _fileSystemService;
public event EventHandler<string> BackgroundChanged;
public ThemeViewModel(TerminalTheme theme, ISettingsService settingsService, IDialogService dialogService, IFileSystemService fileSystemService, bool isNew)
{
Model = theme;
_settingsService = settingsService;
_dialogService = dialogService;
_fileSystemService = fileSystemService;
_isNew = isNew;
Name = Model.Name;
Author = Model.Author;
Id = Model.Id;
Black = Model.Colors.Black;
Red = Model.Colors.Red;
Green = Model.Colors.Green;
Yellow = Model.Colors.Yellow;
Blue = Model.Colors.Blue;
Magenta = Model.Colors.Magenta;
Cyan = Model.Colors.Cyan;
White = Model.Colors.White;
BrightBlack = Model.Colors.BrightBlack;
BrightRed = Model.Colors.BrightRed;
BrightGreen = Model.Colors.BrightGreen;
BrightYellow = Model.Colors.BrightYellow;
BrightBlue = Model.Colors.BrightBlue;
BrightMagenta = Model.Colors.BrightMagenta;
BrightCyan = Model.Colors.BrightCyan;
BrightWhite = Model.Colors.BrightWhite;
Background = Model.Colors.Background;
Foreground = Model.Colors.Foreground;
Cursor = Model.Colors.Cursor;
CursorAccent = Model.Colors.CursorAccent;
Selection = Model.Colors.Selection;
SetActiveCommand = new RelayCommand(SetActive);
DeleteCommand = new RelayCommand(async () => await Delete().ConfigureAwait(false), NotPreInstalled);
EditCommand = new RelayCommand(Edit, NotPreInstalled);
CancelEditCommand = new RelayCommand(async () => await CancelEdit().ConfigureAwait(false));
SaveChangesCommand = new RelayCommand(SaveChanges);
ExportCommand = new RelayCommand(async () => await Export().ConfigureAwait(false), NotPreInstalled);
}
public event EventHandler Activated;
public event EventHandler Deleted;
public TerminalTheme Model { get; }
public string Author
{
get => _author;
set => Set(ref _author, value);
}
public string Background
{
get => _background;
set
{
Set(ref _background, value);
BackgroundChanged?.Invoke(this, value);
}
}
public double BackgroundOpacity
{
get => _backgroundOpacity;
set => Set(ref _backgroundOpacity, value);
}
public string Black
{
get => _black;
set => Set(ref _black, value);
}
public string Blue
{
get => _blue;
set => Set(ref _blue, value);
}
public string BrightBlack
{
get => _brightBlack;
set => Set(ref _brightBlack, value);
}
public string BrightBlue
{
get => _brightBlue;
set => Set(ref _brightBlue, value);
}
public string BrightCyan
{
get => _brightCyan;
set => Set(ref _brightCyan, value);
}
public string BrightGreen
{
get => _brightGreen;
set => Set(ref _brightGreen, value);
}
public string BrightMagenta
{
get => _brightMagenta;
set => Set(ref _brightMagenta, value);
}
public string BrightRed
{
get => _brightRed;
set => Set(ref _brightRed, value);
}
public string BrightWhite
{
get => _brightWhite;
set => Set(ref _brightWhite, value);
}
public string BrightYellow
{
get => _brightYellow;
set => Set(ref _brightYellow, value);
}
public RelayCommand CancelEditCommand { get; }
public string Cursor
{
get => _cursor;
set => Set(ref _cursor, value);
}
public string CursorAccent
{
get => _cursorAccent;
set => Set(ref _cursorAccent, value);
}
public string Cyan
{
get => _cyan;
set => Set(ref _cyan, value);
}
public RelayCommand DeleteCommand { get; }
public RelayCommand EditCommand { get; }
public RelayCommand ExportCommand { get; }
public string Foreground
{
get => _foreground;
set => Set(ref _foreground, value);
}
public string Green
{
get => _green;
set => Set(ref _green, value);
}
public Guid Id { get; }
public bool InEditMode
{
get => _inEditMode;
set => Set(ref _inEditMode, value);
}
public bool IsActive
{
get => _isActive;
set => Set(ref _isActive, value);
}
public string Magenta
{
get => _magenta;
set => Set(ref _magenta, value);
}
public string Name
{
get => _name;
set => Set(ref _name, value);
}
public string Red
{
get => _red;
set => Set(ref _red, value);
}
public RelayCommand SaveChangesCommand { get; }
public string Selection
{
get => _selection;
set => Set(ref _selection, value);
}
public RelayCommand SetActiveCommand { get; }
public string White
{
get => _white;
set => Set(ref _white, value);
}
public string Yellow
{
get => _yellow;
set => Set(ref _yellow, value);
}
public void SaveChanges()
{
Model.Name = Name;
Model.Author = Author;
Model.Colors.Black = Black;
Model.Colors.Red = Red;
Model.Colors.Green = Green;
Model.Colors.Yellow = Yellow;
Model.Colors.Blue = Blue;
Model.Colors.Magenta = Magenta;
Model.Colors.Cyan = Cyan;
Model.Colors.White = White;
Model.Colors.BrightBlack = BrightBlack;
Model.Colors.BrightRed = BrightRed;
Model.Colors.BrightGreen = BrightGreen;
Model.Colors.BrightYellow = BrightYellow;
Model.Colors.BrightBlue = BrightBlue;
Model.Colors.BrightMagenta = BrightMagenta;
Model.Colors.BrightCyan = BrightCyan;
Model.Colors.BrightWhite = BrightWhite;
Model.Colors.Background = Background;
Model.Colors.Foreground = Foreground;
Model.Colors.Cursor = Cursor;
Model.Colors.CursorAccent = CursorAccent;
Model.Colors.Selection = Selection;
_settingsService.SaveTheme(Model);
InEditMode = false;
_isNew = false;
}
private async Task CancelEdit()
{
if (_isNew)
{
await Delete();
}
else
{
TerminalTheme changedTheme = new TerminalTheme()
{
Name = Name,
Author = Author,
Colors = new TerminalColors()
{
Black = Black,
Red = Red,
Green = Green,
Yellow = Yellow,
Blue = Blue,
Magenta = Magenta,
Cyan = Cyan,
White = White,
BrightBlack = BrightBlack,
BrightRed = BrightRed,
BrightGreen = BrightGreen,
BrightYellow = BrightYellow,
BrightBlue = BrightBlue,
BrightMagenta = BrightMagenta,
BrightCyan = BrightCyan,
BrightWhite = BrightWhite,
Background = Background,
Foreground = Foreground,
Cursor = Cursor,
CursorAccent = CursorAccent,
Selection = Selection
}
};
if (!_fallbackTheme.Equals(changedTheme))
{
var result = await _dialogService.ShowMessageDialogAsnyc("Please confirm", "Are you sure you want to discard all changes?", DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
if (result == DialogButton.OK)
{
Black = _fallbackTheme.Colors.Black;
Red = _fallbackTheme.Colors.Red;
Green = _fallbackTheme.Colors.Green;
Yellow = _fallbackTheme.Colors.Yellow;
Blue = _fallbackTheme.Colors.Blue;
Magenta = _fallbackTheme.Colors.Magenta;
Cyan = _fallbackTheme.Colors.Cyan;
White = _fallbackTheme.Colors.White;
BrightBlack = _fallbackTheme.Colors.BrightBlack;
BrightRed = _fallbackTheme.Colors.BrightRed;
BrightGreen = _fallbackTheme.Colors.BrightGreen;
BrightYellow = _fallbackTheme.Colors.BrightYellow;
BrightBlue = _fallbackTheme.Colors.BrightBlue;
BrightMagenta = _fallbackTheme.Colors.BrightMagenta;
BrightCyan = _fallbackTheme.Colors.BrightCyan;
BrightWhite = _fallbackTheme.Colors.BrightWhite;
Background = _fallbackTheme.Colors.Background;
Foreground = _fallbackTheme.Colors.Foreground;
Cursor = _fallbackTheme.Colors.Cursor;
CursorAccent = _fallbackTheme.Colors.CursorAccent;
Selection = _fallbackTheme.Colors.Selection;
Name = _fallbackTheme.Name;
Author = _fallbackTheme.Author;
InEditMode = false;
}
}
else
{
InEditMode = false;
}
}
}
private bool NotPreInstalled()
{
return !Model.PreInstalled;
}
private async Task Delete()
{
var result = await _dialogService.ShowMessageDialogAsnyc("Please confirm", "Are you sure you want to delete this theme?", DialogButton.OK, DialogButton.Cancel).ConfigureAwait(true);
if (result == DialogButton.OK)
{
Deleted?.Invoke(this, EventArgs.Empty);
}
}
private void Edit()
{
_fallbackTheme = new TerminalTheme(Model);
InEditMode = true;
}
private void SetActive()
{
Activated?.Invoke(this, EventArgs.Empty);
}
private Task Export()
{
var content = JsonConvert.SerializeObject(Model, Formatting.Indented, new JsonSerializerSettings { ContractResolver = new TerminalThemeContractResolver() });
return _fileSystemService.SaveTextFile(Name, "Fluent Terminal Theme", ".flutecolors", content);
}
}
}
@@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Timer = System.Timers.Timer;
namespace FluentTerminal.App.ViewModels.Utilities
{
public class InputBuffer
{
private readonly Timer _timer = new Timer();
private readonly Func<string, Task> _writeCallback;
private readonly List<string> _buffer = new List<string>();
private readonly object _lock = new object();
public InputBuffer(Func<string, Task> writeCallback)
{
_timer.AutoReset = true;
_timer.Elapsed += _timer_Elapsed;
_writeCallback = writeCallback;
_timer.Interval = 5;
_timer.Start();
}
private void _timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
lock (_lock)
{
var data = string.Concat(_buffer);
_buffer.Clear();
_writeCallback.Invoke(data);
}
}
public void Write(string data)
{
lock (_lock)
{
_buffer.Add(data);
}
}
}
}
@@ -0,0 +1,23 @@
using FluentTerminal.App.ViewModels.Infrastructure;
using System;
using System.Threading.Tasks;
namespace FluentTerminal.App.ViewModels.Utilities
{
public static class TaskUtilities
{
#pragma warning disable RECS0165 // Asynchronous methods should return a Task instead of void
public static async void FireAndForgetSafeAsync(this Task task, IErrorHandler handler = null)
#pragma warning restore RECS0165 // Asynchronous methods should return a Task instead of void
{
try
{
await task;
}
catch (Exception ex)
{
handler?.HandleError(ex);
}
}
}
}
@@ -0,0 +1,58 @@
using FluentTerminal.App.Services;
using FluentTerminal.Models;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Windows.ApplicationModel.AppService;
using Windows.Foundation.Collections;
namespace FluentTerminal.App.Adapters
{
public class AppServiceConnectionAdapter : IAppServiceConnection
{
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)
{
var valueSet = new ValueSet();
foreach (var pair in message)
{
valueSet.Add(pair.Key, pair.Value);
}
var response = await _appServiceConnection.SendMessageAsync(valueSet).AsTask();
if (response.Status == AppServiceResponseStatus.Success)
{
var responseDict = new Dictionary<string, string>();
foreach (var pair in response.Message)
{
responseDict.Add(pair.Key, (string)pair.Value);
}
return responseDict;
}
return null;
}
}
}
@@ -0,0 +1,76 @@
using FluentTerminal.App.Services;
using FluentTerminal.App.Services.EventArgs;
using System;
using System.Threading.Tasks;
using Windows.ApplicationModel.Core;
using Windows.Foundation.Metadata;
using Windows.UI.Core;
using Windows.UI.Core.Preview;
using Windows.UI.ViewManagement;
namespace FluentTerminal.App.Adapters
{
public class ApplicationViewAdapter : IApplicationView
{
private readonly ApplicationView _applicationView;
private readonly CoreDispatcher _dispatcher;
public event CloseRequestedHandler CloseRequested;
public ApplicationViewAdapter()
{
_applicationView = ApplicationView.GetForCurrentView();
_dispatcher = CoreApplication.GetCurrentView().Dispatcher;
SystemNavigationManagerPreview.GetForCurrentView().CloseRequested += OnCloseRequest;
}
public string Title
{
get => _applicationView.Title;
set => _applicationView.Title = value;
}
public bool IsApiContractPresent(string api, ushort version)
{
return ApiInformation.IsApiContractPresent(api, version);
}
public Task RunOnDispatcherThread(Action action)
{
return _dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => action()).AsTask();
}
public Task<bool> TryClose()
{
return _applicationView.TryConsolidateAsync().AsTask();
}
public bool ToggleFullScreen()
{
if (_applicationView.IsFullScreenMode)
{
_applicationView.ExitFullScreenMode();
return true;
}
else
{
return _applicationView.TryEnterFullScreenMode();
}
}
private async void OnCloseRequest(object sender, SystemNavigationCloseRequestedPreviewEventArgs e)
{
var deferral = e.GetDeferral();
var args = new CancelableEventArgs();
if (CloseRequested != null)
{
await CloseRequested.Invoke(this, args);
}
e.Handled = args.Cancelled;
deferral.Complete();
}
}
}
@@ -0,0 +1,42 @@
using FluentTerminal.App.Services;
using System;
using Windows.UI.Xaml;
namespace FluentTerminal.App.Adapters
{
public class DispatcherTimerAdapter : IDispatcherTimer
{
private readonly DispatcherTimer _dispatcherTimer;
public DispatcherTimerAdapter()
{
_dispatcherTimer = new DispatcherTimer();
_dispatcherTimer.Tick += OnDispatcherTimerTick;
}
private void OnDispatcherTimerTick(object sender, object e)
{
Tick?.Invoke(this, null);
}
public TimeSpan Interval
{
get => _dispatcherTimer.Interval;
set => _dispatcherTimer.Interval = value;
}
public bool IsEnabled => _dispatcherTimer.IsEnabled;
public event EventHandler<object> Tick;
public void Start()
{
_dispatcherTimer.Start();
}
public void Stop()
{
_dispatcherTimer.Stop();
}
}
}
@@ -0,0 +1,42 @@
using FluentTerminal.App.Services;
using FluentTerminal.App.Services.Dialogs;
using System;
using System.Threading.Tasks;
using Windows.UI.Popups;
namespace FluentTerminal.App.Adapters
{
public class MessageDialogAdapter : IMessageDialog
{
private readonly MessageDialog _messageDialog;
public MessageDialogAdapter()
{
_messageDialog = new MessageDialog(string.Empty);
}
public string Title
{
get => _messageDialog.Title;
set => _messageDialog.Title = value;
}
public string Content
{
get => _messageDialog.Content;
set => _messageDialog.Content = value;
}
public void AddButton(DialogButton button)
{
_messageDialog.Commands.Add(new UICommand(button.ToString()));
}
public async Task<DialogButton> ShowAsync()
{
var result = await _messageDialog.ShowAsync();
return Enum.Parse<DialogButton>(result.Label);
}
}
}
+12 -1
View File
@@ -8,6 +8,10 @@
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Styles\Custom.xaml" />
</ResourceDictionary.MergedDictionaries>
<LinearGradientBrush x:Key="MissingColorSelectionBrush" StartPoint="0,0" EndPoint="1,1">
<GradientStop Offset="0" Color="Black" />
<GradientStop Offset="1.0" Color="White" />
</LinearGradientBrush>
<converters:IntToVisibilityConverter
x:Key="OneToVisibleConverter"
Else="Collapsed"
@@ -21,12 +25,19 @@
<converters:NegateConverter x:Key="NegateConverter" />
<converters:FalseToVisibleConverter x:Key="FalseToVisibleConverter" />
<converters:TrueToVisibleConverter x:Key="TrueToVisibleConverter" />
<converters:TabColorFallbackConverter x:Key="TabColorFallbackConverter" />
<converters:IntToVisibilityConverter
x:Key="ZeroToCollapsedConverter"
Else="Visible"
If="0"
Then="Collapsed" />
<converters:IntToExtendedVirtualKeyConverter x:Key="IntToExtendedVirtualKeyConverter" />
<converters:BackgroundToApplicationThemeConverter x:Key="BackgroundToApplicationThemeConverter" />
<converters:StringToColorConverter x:Key="StringToColorConverter" />
<converters:EnumValueToVisibiltyConverter x:Key="EnumValueToVisibiltyConverter" />
<converters:TabThemeSelectedConverter x:Key="TabThemeSelectedConverter" />
<converters:ColorResourceKeyFallbackConverter x:Key="ColorResourceKeyFallbackConverter" />
<converters:EnumToDescriptionConverter x:Key="EnumToDescriptionConverter" />
</ResourceDictionary>
</Application.Resources>
</Application>
</Application>
+67 -16
View File
@@ -1,5 +1,10 @@
using Autofac;
using FluentTerminal.App.Adapters;
using FluentTerminal.App.Dialogs;
using FluentTerminal.App.Services;
using FluentTerminal.App.Services.Adapters;
using FluentTerminal.App.Services.Dialogs;
using FluentTerminal.App.Services.EventArgs;
using FluentTerminal.App.Services.Implementation;
using FluentTerminal.App.ViewModels;
using FluentTerminal.App.Views;
@@ -15,6 +20,7 @@ using Windows.ApplicationModel.Activation;
using Windows.ApplicationModel.AppService;
using Windows.ApplicationModel.Background;
using Windows.ApplicationModel.Core;
using Windows.Storage;
using Windows.UI.Core;
using Windows.UI.ViewManagement;
using Windows.UI.Xaml;
@@ -34,7 +40,7 @@ namespace FluentTerminal.App
private readonly List<MainViewModel> _mainViewModels;
private SettingsViewModel _settingsViewModel;
private int? _settingsWindowId;
private AppServiceConnection _appServiceConnection;
private IAppServiceConnection _appServiceConnection;
private BackgroundTaskDeferral _appServiceDeferral;
public App()
@@ -43,14 +49,41 @@ namespace FluentTerminal.App
InitializeComponent();
UnhandledException += OnUnhandledException;
var applicationDataContainers = new ApplicationDataContainers
{
LocalSettings = new ApplicationDataContainerAdapter(ApplicationData.Current.LocalSettings),
RoamingSettings = new ApplicationDataContainerAdapter(ApplicationData.Current.RoamingSettings),
KeyBindings = new ApplicationDataContainerAdapter(ApplicationData.Current.RoamingSettings.CreateContainer(Constants.KeyBindingsContainerName, ApplicationDataCreateDisposition.Always)),
ShellProfiles = new ApplicationDataContainerAdapter(ApplicationData.Current.LocalSettings.CreateContainer(Constants.ShellProfilesContainerName, ApplicationDataCreateDisposition.Always)),
Themes = new ApplicationDataContainerAdapter(ApplicationData.Current.RoamingSettings.CreateContainer(Constants.ThemesContainerName, ApplicationDataCreateDisposition.Always))
};
var builder = new ContainerBuilder();
builder.RegisterType<SettingsService>().As<ISettingsService>().SingleInstance();
builder.RegisterType<DefaultValueProvider>().As<IDefaultValueProvider>().SingleInstance();
builder.RegisterType<TrayProcessCommunicationService>().As<ITrayProcessCommunicationService>().SingleInstance();
builder.RegisterType<DialogService>().As<IDialogService>().SingleInstance();
builder.RegisterType<KeyboardCommandService>().As<IKeyboardCommandService>().InstancePerDependency();
builder.RegisterType<NotificationService>().As<INotificationService>().InstancePerDependency();
builder.RegisterType<UpdateService>().As<IUpdateService>().InstancePerDependency();
builder.RegisterType<MainViewModel>().InstancePerDependency();
builder.RegisterType<SettingsViewModel>().InstancePerDependency();
builder.RegisterType<ThemeParserFactory>().As<IThemeParserFactory>().SingleInstance();
builder.RegisterType<ITermThemeParser>().As<IThemeParser>().SingleInstance();
builder.RegisterType<FluentTerminalThemeParser>().As<IThemeParser>().SingleInstance();
builder.RegisterType<ClipboardService>().As<IClipboardService>().SingleInstance();
builder.RegisterType<FileSystemService>().As<IFileSystemService>().SingleInstance();
builder.RegisterType<SystemFontService>().As<ISystemFontService>().SingleInstance();
builder.RegisterType<ShellProfileSelectionDialog>().As<IShellProfileSelectionDialog>().InstancePerDependency();
builder.RegisterType<CreateKeyBindingDialog>().As<ICreateKeyBindingDialog>().InstancePerDependency();
builder.RegisterType<InputDialog>().As<IInputDialog>().InstancePerDependency();
builder.RegisterType<MessageDialogAdapter>().As<IMessageDialog>().InstancePerDependency();
builder.RegisterType<ApplicationViewAdapter>().As<IApplicationView>().InstancePerDependency();
builder.RegisterType<DispatcherTimerAdapter>().As<IDispatcherTimer>().InstancePerDependency();
builder.RegisterType<StartupTaskService>().As<IStartupTaskService>().SingleInstance();
builder.RegisterInstance(applicationDataContainers);
_container = builder.Build();
@@ -62,6 +95,11 @@ namespace FluentTerminal.App
_applicationSettings = _settingsService.GetApplicationSettings();
}
private void OnUnhandledException(object sender, Windows.UI.Xaml.UnhandledExceptionEventArgs e)
{
System.Diagnostics.Debug.WriteLine(e.Exception.ToString());
}
protected override async void OnActivated(IActivatedEventArgs args)
{
if (args is CommandLineActivatedEventArgs commandLineActivated)
@@ -85,11 +123,11 @@ namespace FluentTerminal.App
{
if (_applicationSettings.NewTerminalLocation == NewTerminalLocation.Tab && _mainViewModels.Count > 0)
{
await _mainViewModels.Last().AddTerminal(parameter, false).ConfigureAwait(true);
await _mainViewModels.Last().AddTerminal(parameter, false, Guid.Empty).ConfigureAwait(true);
}
else
{
await CreateNewTerminalWindow(parameter).ConfigureAwait(true);
await CreateNewTerminalWindow(parameter, false).ConfigureAwait(true);
}
}
}
@@ -103,7 +141,7 @@ namespace FluentTerminal.App
else if (command == "new")
{
var viewModel = _container.Resolve<MainViewModel>();
await viewModel.AddTerminal(parameter, false).ConfigureAwait(true);
await viewModel.AddTerminal(parameter, false, Guid.Empty).ConfigureAwait(true);
await CreateMainView(typeof(MainPage), viewModel, true).ConfigureAwait(true);
}
}
@@ -115,13 +153,13 @@ namespace FluentTerminal.App
if (!_alreadyLaunched)
{
var viewModel = _container.Resolve<MainViewModel>();
await viewModel.AddTerminal(null, false).ConfigureAwait(true);
await viewModel.AddTerminal(null, false, Guid.Empty).ConfigureAwait(true);
await CreateMainView(typeof(MainPage), viewModel, true).ConfigureAwait(true);
Window.Current.Activate();
}
else if (_mainViewModels.Count == 0)
{
await CreateSecondaryView<MainViewModel>(typeof(MainPage), true, string.Empty).ConfigureAwait(true);
await CreateSecondaryView<MainViewModel>(typeof(MainPage), true, false, string.Empty).ConfigureAwait(true);
}
}
@@ -136,7 +174,7 @@ namespace FluentTerminal.App
_appServiceDeferral = args.TaskInstance.GetDeferral();
args.TaskInstance.Canceled += OnTaskCanceled;
_appServiceConnection = details.AppServiceConnection;
_appServiceConnection = new AppServiceConnectionAdapter(details.AppServiceConnection);
_trayReady.SetResult(0);
}
@@ -172,6 +210,7 @@ namespace FluentTerminal.App
mainViewModel.Closed += OnMainViewModelClosed;
mainViewModel.NewWindowRequested += OnNewWindowRequested;
mainViewModel.ShowSettingsRequested += OnShowSettingsRequested;
mainViewModel.ShowAboutRequested += OnShowAboutRequested;
_mainViewModels.Add(mainViewModel);
}
@@ -181,13 +220,13 @@ namespace FluentTerminal.App
Window.Current.Activate();
}
private async Task CreateNewTerminalWindow(string startupDirectory)
private async Task CreateNewTerminalWindow(string startupDirectory, bool showProfileSelection)
{
var id = await CreateSecondaryView<MainViewModel>(typeof(MainPage), true, startupDirectory).ConfigureAwait(true);
var id = await CreateSecondaryView<MainViewModel>(typeof(MainPage), true, showProfileSelection, startupDirectory).ConfigureAwait(true);
await ApplicationViewSwitcher.TryShowAsStandaloneAsync(id);
}
private async Task<int> CreateSecondaryView<TViewModel>(Type pageType, bool ExtendViewIntoTitleBar, object parameter)
private async Task<int> CreateSecondaryView<TViewModel>(Type pageType, bool ExtendViewIntoTitleBar, bool showProfileSelection, object parameter)
{
int windowId = 0;
TViewModel viewModel = default;
@@ -209,8 +248,9 @@ namespace FluentTerminal.App
mainViewModel.Closed += OnMainViewModelClosed;
mainViewModel.NewWindowRequested += OnNewWindowRequested;
mainViewModel.ShowSettingsRequested += OnShowSettingsRequested;
mainViewModel.ShowAboutRequested += OnShowAboutRequested;
_mainViewModels.Add(mainViewModel);
await mainViewModel.AddTerminal(directory, false).ConfigureAwait(true);
await mainViewModel.AddTerminal(directory, showProfileSelection, Guid.Empty).ConfigureAwait(true);
}
if (viewModel is SettingsViewModel settingsViewModel)
@@ -239,9 +279,9 @@ namespace FluentTerminal.App
}
}
private async void OnNewWindowRequested(object sender, EventArgs e)
private async void OnNewWindowRequested(object sender, NewWindowRequestedEventArgs e)
{
await CreateNewTerminalWindow(string.Empty).ConfigureAwait(true);
await CreateNewTerminalWindow(string.Empty, e.ShowProfileSelection).ConfigureAwait(true);
}
private void OnSettingsClosed(object sender, EventArgs e)
@@ -251,26 +291,37 @@ namespace FluentTerminal.App
_settingsWindowId = null;
}
private void OnShowAboutRequested(object sender, EventArgs e)
{
ShowAbout().ConfigureAwait(true);
}
private async void OnShowSettingsRequested(object sender, EventArgs e)
{
await ShowSettings().ConfigureAwait(true);
}
private async Task ShowAbout()
{
await ShowSettings().ConfigureAwait(true);
_settingsViewModel.NavigateToAboutPage();
}
private async Task ShowSettings()
{
if (_settingsViewModel == null)
{
_settingsWindowId = await CreateSecondaryView<SettingsViewModel>(typeof(SettingsPage), true, null).ConfigureAwait(true);
_settingsWindowId = await CreateSecondaryView<SettingsViewModel>(typeof(SettingsPage), true, false, null).ConfigureAwait(true);
}
await ApplicationViewSwitcher.TryShowAsStandaloneAsync(_settingsWindowId.Value);
}
private async Task StartSystemTray()
{
var launch = FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync().AsTask();
var launch = FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync("AppLaunchedParameterGroup").AsTask();
var clearCache = WebView.ClearTemporaryWebDataAsync().AsTask();
await Task.WhenAll(launch, clearCache, _trayReady.Task).ConfigureAwait(true);
await _trayProcessCommunicationService.Initialize(_appServiceConnection).ConfigureAwait(true);
_trayProcessCommunicationService.Initialize(_appServiceConnection);
}
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.
@@ -0,0 +1,36 @@
using FluentTerminal.App.Utilities;
using Microsoft.Toolkit.Uwp.Helpers;
using System;
using Windows.UI;
using Windows.UI.Xaml.Data;
namespace FluentTerminal.App.Converters
{
public class BackgroundToApplicationThemeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
Color color;
if (value is string colorString)
{
color = colorString.ToColor();
}
else if (value is Color)
{
color = (Color)value;
}
if (color != null)
{
return ContrastHelper.GetIdealThemeForBackgroundColor(color);
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}
@@ -0,0 +1,30 @@
using Microsoft.Toolkit.Uwp.Helpers;
using System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Media;
namespace FluentTerminal.App.Converters
{
public class ColorResourceKeyFallbackConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value == null)
{
return Application.Current.Resources[(string)parameter];
}
else if (value is string hex)
{
var color = hex.ToColor();
return new SolidColorBrush(color);
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}
@@ -0,0 +1,24 @@
using System;
using FluentTerminal.App.Services.Utilities;
using Windows.UI.Xaml.Data;
namespace FluentTerminal.App.Converters
{
public class EnumToDescriptionConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
// value is the Enum, parameter is the string.
if (value is Enum enumValue)
{
return EnumHelper.GetEnumDescription(enumValue);
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotSupportedException();
}
}
}
@@ -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;
}
}
}
}
@@ -1,6 +1,6 @@
using FluentTerminal.Models.Enums;
using FluentTerminal.App.Services.Utilities;
using FluentTerminal.Models.Enums;
using System;
using System.ComponentModel;
using Windows.UI.Xaml.Data;
namespace FluentTerminal.App.Converters
@@ -11,7 +11,7 @@ namespace FluentTerminal.App.Converters
{
if (value is int intValue)
{
return GetEnumDescription((ExtendedVirtualKey)intValue);
return EnumHelper.GetEnumDescription((ExtendedVirtualKey)intValue);
}
return null;
}
@@ -20,24 +20,5 @@ namespace FluentTerminal.App.Converters
{
throw new NotImplementedException();
}
private static string GetEnumDescription(Enum value)
{
var fieldInfo = value.GetType().GetField(value.ToString());
if (fieldInfo == null)
{
return value.ToString();
}
var attributes = (DescriptionAttribute[])fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attributes?.Length > 0)
{
return attributes[0].Description;
}
return value.ToString();
}
}
}
}
Loaded 100 of 259 files, more files were not shown because too many files have changed in this diff. Show more