public SearchHints(IRecentSearchCollection recentSearchCollection, ISchedulerProvider schedulerProvider) { //User feedback to guide them whilst typing var searchText = this.WhenValueChanged(vm => vm.SearchText); var useRegEx = this.WhenValueChanged(vm => vm.UseRegex); //if regex then validate var combined = searchText.CombineLatest(useRegEx, (text, regex) => new SearchRequest(text, regex)) .Throttle(TimeSpan.FromMilliseconds(250)) .Select(searchRequest => searchRequest.BuildMessage()) .Publish(); IsValid = combined.Select(shm => shm.IsValid).DistinctUntilChanged().ForBinding(); Message = combined.Select(shm => shm.Message).DistinctUntilChanged().ForBinding(); var forceRefreshOfError = combined.Select(shm => shm.IsValid) .DistinctUntilChanged() .Subscribe(_ => { this.OnPropertyChanged("SearchText"); }); var predictRegex = this.WhenValueChanged(vm => vm.SearchText) .Select(text => _regexInspector.DoesThisLookLikeRegEx(text)) .Subscribe(likeRegex => UseRegex = likeRegex); //Handle adding new search var searchRequested = new Subject <SearchRequest>(); SearchRequested = searchRequested.AsObservable(); AddSearchCommand = new Command(async() => { await Task.Run(() => { recentSearchCollection.Add(new RecentSearch(SearchText)); searchRequested.OnNext(new SearchRequest(SearchText, UseRegex)); SearchText = string.Empty; UseRegex = false; }); }, () => IsValid.Value && SearchText.Length > 0); var dataLoader = recentSearchCollection.Items.Connect() // .Filter(filter) .Transform(recentSearch => recentSearch.Text) .Sort(SortExpressionComparer <string> .Ascending(str => str)) .ObserveOn(schedulerProvider.MainThread) .Bind(out _hints) .Subscribe(); _cleanUp = new CompositeDisposable(IsValid, Message, predictRegex, dataLoader, searchRequested.SetAsComplete(), combined.Connect(), forceRefreshOfError); }
public SearchHints(IRecentSearchCollection recentSearchCollection, ISchedulerProvider schedulerProvider) { //User feedback to guide them whilst typing var searchText = this.WhenValueChanged(vm => vm.SearchText); var useRegEx = this.WhenValueChanged(vm => vm.UseRegex); //if regex then validate var combined = searchText.CombineLatest(useRegEx, (text, regex) => new SearchRequest(text, regex)) .Throttle(TimeSpan.FromMilliseconds(250)) .Select(searchRequest => searchRequest.BuildMessage()) .Publish(); IsValid = combined.Select(shm => shm.IsValid).DistinctUntilChanged().ForBinding(); Message = combined.Select(shm => shm.Message).DistinctUntilChanged().ForBinding(); var forceRefreshOfError = combined.Select(shm => shm.IsValid) .DistinctUntilChanged() .Subscribe(_ => { this.OnPropertyChanged("SearchText"); }); var predictRegex = this.WhenValueChanged(vm => vm.SearchText) .Select(text => _regexInspector.DoesThisLookLikeRegEx(text)) .Subscribe(likeRegex => UseRegex = likeRegex); //Handle adding new search var searchRequested = new Subject<SearchRequest>(); SearchRequested = searchRequested.AsObservable(); AddSearchCommand = new Command(async () => { await Task.Run(() => { recentSearchCollection.Add(new RecentSearch(SearchText)); searchRequested.OnNext(new SearchRequest(SearchText, UseRegex)); SearchText = string.Empty; UseRegex = false; }); }, () => IsValid.Value && SearchText.Length > 0); var dataLoader = recentSearchCollection.Items.Connect() // .Filter(filter) .Transform(recentSearch => recentSearch.Text) .Sort(SortExpressionComparer<string>.Ascending(str => str)) .ObserveOn(schedulerProvider.MainThread) .Bind(out _hints) .Subscribe(); _cleanUp = new CompositeDisposable(IsValid, Message, predictRegex, dataLoader, searchRequested.SetAsComplete(), combined.Connect(), forceRefreshOfError); }
public SearchHints(IRecentSearchCollection recentSearchCollection, ISchedulerProvider schedulerProvider) { //build a predicate when SearchText changes var filter = this.WhenValueChanged(t => t.SearchText) .Throttle(TimeSpan.FromMilliseconds(250)) .Select(BuildFilter); //User feedback to guide them whilst typing var searchText = this.WhenValueChanged(vm => vm.SearchText); var useRegEx = this.WhenValueChanged(vm => vm.UseRegex); //if regex then validate var combined = searchText.CombineLatest(useRegEx, (text, regex) => new SearchRequest(text, regex)) .Publish(); IsValid = combined.Select(searchRequest => searchRequest.BuildMessage()).ForBinding(); var predictRegex = this.WhenValueChanged(vm => vm.SearchText) //.Where(text=>!string.IsNullOrEmpty(text)) .Select(text=>_regexInspector.DoesThisLookLikeRegEx(text)) // .DistinctUntilChanged() .Subscribe(likeRegex=> UseRegex= likeRegex); //Handle adding new search var searchRequested = new Subject<SearchRequest>(); SearchRequested = searchRequested.AsObservable(); AddSearchCommand = new Command(() => { recentSearchCollection.Add(new RecentSearch(SearchText)); searchRequested.OnNext(new SearchRequest(SearchText, UseRegex)); SearchText = string.Empty; UseRegex = false; }, () => IsValid.Value.IsValid && SearchText.Length>0); var dataLoader = recentSearchCollection.Items.Connect() .Filter(filter) .Transform(recentSearch=> recentSearch.Text) .Sort(SortExpressionComparer<string>.Ascending(str => str)) .ObserveOn(schedulerProvider.MainThread) .Bind(out _hints) .Subscribe(); _cleanUp = new CompositeDisposable( dataLoader, IsValid, predictRegex, Disposable.Create(searchRequested.OnCompleted), combined.Connect()); }
public FileTailerViewModel([NotNull] ILogger logger, [NotNull] ISchedulerProvider schedulerProvider, [NotNull] IFileWatcher fileWatcher, [NotNull] ISelectionMonitor selectionMonitor, [NotNull] IClipboardHandler clipboardHandler, [NotNull] ISearchInfoCollection searchInfoCollection, [NotNull] IInlineViewerFactory inlineViewerFactory, [NotNull] ISetting <GeneralOptions> generalOptions, [NotNull] IRecentSearchCollection recentSearchCollection, [NotNull] SearchHints searchHints) { if (logger == null) { throw new ArgumentNullException(nameof(logger)); } if (schedulerProvider == null) { throw new ArgumentNullException(nameof(schedulerProvider)); } if (fileWatcher == null) { throw new ArgumentNullException(nameof(fileWatcher)); } if (selectionMonitor == null) { throw new ArgumentNullException(nameof(selectionMonitor)); } if (clipboardHandler == null) { throw new ArgumentNullException(nameof(clipboardHandler)); } if (searchInfoCollection == null) { throw new ArgumentNullException(nameof(searchInfoCollection)); } if (inlineViewerFactory == null) { throw new ArgumentNullException(nameof(inlineViewerFactory)); } if (generalOptions == null) { throw new ArgumentNullException(nameof(generalOptions)); } if (searchHints == null) { throw new ArgumentNullException(nameof(searchHints)); } SelectionMonitor = selectionMonitor; SearchHints = searchHints; CopyToClipboardCommand = new Command(() => clipboardHandler.WriteToClipboard(selectionMonitor.GetSelectedText())); SelectedItemsCount = selectionMonitor.Selected.Connect().QueryWhenChanged(collection => collection.Count).ForBinding(); SearchCollection = new SearchCollection(searchInfoCollection, schedulerProvider); UsingDarkTheme = generalOptions.Value .ObserveOn(schedulerProvider.MainThread) .Select(options => options.Theme == Theme.Dark) .ForBinding(); HighlightTail = generalOptions.Value .ObserveOn(schedulerProvider.MainThread) .Select(options => options.HighlightTail) .ForBinding(); HighlightDuration = generalOptions.Value .ObserveOn(schedulerProvider.MainThread) .Select(options => new Duration(TimeSpan.FromSeconds(options.HighlightDuration))) .ForBinding(); //An observable which acts as a scroll command var autoChanged = this.WhenValueChanged(vm => vm.AutoTail); var scroller = _userScrollRequested.CombineLatest(autoChanged, (user, auto) => { var mode = AutoTail ? ScrollReason.Tail : ScrollReason.User; return(new ScrollRequest(mode, user.PageSize, user.FirstIndex)); }) .ObserveOn(schedulerProvider.Background) .DistinctUntilChanged(); //tailer is the main object used to tail, scroll and filter in a file var lineScroller = new LineScroller(SearchCollection.Latest.ObserveOn(schedulerProvider.Background), scroller); //Add a complete file display [No search info here] var indexed = fileWatcher.Latest.Index() .Replay(1).RefCount(); IsLoading = indexed.Take(1).Select(_ => false).StartWith(true).ForBinding(); searchInfoCollection.Add("<All>", indexed, SearchType.All); //command to add the current search to the tail collection KeepSearchCommand = new Command(() => { var text = SearchHints.SearchText; var latest = fileWatcher.Latest .Search(s => s.Contains(text, StringComparison.OrdinalIgnoreCase)) .Replay(1).RefCount(); searchInfoCollection.Add(text, latest); recentSearchCollection.Add(new RecentSearch(text)); SearchHints.SearchText = string.Empty; }, () => SearchHints.SearchText.IsLongerThanOrEqualTo(3)); //User feedback to show file size FileSizeText = fileWatcher.Latest.Select(fn => fn.Size) .Select(size => size.FormatWithAbbreviation()) .DistinctUntilChanged() .ForBinding(); //User feedback to guide them whilst typing SearchHint = this.WhenValueChanged(vm => vm.SearchHints.SearchText) .Select(text => { if (string.IsNullOrEmpty(text)) { return("Type to search"); } return(text.Length < 3 ? "Enter at least 3 characters" : "Hit enter to search"); }).ForBinding(); //load lines into observable collection var lineProxyFactory = new LineProxyFactory(new TextFormatter(searchInfoCollection)); var loader = lineScroller.Lines.Connect() .Transform(lineProxyFactory.Create, new ParallelisationOptions(ParallelType.Ordered, 5)) .Sort(SortExpressionComparer <LineProxy> .Ascending(proxy => proxy)) .ObserveOn(schedulerProvider.MainThread) .Bind(out _data) .DisposeMany() .Subscribe(changes => logger.Info($"Rows changed. {changes.Adds} adds, {changes.Removes} removed"), ex => logger.Error(ex, "There is a problem with bind data")); //monitor matching lines and start index, Count = indexed.Select(latest => latest.Count).ForBinding(); CountText = indexed.Select(latest => $"{latest.Count.ToString("##,###")} lines").ForBinding(); LatestCount = SearchCollection.Latest.Select(latest => latest.Count).ForBinding(); //track first visible index var firstIndexMonitor = lineScroller.Lines.Connect() .Buffer(TimeSpan.FromMilliseconds(250)).FlattenBufferResult() .ToCollection() .Select(lines => lines.Count == 0 ? 0 : lines.Select(l => l.Index).Max() - lines.Count + 1) .ObserveOn(schedulerProvider.MainThread) .Subscribe(first => FirstIndex = first); //Create objects required for inline viewing var isUserDefinedChanged = SearchCollection.WhenValueChanged(sc => sc.Selected) .Select(selected => selected.IsUserDefined) .DistinctUntilChanged(); var inlineViewerVisible = isUserDefinedChanged.CombineLatest(this.WhenValueChanged(vm => vm.ShowInline), (userDefined, showInline) => userDefined && showInline); CanViewInline = isUserDefinedChanged.ForBinding(); InlineViewerVisible = inlineViewerVisible.ForBinding(); //return an empty line provider unless user is viewing inline - this saves needless trips to the file var inline = indexed.CombineLatest(inlineViewerVisible, (index, ud) => ud ? index : new EmptyLineProvider()); InlineViewer = inlineViewerFactory.Create(inline, this.WhenValueChanged(vm => vm.SelectedItem), lineProxyFactory); _cleanUp = new CompositeDisposable(lineScroller, loader, firstIndexMonitor, IsLoading, Count, LatestCount, FileSizeText, SearchHint, SelectedItemsCount, CanViewInline, InlineViewer, InlineViewerVisible, SearchCollection, searchInfoCollection, HighlightTail, UsingDarkTheme, searchHints, Disposable.Create(() => { _userScrollRequested.OnCompleted(); SelectionMonitor?.Dispose(); })); }