Example #1
0
        protected override void GetClassificationSpans(List <ClassificationSpan> result, SnapshotSpan span, Options options)
        {
            bool showFileNames = options.HighlightFindResultsFileNames;
            bool showMatches   = options.HighlightFindResultsMatches;
            bool showDetails   = options.HighlightFindResultsDetails;

            if (showFileNames || showMatches || showDetails)
            {
                foreach (ITextSnapshotLine line in GetSpanLines(span))
                {
                    string text = line.GetText();
                    if (!string.IsNullOrEmpty(text))
                    {
                        // The first line in the window always contains the Find arguments, so we parse and highlight it specially.
                        bool firstLine = line.LineNumber == 0;
                        if (firstLine)
                        {
                            this.findArgs = FindArgs.TryParse(text);
                        }

                        // If we couldn't parse the find args (on this or an earlier call), then we don't need to iterate through the rest of the lines.
                        if (this.findArgs == null)
                        {
                            break;
                        }

                        if (firstLine)
                        {
                            if (showMatches)
                            {
                                AddClassificationSpan(result, line, this.findArgs.PatternIndex, this.findArgs.PatternLength, matchType);
                            }
                        }
                        else
                        {
                            this.HighlightResultLine(result, line, text, showFileNames, showMatches, showDetails);
                        }
                    }
                }
            }
        }
Example #2
0
            public static FindArgs TryParse(string text)
            {
                FindArgs result = null;

                // Example line to parse with all Find args enabled:
                // Find all "X", Match case, Whole word, Regular expressions, Subfolders, Keep modified files open, List filenames only, Find Results 1, "C:\...
                const string FindLinePrefix    = "Find all \"";
                const string ReplaceLinePrefix = "Replace all \"";
                string       linePrefix        = text.StartsWith(FindLinePrefix) ? FindLinePrefix : text.StartsWith(ReplaceLinePrefix) ? ReplaceLinePrefix : null;

                if (!string.IsNullOrEmpty(linePrefix))
                {
                    // The Find Results header line contains an unescaped search term/expression, which can be a problem
                    // if it contains commas, double quotes, or text that also appears as one of the options.  To try to make
                    // parsing as reliable as possible, we'll validate the line start, and then we'll work backward through the
                    // known arg terms until we find the earliest one present.  Then the unescaped search term/expression
                    // should be in double quotes immediately before that arg.
                    int   findResults        = text.LastIndexOf(", Find Results ");              // This should always be present.
                    int   listFileNamesOnly  = text.LastIndexOf(", List filenames only, ");
                    int   keepOpen           = text.LastIndexOf(", Keep modified files open, "); // This is always present for Find; it's optional for Replace.
                    int   subfolders         = text.LastIndexOf(", Subfolders, ");
                    int   regularExpressions = text.LastIndexOf(", Regular expressions, ");
                    int   wholeWord          = text.LastIndexOf(", Whole word, ");
                    int   matchCase          = text.LastIndexOf(", Match case, ");
                    int   block = text.LastIndexOf(", Block, ");                   // When "Current Block (...)" is selected
                    int[] afterPatternChoices = new[]
                    {
                        findResults, listFileNamesOnly, keepOpen, subfolders, regularExpressions, wholeWord, matchCase, block,
                        int.MaxValue                                 // Include at least one value that always >= 0 since Min() requires that.
                    };
                    int afterPattern = afterPatternChoices.Where(index => index >= 0).Min();

                    // VS won't let you search for an empty string, and the pattern should always have a double quote added after it.
                    int patternIndex = linePrefix.Length;
                    if (afterPattern < int.MaxValue && afterPattern > (patternIndex + 1))
                    {
                        string pattern = text.Substring(patternIndex, afterPattern - (patternIndex + 1));

                        // For Replace All, the Find and Replace terms are both listed.  We only want the Replace term
                        // since it's all that we'll be able to highlight in the matched/replaced lines that are returned.
                        if (linePrefix == ReplaceLinePrefix)
                        {
                            // The Find and Replace terms are unescaped, so it's possible that one contains this separator.
                            // It's unlikely, but if it happens, then our highlights may be off or non-existent.
                            const string Separator      = "\", \"";
                            int          separatorIndex = pattern.IndexOf(Separator);
                            if (separatorIndex >= 0)
                            {
                                separatorIndex += Separator.Length;
                                pattern         = pattern.Substring(separatorIndex);
                                patternIndex   += separatorIndex;
                            }
                            else
                            {
                                // Something is wrong.  The Find and Replace terms weren't formatted like we expected.
                                pattern = null;
                            }
                        }

                        if (!string.IsNullOrEmpty(pattern))
                        {
                            result = new FindArgs();
                            result.ListFileNamesOnly = listFileNamesOnly >= 0;
                            result.PatternIndex      = patternIndex;
                            result.PatternLength     = pattern.Length;

                            try
                            {
                                if (regularExpressions < 0)
                                {
                                    pattern = Regex.Escape(pattern);

                                    // VS seems to only apply the "Whole word" option when "Regular expressions" isn't used, so
                                    // we'll do the same.  Otherwise, VS would return lines that we couldn't highlight a match in!
                                    if (wholeWord >= 0)
                                    {
                                        const string WholeWordBoundary = @"\b";
                                        pattern = WholeWordBoundary + pattern + WholeWordBoundary;
                                    }
                                }

                                // We don't want to spend too much time searching each line.
                                TimeSpan timeout = TimeSpan.FromMilliseconds(100);
                                result.MatchExpression = new Regex(pattern, matchCase >= 0 ? RegexOptions.None : RegexOptions.IgnoreCase, timeout);
                            }
                            catch (ArgumentException)
                            {
                                // We did our best to parse out the pattern and build a suitable regex.  But it's possible that the
                                // parsed pattern was wrong (e.g., if it contained an unescaped ", " substring).  So if Regex
                                // throws an ArgumentException, we just can't highlight this time.
                                result = null;
                            }
                        }
                    }
                }

                return(result);
            }
Example #3
0
            public static FindArgs TryParse(string text)
            {
                FindArgs result = null;

                // VS 2019 16.5 totally changed the Find Results window and options. Update 16.5.4 restored some functionality to its List View,
                // but now it truncates the pattern after 20 characters. It still doesn't escape patterns, so comma and double quote are ambiguous.
                Match match = FindAllPattern.Match(text);

                if (!match.Success)
                {
                    match = ReplaceAllPattern.Match(text);
                }

                if (match.Success && match.Groups.Count == 2)
                {
                    int afterMatchIndex    = match.Index + match.Value.Length;
                    int listFileNamesOnly  = text.IndexOf("List filenames only", afterMatchIndex, StringComparison.OrdinalIgnoreCase);
                    int regularExpressions = text.IndexOf("Regular expressions", afterMatchIndex, StringComparison.OrdinalIgnoreCase);
                    int wholeWord          = text.IndexOf("Whole word", afterMatchIndex, StringComparison.OrdinalIgnoreCase);
                    int matchCase          = text.IndexOf("Match case", afterMatchIndex, StringComparison.OrdinalIgnoreCase);

                    Group        group     = match.Groups[1];
                    string       pattern   = group.Value;
                    const string Ellipsis  = "...";
                    bool         truncated = false;
                    if (pattern.EndsWith(Ellipsis))
                    {
                        pattern   = pattern.Substring(0, pattern.Length - Ellipsis.Length);
                        truncated = true;
                    }

                    result = new FindArgs
                    {
                        ListFileNamesOnly = listFileNamesOnly >= 0,
                        PatternIndex      = group.Index,
                        PatternLength     = pattern.Length,
                    };

                    try
                    {
                        if (regularExpressions < 0)
                        {
                            pattern = Regex.Escape(pattern);

                            // VS seems to only apply the "Whole word" option when "Regular expressions" isn't used, so we'll do the same.
                            // We can't apply it at the end of a truncated pattern because the truncation might have occurred mid-word.
                            if (wholeWord >= 0)
                            {
                                const string WholeWordBoundary = @"\b";
                                pattern = WholeWordBoundary + pattern + (truncated ? string.Empty : WholeWordBoundary);
                            }
                        }

                        // We don't want to spend too much time searching each line.
                        TimeSpan timeout = TimeSpan.FromMilliseconds(100);
                        result.MatchExpression = new Regex(pattern, matchCase >= 0 ? RegexOptions.None : RegexOptions.IgnoreCase, timeout);
                    }
                    catch (ArgumentException)
                    {
                        // We did our best to parse out the pattern and build a suitable regex.  But it's possible that the
                        // parsed pattern was wrong (e.g., if it contained an unescaped ", " substring).  So if Regex
                        // throws an ArgumentException, we just can't highlight this time.
                        result = null;
                    }
                }

                return(result);
            }