public WindowViewModel(IObjectProvider objectProvider, IWindowFactory windowFactory) { _objectProvider = objectProvider; InterTabClient = new InterTabClient(windowFactory); OpenFileCommand = new Command(OpenFile); ShowInGitHubCommand = new Command(()=> Process.Start("https://github.com/RolandPheasant")); Version = $"v{Assembly.GetEntryAssembly().GetName().Version.ToString(3)}"; var fileDropped = DropMonitor.Dropped.Subscribe(OpenFile); var isEmptyChecker = Views.ToObservableChangeSet() .Count() .StartWith(0) .Select(count=>count==0) .Subscribe(isEmpty=> IsEmpty = isEmpty); _cleanUp = Disposable.Create(() => { isEmptyChecker.Dispose(); fileDropped.Dispose(); DropMonitor.Dispose(); foreach (var disposable in Views.Select(vc=>vc.Content).OfType<IDisposable>()) disposable.Dispose(); }); }
public SearchViewModel(SearchInfo info, Action<SearchViewModel> removeAction) { _info = info; RemoveCommand = new Command(()=> removeAction(this)); var counter = _info.Latest .Select(lp => lp.Count) .Subscribe(count => Count = count); var counterTextFormatter = _info.Latest .Select(lp => { var limited = lp as IHasLimitationOfLines; if (limited == null) return $"{lp.Count.ToString("#,###0")}"; return limited.HasReachedLimit ? $"{limited.Maximum.ToString("#,###0")}+" : $"{lp.Count.ToString("#,###0")}"; }) .Subscribe(countText => CountText = countText); var progressMonitor = _info.Latest.OfType<IProgressInfo>().Subscribe(result => { Searching = result.IsSearching; Segments = result.Segments; SegmentsSearched = result.SegmentsCompleted; }); _cleanUp = new CompositeDisposable(progressMonitor, counter, counterTextFormatter); }
public SearchOptionsProxy(SearchMetadata searchMetadata, IEnumerable<Swatch> swatches, Action<SearchMetadata> removeAction) { Swatches = swatches; if (searchMetadata == null) throw new ArgumentNullException(nameof(searchMetadata)); if (swatches == null) throw new ArgumentNullException(nameof(swatches)); if (removeAction == null) throw new ArgumentNullException(nameof(removeAction)); _searchMetadata = searchMetadata; Highlight = _searchMetadata.Highlight; Filter = _searchMetadata.Filter; RemoveCommand = new Command(() => removeAction(searchMetadata)); }
public WindowViewModel(IObjectProvider objectProvider, IWindowFactory windowFactory, ILogger logger, IWindowsController windowsController, RecentFilesViewModel recentFilesViewModel, GeneralOptionsViewModel generalOptionsViewModel, ISchedulerProvider schedulerProvider) { _logger = logger; _windowsController = windowsController; RecentFiles = recentFilesViewModel; GeneralOptions = generalOptionsViewModel; _schedulerProvider = schedulerProvider; _objectProvider = objectProvider; InterTabClient = new InterTabClient(windowFactory); OpenFileCommand = new Command(OpenFile); ShowInGitHubCommand = new Command(()=> Process.Start("https://github.com/RolandPheasant")); ZoomOutCommand= new Command(()=> { GeneralOptions.Scale = GeneralOptions.Scale + 5; }); ZoomInCommand = new Command(() => { GeneralOptions.Scale = GeneralOptions.Scale - 5; }); SaveLayoutCommand = new Command(WalkTheLayout); ExitCommmand = new Command(() => Application.Current.Shutdown()); Version = $"v{Assembly.GetEntryAssembly().GetName().Version.ToString(3)}"; var fileDropped = DropMonitor.Dropped.Subscribe(OpenFile); var isEmptyChecker = Views.ToObservableChangeSet() .ToCollection() .Select(items=>items.Count) .StartWith(0) .Select(count=>count==0) .Subscribe(isEmpty=> IsEmpty = isEmpty); var openRecent = recentFilesViewModel.OpenFileRequest .Subscribe(file => { MenuIsOpen = false; OpenFile(file); }); _cleanUp = new CompositeDisposable(recentFilesViewModel, isEmptyChecker, fileDropped, DropMonitor, openRecent, Disposable.Create(() => { Views.Select(vc => vc.Content) .OfType<IDisposable>() .ForEach(d=>d.Dispose()); })); }
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 SearchOptionsProxy(SearchMetadata searchMetadata, IEnumerable<Swatch> swatches, Action<SearchMetadata> removeAction) { Swatches = swatches; if (searchMetadata == null) throw new ArgumentNullException(nameof(searchMetadata)); if (swatches == null) throw new ArgumentNullException(nameof(swatches)); if (removeAction == null) throw new ArgumentNullException(nameof(removeAction)); _searchMetadata = searchMetadata; Highlight = _searchMetadata.Highlight; Filter = _searchMetadata.Filter; UseRegex = searchMetadata.UseRegex; IgnoreCase = searchMetadata.IgnoreCase; Status = searchMetadata.UseRegex ? SearchResultIndicatorStatus.Regex : SearchResultIndicatorStatus.Text; RemoveCommand = new Command(() => removeAction(searchMetadata)); }
public SearchOptionsViewModel(ISearchMetadataCollection metadataCollection, ISchedulerProvider schedulerProvider) { //TODO: options for colour var swatches = new SwatchesProvider().Swatches; ReadOnlyObservableCollection<SearchOptionsProxy> data; var userOptions = metadataCollection.Metadata.Connect() .WhereReasonsAre(ChangeReason.Add, ChangeReason.Remove) //ignore updates because we update from here .Transform(meta => new SearchOptionsProxy(meta, swatches,m => metadataCollection.Remove(m.SearchText))) .SubscribeMany(so => { //when a value changes, write the original value back to the cache return so.WhenAnyPropertyChanged() .Subscribe(_ => metadataCollection.Add(new SearchMetadata(so.Text, so.Filter, so.Highlight))); }) .Sort(SortExpressionComparer<SearchOptionsProxy>.Ascending(proxy=>proxy.Text)) .ObserveOn(schedulerProvider.MainThread) .Bind(out data) .Subscribe(); Data = data; AddSearchCommand = new Command(() => { metadataCollection.Add(new SearchMetadata(SearchText,false,true)); SearchText = string.Empty; }, () => SearchText.IsLongerThanOrEqualTo(3) && !metadataCollection.Metadata.Lookup((CaseInsensitiveString)SearchText).HasValue); var commandRefresher = this.WhenValueChanged(vm => vm.SearchText) .Subscribe(_ => ((Command) AddSearchCommand).Refresh()); //User feedback to guide them whilst typing SearchHint = this.WhenValueChanged(vm => vm.SearchText) .Select(text => { if (string.IsNullOrEmpty(text)) return "Type to highlight"; return text.Length < 3 ? "Enter at least 3 characters" : "Hit enter for more options"; }).ForBinding(); _cleanUp = new CompositeDisposable(commandRefresher, userOptions, SearchHint); }
public InlineViewer([NotNull] InlineViewerArgs args, [NotNull] IClipboardHandler clipboardHandler, [NotNull] ISchedulerProvider schedulerProvider, [NotNull] ISelectionMonitor selectionMonitor, [NotNull] ILogger logger, [NotNull] IThemeProvider themeProvider) { if (args == null) throw new ArgumentNullException(nameof(args)); if (clipboardHandler == null) throw new ArgumentNullException(nameof(clipboardHandler)); if (schedulerProvider == null) throw new ArgumentNullException(nameof(schedulerProvider)); if (selectionMonitor == null) throw new ArgumentNullException(nameof(selectionMonitor)); if (themeProvider == null) throw new ArgumentNullException(nameof(themeProvider)); SelectionMonitor = selectionMonitor; CopyToClipboardCommand = new Command(() => clipboardHandler.WriteToClipboard(selectionMonitor.GetSelectedText())); _isSettingScrollPosition = false; var lineProvider = args.LineProvider; var selectedChanged = args.SelectedChanged; var pageSize = this.WhenValueChanged(vm=>vm.PageSize); //if use selection is null, tail the file var scrollSelected = selectedChanged .CombineLatest(lineProvider, pageSize, (proxy, lp, pge) => proxy == null ? new ScrollRequest(pge,0) : new ScrollRequest(pge, proxy.Start)) .DistinctUntilChanged(); var horizonalScrollArgs = new ReplaySubject<TextScrollInfo>(1); HorizonalScrollChanged = hargs => { horizonalScrollArgs.OnNext(hargs); }; var scrollUser = _userScrollRequested .Where(x => !_isSettingScrollPosition) .Select(request => new ScrollRequest(ScrollReason.User, request.PageSize, request.FirstIndex)); var scroller = scrollSelected.Merge(scrollUser) .ObserveOn(schedulerProvider.Background) .DistinctUntilChanged(); var lineScroller = new LineScroller(lineProvider, scroller); Count = lineProvider.Select(lp=>lp.Count).ForBinding(); MaximumChars = lineScroller.MaximumLines() .ObserveOn(schedulerProvider.MainThread) .ForBinding(); var proxyFactory = new LineProxyFactory(new TextFormatter(args.SearchMetadataCollection), new LineMatches(args.SearchMetadataCollection), horizonalScrollArgs.DistinctUntilChanged(), themeProvider); //load lines into observable collection var loader = lineScroller.Lines.Connect() .Transform(proxyFactory.Create,new ParallelisationOptions(ParallelType.Ordered,3)) .Sort(SortExpressionComparer<LineProxy>.Ascending(proxy => proxy)) .ObserveOn(schedulerProvider.MainThread) .Bind(out _data) .DisposeMany() .LogErrors(logger) .Subscribe(); // track first visible index [required to set scroll extent] 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 => { try { _isSettingScrollPosition = true; FirstIndex = first; } finally { _isSettingScrollPosition = false; } }); _cleanUp = new CompositeDisposable(lineScroller, loader, Count, firstIndexMonitor, SelectionMonitor, MaximumChars, horizonalScrollArgs.SetAsComplete(), _userScrollRequested.SetAsComplete()); }
public InlineViewer([NotNull] InlineViewerArgs args, [NotNull] IClipboardHandler clipboardHandler, [NotNull] ISchedulerProvider schedulerProvider, [NotNull] ISelectionMonitor selectionMonitor) { if (args == null) throw new ArgumentNullException(nameof(args)); if (clipboardHandler == null) throw new ArgumentNullException(nameof(clipboardHandler)); if (schedulerProvider == null) throw new ArgumentNullException(nameof(schedulerProvider)); if (selectionMonitor == null) throw new ArgumentNullException(nameof(selectionMonitor)); SelectionMonitor = selectionMonitor; CopyToClipboardCommand = new Command(() => clipboardHandler.WriteToClipboard(selectionMonitor.GetSelectedText())); _isSettingScrollPosition = false; var lineProvider = args.LineProvider; var selectedChanged = args.SelectedChanged; var pageSize = this.WhenValueChanged(vm=>vm.PageSize); var scrollSelected = selectedChanged.Where(proxy => proxy != null) .CombineLatest(lineProvider, pageSize,(proxy, lp,pge) => new ScrollRequest(pge, proxy.Start)); var scrollUser = _userScrollRequested .Where(x=>!_isSettingScrollPosition) .Select(request => new ScrollRequest(ScrollReason.User, request.PageSize, request.FirstIndex)); var scroller= scrollSelected.Merge(scrollUser) .ObserveOn(schedulerProvider.Background) .DistinctUntilChanged(); var lineScroller = new LineScroller(lineProvider, scroller); Count = lineProvider.Select(lp=>lp.Count).ForBinding(); //load lines into observable collection var loader = lineScroller.Lines.Connect() .Transform(args.LineProxyFactory.Create) .Sort(SortExpressionComparer<LineProxy>.Ascending(proxy => proxy)) .ObserveOn(schedulerProvider.MainThread) .Bind(out _data) .DisposeMany() .Subscribe(); // track first visible index[required to set scroll extent] 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 => { try { _isSettingScrollPosition = true; FirstIndex = first; } finally { _isSettingScrollPosition = false; } }); _cleanUp = new CompositeDisposable(lineScroller, loader, Count, firstIndexMonitor, SelectionMonitor, Disposable.Create(() => { _userScrollRequested.OnCompleted(); })); }
public TailViewModel([NotNull] ILogger logger, [NotNull] ISchedulerProvider schedulerProvider, [NotNull] IFileWatcher fileWatcher, [NotNull] ISelectionMonitor selectionMonitor, [NotNull] IClipboardHandler clipboardHandler, [NotNull] ISearchInfoCollection searchInfoCollection, [NotNull] IInlineViewerFactory inlineViewerFactory, [NotNull] GeneralOptionBindings generalOptionBindings, [NotNull] ICombinedSearchMetadataCollection combinedSearchMetadataCollection, [NotNull] IStateBucketService stateBucketService, [NotNull] ITailViewStateRestorer restorer, [NotNull] SearchHints searchHints, [NotNull] ITailViewStateControllerFactory tailViewStateControllerFactory, [NotNull] IThemeProvider themeProvider, [NotNull] SearchCollection searchCollection, [NotNull] ITextFormatter textFormatter, [NotNull] ILineMatches lineMatches, [NotNull] IObjectProvider objectProvider, [NotNull] IDialogCoordinator dialogCoordinator) { 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 (stateBucketService == null) throw new ArgumentNullException(nameof(stateBucketService)); if (searchHints == null) throw new ArgumentNullException(nameof(searchHints)); if (themeProvider == null) throw new ArgumentNullException(nameof(themeProvider)); if (searchCollection == null) throw new ArgumentNullException(nameof(searchCollection)); if (textFormatter == null) throw new ArgumentNullException(nameof(textFormatter)); if (lineMatches == null) throw new ArgumentNullException(nameof(lineMatches)); if (objectProvider == null) throw new ArgumentNullException(nameof(objectProvider)); if (dialogCoordinator == null) throw new ArgumentNullException(nameof(dialogCoordinator)); if (combinedSearchMetadataCollection == null) throw new ArgumentNullException(nameof(combinedSearchMetadataCollection)); Name = fileWatcher.FullName; SelectionMonitor = selectionMonitor; GeneralOptionBindings = generalOptionBindings; SearchHints = searchHints; CopyToClipboardCommand = new Command(() => clipboardHandler.WriteToClipboard(selectionMonitor.GetSelectedText())); OpenFileCommand = new Command(() => Process.Start(fileWatcher.FullName)); OpenFolderCommand = new Command(() => Process.Start(fileWatcher.Folder)); CopyPathToClipboardCommand = new Command(() => clipboardHandler.WriteToClipboard(fileWatcher.FullName)); UnClearCommand = new Command(fileWatcher.Reset); ClearCommand = new Command(fileWatcher.Clear); KeyAutoTail = new Command(() => { AutoTail = true; }); OpenSearchOptionsCommand = new Command(async () => { await Task.Run(() => { var content = objectProvider.Get<SearchOptionsViewModel>(new Argument<ICombinedSearchMetadataCollection>(combinedSearchMetadataCollection)); dialogCoordinator.Show(this, content, x => content.Dispose()); }); }); SearchCollection = searchCollection; SearchMetadataCollection = combinedSearchMetadataCollection.Local; var horizonalScrollArgs = new ReplaySubject<TextScrollInfo>(1); HorizonalScrollChanged = args => horizonalScrollArgs.OnNext(args); _tailViewStateControllerFactory = tailViewStateControllerFactory; //this deals with state when loading the system at start up and at shut-down _persister = new TailViewPersister(this, restorer); FileStatus = fileWatcher.Status.ForBinding(); //command to add the current search to the tail collection var searchInvoker = SearchHints.SearchRequested.Subscribe(request => searchInfoCollection.Add(request.Text, request.UseRegEx)); //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); }) .Do(x => logger.Info("Scrolling to {0}/{1}", x.FirstIndex, x.PageSize)) .DistinctUntilChanged(); //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 selectedProvider = SearchCollection.Latest.ObserveOn(schedulerProvider.Background); var lineScroller = new LineScroller(selectedProvider, scroller); MaximumChars = lineScroller.MaximumLines() .ObserveOn(schedulerProvider.MainThread) .ForBinding(); var lineProxyFactory = new LineProxyFactory(textFormatter, lineMatches, horizonalScrollArgs.DistinctUntilChanged(), themeProvider); var loader = lineScroller.Lines.Connect() .LogChanges(logger, "Received") .Transform(lineProxyFactory.Create, new ParallelisationOptions(ParallelType.Ordered,5)) .LogChanges(logger, "Sorting") .Sort(SortExpressionComparer<LineProxy>.Ascending(proxy => proxy)) .ObserveOn(schedulerProvider.MainThread) .Bind(out _data,100) .LogChanges(logger, "Bound") .DisposeMany() .LogErrors(logger) .Subscribe(); //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(25)).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 showInline = this.WhenValueChanged(vm => vm.ShowInline); var inlineViewerVisible = isUserDefinedChanged.CombineLatest(showInline, (userDefined, showInlne) => userDefined && showInlne); 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 : EmptyLineProvider.Instance); InlineViewer = inlineViewerFactory. Create(combinedSearchMetadataCollection, inline, this.WhenValueChanged(vm => vm.SelectedItem)); _cleanUp = new CompositeDisposable(lineScroller, loader, firstIndexMonitor, FileStatus, Count, LatestCount, FileSizeText, CanViewInline, InlineViewer, InlineViewerVisible, SearchCollection, searchInfoCollection, searchHints, combinedSearchMetadataCollection, SelectionMonitor, Disposable.Create(dialogCoordinator.Close), searchInvoker, MaximumChars, _stateMonitor, combinedSearchMetadataCollection, horizonalScrollArgs.SetAsComplete(), _userScrollRequested.SetAsComplete()); }
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] ISearchMetadataCollection searchMetadataCollection, [NotNull] SearchOptionsViewModel searchOptionsViewModel, [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 (searchMetadataCollection == null) throw new ArgumentNullException(nameof(searchMetadataCollection)); if (searchOptionsViewModel == null) throw new ArgumentNullException(nameof(searchOptionsViewModel)); if (searchHints == null) throw new ArgumentNullException(nameof(searchHints)); 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 AddSearchCommand = new Command(() => { var text = SearchHints.SearchText; searchInfoCollection.Add(text); 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 = SearchHints.WhenValueChanged(vm => vm.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(); //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)); var loader = lineScroller.Lines.Connect() .Transform(x=>lineProxyFactory.Create(x), 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 = 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, SearchHint, SelectedItemsCount, CanViewInline, InlineViewer, InlineViewerVisible, SearchCollection, searchInfoCollection, HighlightTail, UsingDarkTheme, searchHints, searchMetadataCollection, searchMetadataCollection, SelectionMonitor, SearchOptions, Disposable.Create(_userScrollRequested.OnCompleted) ); }
public TailViewModel([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)); _stateProvider = new TailViewPersister(this); Name = fileWatcher.FullName; SelectionMonitor = selectionMonitor; SearchOptions = searchOptionsViewModel; SearchHints = searchHints; CopyToClipboardCommand = new Command(()=> clipboardHandler.WriteToClipboard(selectionMonitor.GetSelectedText())); 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); }) .Do(x=>logger.Info("Scrolling to {0}/{1}", x.FirstIndex,x.PageSize)) .DistinctUntilChanged(); //IsLoading = searchInfoCollection.All.Take(1).Select(_ => false).StartWith(true).ForBinding(); FileStatus = fileWatcher.Status.Throttle(TimeSpan.FromMilliseconds(250)).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() .RecordChanges(logger, "Received") .Transform(lineProxyFactory.Create, new ParallelisationOptions(ParallelType.Ordered, 3)) .Sort(SortExpressionComparer<LineProxy>.Ascending(proxy => proxy)) .ObserveOn(schedulerProvider.MainThread) .Bind(out _data,100) .DisposeMany() .RecordErrors(logger) .Subscribe(); //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(25)).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, FileStatus, Count, LatestCount, FileSizeText, CanViewInline, InlineViewer, InlineViewerVisible, SearchCollection, searchInfoCollection, HighlightTail, UsingDarkTheme, searchHints, searchMetadataCollection, searchMetadataCollection, SelectionMonitor, SearchOptions, searchInvoker, _userScrollRequested.SetAsComplete()); }
public WindowViewModel(IObjectProvider objectProvider, IWindowFactory windowFactory, ILogger logger, IWindowsController windowsController, RecentFilesViewModel recentFilesViewModel, GeneralOptionsViewModel generalOptionsViewModel, ISchedulerProvider schedulerProvider, IApplicationStatePublisher applicationStatePublisher) { _logger = logger; _windowsController = windowsController; RecentFiles = recentFilesViewModel; GeneralOptions = generalOptionsViewModel; _schedulerProvider = schedulerProvider; _objectProvider = objectProvider; InterTabClient = new InterTabClient(windowFactory); OpenFileCommand = new Command(OpenFile); ShowInGitHubCommand = new Command(()=> Process.Start("https://github.com/RolandPheasant")); ZoomOutCommand= new Command(()=> { GeneralOptions.Scale = GeneralOptions.Scale + 5; }); ZoomInCommand = new Command(() => { GeneralOptions.Scale = GeneralOptions.Scale - 5; }); CollectMemoryCommand = new Command(() => { //Diagnostics [useful for memory testing] GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); }); ExitCommmand = new Command(() => { applicationStatePublisher.Publish(ApplicationState.ShuttingDown); Application.Current.Shutdown(); }); WindowExiting = () => { applicationStatePublisher.Publish(ApplicationState.ShuttingDown); }; Version = $"v{Assembly.GetEntryAssembly().GetName().Version.ToString(3)}"; var fileDropped = DropMonitor.Dropped.Subscribe(OpenFile); var isEmptyChecker = Views.ToObservableChangeSet() .ToCollection() .Select(items=>items.Count) .StartWith(0) .Select(count=>count==0) .LogErrors(logger) .Subscribe(isEmpty=> IsEmpty = isEmpty); var openRecent = recentFilesViewModel.OpenFileRequest .LogErrors(logger) .Subscribe(file => { MenuIsOpen = false; OpenFile(file); }); var selectedChange = this.WhenValueChanged(vm => vm.Selected) .Subscribe(selected => { var currentSelection = selected?.Content as ISelectedAware; Views.Where(hv=> !hv.Equals(selected)) .Select(hv => hv.Content) .OfType<ISelectedAware>() .ForEach(selectedAware => selectedAware.IsSelected = false); if (currentSelection != null) currentSelection.IsSelected = true; }); _cleanUp = new CompositeDisposable(recentFilesViewModel, isEmptyChecker, fileDropped, DropMonitor, openRecent, selectedChange, Disposable.Create(() => { Views.Select(vc => vc.Content) .OfType<IDisposable>() .ForEach(d=>d.Dispose()); })); }