Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/quick search #66

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/Camelot.Services.Abstractions/IQuickSearchService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System;
using Camelot.Services.Abstractions.Models;

namespace Camelot.Services.Abstractions;

public interface IQuickSearchService
{
bool IsEnabled { get; }

event EventHandler<EventArgs> QuickSearchModeChanged;

QuickSearchModel GetQuickSearchSettings();

void SaveQuickSearchSettings(QuickSearchModel quickSearchModel);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System;

namespace Camelot.Services.Abstractions.Models.Enums.Input;

/// <summary>
/// Needed to be duplicated here, since nor Avalonia, nor Windows.Forms, etc
/// are referenced in "Camelot.Services.Abstractions"
/// Used in feature of 'quick search' to determine if shift key is down.
/// </summary>

[Flags]
public enum KeyModifiers
{
None = 0,
Alt = 1,
Control = 2,
Shift = 4,
Meta = 8,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@

namespace Camelot.Services.Abstractions.Models.Enums;

public enum QuickSearchMode : byte
{
Disabled,
Letter,
Word
}
13 changes: 13 additions & 0 deletions src/Camelot.Services.Abstractions/Models/QuickSearchModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Camelot.Services.Abstractions.Models.Enums;

namespace Camelot.Services.Abstractions.Models;

public class QuickSearchModel
{
public QuickSearchMode SelectedMode { get; }

public QuickSearchModel(QuickSearchMode selectedMode)
{
SelectedMode = selectedMode;
}
}
55 changes: 55 additions & 0 deletions src/Camelot.Services/QuickSearchService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using System;
using Camelot.DataAccess.UnitOfWork;
using Camelot.Extensions;
using Camelot.Services.Abstractions;
using Camelot.Services.Abstractions.Models;
using Camelot.Services.Abstractions.Models.Enums;

namespace Camelot.Services;

public class QuickSearchService : IQuickSearchService
{
private const string SettingsId = "QuickSearchSettings";

private readonly IUnitOfWorkFactory _unitOfWorkFactory;

private QuickSearchModel _cachedSettingsValue;

public bool IsEnabled => _cachedSettingsValue.SelectedMode != QuickSearchMode.Disabled;

public event EventHandler<EventArgs> QuickSearchModeChanged;

public QuickSearchService(IUnitOfWorkFactory unitOfWorkFactory)
{
_unitOfWorkFactory = unitOfWorkFactory;

GetQuickSearchSettings();
}

public QuickSearchModel GetQuickSearchSettings()
{
if (_cachedSettingsValue is not null)
{
return _cachedSettingsValue;
}

using var uow = _unitOfWorkFactory.Create();
var repository = uow.GetRepository<QuickSearchModel>();
var dbModel = repository.GetById(SettingsId) ?? new QuickSearchModel(QuickSearchMode.Disabled);

return _cachedSettingsValue = dbModel;
}

public void SaveQuickSearchSettings(QuickSearchModel quickSearchModel)
{
using var uow = _unitOfWorkFactory.Create();
var repository = uow.GetRepository<QuickSearchModel>();

repository.Upsert(SettingsId, quickSearchModel);
_cachedSettingsValue = quickSearchModel;

RaiseQuickSearchModeChangedEvent();
}

private void RaiseQuickSearchModeChangedEvent() => QuickSearchModeChanged.Raise(this, EventArgs.Empty);
}
1 change: 0 additions & 1 deletion src/Camelot.ViewModels.Windows/WinApi/ShellIcon.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using Camelot.Services.Windows.WinApi;
using System.Diagnostics;

namespace Camelot.ViewModels.Windows.WinApi;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@ public class SettingsDialogViewModel : DialogViewModelBase
public ISettingsViewModel TerminalSettingsViewModel { get; set; }

public ISettingsViewModel GeneralSettingsViewModel { get; set; }

public ISettingsViewModel IconsSettingsViewModel { get; set; }

public ISettingsViewModel KeyboardSettingsViewModel { get; set; }

public int SelectedIndex
{
get => _selectedIndex;
Expand All @@ -32,17 +35,20 @@ public int SelectedIndex
public SettingsDialogViewModel(
ISettingsViewModel generalSettingsViewModel,
ISettingsViewModel terminalSettingsViewModel,
ISettingsViewModel iconsSettingsViewModel)
ISettingsViewModel iconsSettingsViewModel,
ISettingsViewModel keyboardSettingsViewModel)
{
TerminalSettingsViewModel = terminalSettingsViewModel;
GeneralSettingsViewModel = generalSettingsViewModel;
IconsSettingsViewModel = iconsSettingsViewModel;
KeyboardSettingsViewModel = keyboardSettingsViewModel;

_settingsViewModels = new[]
{
generalSettingsViewModel,
terminalSettingsViewModel,
iconsSettingsViewModel
iconsSettingsViewModel,
keyboardSettingsViewModel
};

Activate(_settingsViewModels.First());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@
using Camelot.ViewModels.Implementations.Dialogs;
using Camelot.ViewModels.Implementations.Dialogs.NavigationParameters;
using Camelot.ViewModels.Interfaces.MainWindow.FilePanels;
using Camelot.ViewModels.Interfaces.MainWindow.FilePanels.EventArgs;
using Camelot.ViewModels.Interfaces.MainWindow.FilePanels.Nodes;
using Camelot.ViewModels.Interfaces.MainWindow.FilePanels.Tabs;
using Camelot.ViewModels.Interfaces.MainWindow.Operations;
using Camelot.ViewModels.Services.Interfaces;

using DynamicData;
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
Expand Down Expand Up @@ -49,7 +51,8 @@ public class FilesPanelViewModel : ViewModelBase, IFilesPanelViewModel

private CancellationTokenSource _cancellationTokenSource;
private string _currentDirectory;
private INodeSpecification _specification;
private INodeSpecification _searchSpecification;
private ISpecification<IFileSystemNodeViewModel> _filterSpecification;

private IEnumerable<IFileViewModel> SelectedFiles => _selectedFileSystemNodes.OfType<IFileViewModel>();

Expand All @@ -63,6 +66,8 @@ public class FilesPanelViewModel : ViewModelBase, IFilesPanelViewModel

public ISearchViewModel SearchViewModel { get; }

public IQuickSearchViewModel QuickSearchViewModel { get; }

public ITabsListViewModel TabsListViewModel { get; }

public IOperationsViewModel OperationsViewModel { get; }
Expand Down Expand Up @@ -129,6 +134,7 @@ public FilesPanelViewModel(
IPermissionsService permissionsService,
IDialogService dialogService,
ISearchViewModel searchViewModel,
IQuickSearchViewModel quickSearchViewModel,
ITabsListViewModel tabsListViewModel,
IOperationsViewModel operationsViewModel,
IDirectorySelectorViewModel directorySelectorViewModel,
Expand All @@ -150,6 +156,7 @@ public FilesPanelViewModel(
_dialogService = dialogService;

SearchViewModel = searchViewModel;
QuickSearchViewModel = quickSearchViewModel;
TabsListViewModel = tabsListViewModel;
OperationsViewModel = operationsViewModel;
DirectorySelectorViewModel = directorySelectorViewModel;
Expand All @@ -165,7 +172,6 @@ public FilesPanelViewModel(
(DirectoryModel dm) => dm is not null);
GoToParentDirectoryCommand = ReactiveCommand.Create(GoToParentDirectory, canGoToParentDirectory);
SortFilesCommand = ReactiveCommand.Create<SortingMode>(SortFiles);

SubscribeToEvents();
UpdateStateAsync().Forget();
}
Expand Down Expand Up @@ -209,6 +215,7 @@ private void SubscribeToEvents()
SearchViewModel.SearchSettingsChanged += SearchViewModelOnSearchSettingsChanged;
_filePanelDirectoryObserver.CurrentDirectoryChanged += async (_, _) => await UpdateStateAsync();
_selectedFileSystemNodes.CollectionChanged += SelectedFileSystemNodesOnCollectionChanged;
QuickSearchViewModel.QuickSearchFilterChanged += QuickSearchViewModelOnQuickSearchFilterChanged;

_fileSystemWatchingService.NodeCreated += (_, args) =>
ExecuteInUiThread(() => InsertNode(args.Node));
Expand Down Expand Up @@ -257,6 +264,8 @@ private async Task UpdateStateAsync()

CurrentDirectoryChanged.Raise(this, EventArgs.Empty);
this.RaisePropertyChanged(nameof(ParentDirectory));

QuickSearchViewModel.ClearQuickSearch();
}

private void UpdateNode(string nodePath) => RecreateNode(nodePath, nodePath);
Expand All @@ -283,6 +292,7 @@ private void InsertNode(string nodePath, bool isSelected = false)

var index = GetInsertIndex(newNodeModel);
_fileSystemNodes.Insert(index, newNodeModel);
UpdateNodeFiltering(newNodeModel);

if (isSelected)
{
Expand Down Expand Up @@ -328,14 +338,14 @@ private void ReloadFiles()
{
CancelPreviousSearchIfNeeded();

_specification = SearchViewModel.GetSpecification();
if (_specification.IsRecursive)
_searchSpecification = SearchViewModel.GetSpecification();
if (_searchSpecification.IsRecursive)
{
RecursiveSearch(_specification);
RecursiveSearch(_searchSpecification);
}
else
{
Search(_specification);
Search(_searchSpecification);
}

InsertParentDirectory();
Expand Down Expand Up @@ -420,6 +430,27 @@ private void SelectedFileSystemNodesOnCollectionChanged(object sender, NotifyCol
this.RaisePropertyChanged(nameof(AreAnyFileSystemNodesSelected));
}

private void QuickSearchViewModelOnQuickSearchFilterChanged(object sender, QuickSearchFilterChangedEventArgs e)
{
_filterSpecification = QuickSearchViewModel.GetSpecification();
_fileSystemNodes.ForEach(UpdateNodeFiltering);

MoveSelection(e.Direction);
}

private void MoveSelection(SelectionChangeDirection direction)
{
switch (direction)
{
case SelectionChangeDirection.Backward:
MoveSelection(-1);
break;
case SelectionChangeDirection.Forward:
MoveSelection(1);
break;
}
}

private int GetInsertIndex(IFileSystemNodeViewModel newNodeViewModel)
{
var comparer = GetComparer();
Expand All @@ -428,14 +459,71 @@ private int GetInsertIndex(IFileSystemNodeViewModel newNodeViewModel)
return index < 0 ? index ^ -1 : index;
}

private IComparer<IFileSystemNodeViewModel> GetComparer() =>
_comparerFactory.Create(SelectedTab.SortingViewModel);
private IComparer<IFileSystemNodeViewModel> GetComparer() => _comparerFactory.Create(SelectedTab.SortingViewModel);

private IFileSystemNodeViewModel GetViewModel(string nodePath) =>
_fileSystemNodes.FirstOrDefault(n => n.FullPath == nodePath);

private void ExecuteInUiThread(Action action) => _applicationDispatcher.Dispatch(action);

private bool CheckIfShouldShowNode(string nodePath) =>
_specification?.IsSatisfiedBy(_nodeService.GetNode(nodePath)) ?? true;
}
_searchSpecification?.IsSatisfiedBy(_nodeService.GetNode(nodePath)) ?? true;

private void MoveSelection(int step)
{
var (selectedIndex, selectedNode) = GetSelectedNodeWithIndex();
var newNode = GetNewSelectedNode(selectedIndex, step);

ChangeSelectedNode(selectedNode, newNode);
}

private IFileSystemNodeViewModel GetNewSelectedNode(int selectedIndex, int step)
{
var count = _fileSystemNodes.Count;

var start = selectedIndex > -1 ? selectedIndex + step : 0;
start = (start + count) % count;

for (var i = start; i != start - step; i = (i + step + count) % count)
{
var file = _fileSystemNodes[i];
if (!file.IsFilteredOut)
{
return file;
}
}

return null;
}

private (int, IFileSystemNodeViewModel) GetSelectedNodeWithIndex()
{
// TODO: check if possible to improve
var currentSelectedNode = _selectedFileSystemNodes.FirstOrDefault();

return currentSelectedNode is null
? (-1, null)
: (_fileSystemNodes.IndexOf(currentSelectedNode), currentSelectedNode);
}

private void ChangeSelectedNode(IFileSystemNodeViewModel oldNode, IFileSystemNodeViewModel newNode)
{
if (newNode is null)
{
return;
}

if (oldNode is not null)
{
UnselectNode(oldNode.FullPath);
}

SelectNode(newNode.FullPath);
}

private void UpdateNodeFiltering(IFileSystemNodeViewModel nodeViewModel) =>
nodeViewModel.IsFilteredOut = CheckIfShouldFilterOutNode(nodeViewModel);

private bool CheckIfShouldFilterOutNode(IFileSystemNodeViewModel nodeViewModel) =>
!_filterSpecification?.IsSatisfiedBy(nodeViewModel) ?? false;
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ public abstract class FileSystemNodeViewModelBase : ViewModelBase, IFileSystemNo
[Reactive]
public bool IsEditing { get; set; }

[Reactive]
public bool IsFilteredOut { get; set; }

public bool IsArchive => _fileSystemNodeFacade.CheckIfNodeIsArchive(FullPath);

public bool ShouldShowOpenSubmenu { get; }
Expand Down
Loading
Loading