예제 #1
0
        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();
                                         });
        }
예제 #2
0
        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);
        }
예제 #3
0
        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));
        }
예제 #4
0
        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());
                }));
        }
예제 #5
0
        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);
        }
예제 #6
0
        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());
        }
예제 #7
0
        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);
        }
예제 #9
0
        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());
        }
예제 #10
0
        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();
                        }));
        }
예제 #11
0
        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());
        }
예제 #12
0
        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)
                );
        }
예제 #13
0
        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());
        }
예제 #14
0
        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());
                }));
        }