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] ISearchMetadataCollection searchMetadataCollection, [NotNull] SearchOptionsViewModel searchOptionsViewModel, [NotNull] SearchHints searchHints, IObjectProvider objectProvider) { 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 (searchMetadataCollection == null) throw new ArgumentNullException(nameof(searchMetadataCollection)); if (searchOptionsViewModel == null) throw new ArgumentNullException(nameof(searchOptionsViewModel)); if (searchHints == null) throw new ArgumentNullException(nameof(searchHints)); _objectProvider = objectProvider; SelectionMonitor = selectionMonitor; SearchOptions = searchOptionsViewModel; 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(); IsLoading = searchInfoCollection.All.Take(1).Select(_ => false).StartWith(true).ForBinding(); //command to add the current search to the tail collection var searchInvoker = SearchHints.SearchRequested.Subscribe(request => { searchInfoCollection.Add(request.Text, request.UseRegEx); }); //User feedback to show file size FileSizeText = fileWatcher.Latest.Select(fn=>fn.Size) .Select(size => size.FormatWithAbbreviation()) .DistinctUntilChanged() .ForBinding(); //tailer is the main object used to tail, scroll and filter in a file var lineScroller = new LineScroller(SearchCollection.Latest.ObserveOn(schedulerProvider.Background), scroller); //load lines into observable collection var lineProxyFactory = new LineProxyFactory(new TextFormatter(searchMetadataCollection),new LineMatches(searchMetadataCollection)); var loader = lineScroller.Lines.Connect() .Transform(x=>lineProxyFactory.Create(x), new ParallelisationOptions(ParallelType.Ordered,3)) .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 = searchInfoCollection.All.Select(latest=>latest.Count).ForBinding(); CountText = searchInfoCollection.All.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) .Where(selected=> selected!=null) .Select(selected => selected.IsUserDefined) .DistinctUntilChanged() .Replay(1) .RefCount(); 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 = searchInfoCollection.All.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, SelectedItemsCount, CanViewInline, InlineViewer, InlineViewerVisible, SearchCollection, searchInfoCollection, HighlightTail, UsingDarkTheme, searchHints, searchMetadataCollection, searchMetadataCollection, SelectionMonitor, SearchOptions, searchInvoker, _userScrollRequested.SetAsComplete()); }