示例#1
0
        public void OnDeleteTopic(Engine.Topics.Topic topic, IDObjects.NumberSet linksAffected, CodeDB.EventAccessor eventAccessor)
        {
            if (!IncludeInIndex(topic))
            {
                return;
            }

            var entry = new SearchIndex.Entries.Topic(topic, this);

            accessLock.EnterWriteLock();

            try
            {
                foreach (string keyword in entry.Keywords)
                {
                    string prefix    = KeywordPrefix(keyword);
                    var    numberSet = prefixTopicIDs[prefix];

                    if (numberSet != null)
                    {
                        numberSet.Remove(topic.TopicID);

                        if (numberSet.IsEmpty)
                        {
                            prefixTopicIDs.Remove(prefix);

                            foreach (var changeWatcher in changeWatchers)
                            {
                                changeWatcher.OnDeletePrefix(prefix, eventAccessor);
                            }
                        }
                        else
                        {
                            foreach (var changeWatcher in changeWatchers)
                            {
                                changeWatcher.OnUpdatePrefix(prefix, eventAccessor);
                            }
                        }
                    }
                }
            }
            finally
            { accessLock.ExitWriteLock(); }
        }
示例#2
0
        // Group: CodeDB.IChangeWatcher Functions
        // __________________________________________________________________________


        public void OnAddTopic(Engine.Topics.Topic topic, CodeDB.EventAccessor eventAccessor)
        {
            if (!IncludeInIndex(topic))
            {
                return;
            }

            var entry = new SearchIndex.Entries.Topic(topic, this);

            accessLock.EnterWriteLock();

            try
            {
                foreach (string keyword in entry.Keywords)
                {
                    string prefix   = KeywordPrefix(keyword);
                    var    topicIDs = prefixTopicIDs[prefix];

                    if (topicIDs == null)
                    {
                        topicIDs = new IDObjects.NumberSet();
                        topicIDs.Add(topic.TopicID);
                        prefixTopicIDs[prefix] = topicIDs;

                        foreach (var changeWatcher in changeWatchers)
                        {
                            changeWatcher.OnAddPrefix(prefix, eventAccessor);
                        }
                    }
                    else
                    {
                        topicIDs.Add(topic.TopicID);

                        foreach (var changeWatcher in changeWatchers)
                        {
                            changeWatcher.OnUpdatePrefix(prefix, eventAccessor);
                        }
                    }
                }
            }
            finally
            { accessLock.ExitWriteLock(); }
        }
示例#3
0
        /* Function: IncludeInIndex
         * Whether the passed <Engine.Topics.Topic> should be included in the search index.
         */
        protected bool IncludeInIndex(Engine.Topics.Topic topic)
        {
            var commentType = EngineInstance.CommentTypes.FromID(topic.CommentTypeID);

            if (commentType.ID == EngineInstance.CommentTypes.IDFromKeyword("group"))
            {
                return(false);
            }

            // If it's a code topic and it ends with a duplicate it's most likely a constructor.  Don't index it since the user almost
            // certainly want the class and this just pollutes the results by forcing them under a keyword heading.
            else if (commentType.Flags.Code && commentType.Flags.ClassHierarchy == false && topic.Symbol.EndsWithDuplicate())
            {
                return(false);
            }

            else
            {
                return(true);
            }
        }
示例#4
0
        /* Function: HashPath
         *
         * Returns the hash path for the topic.  When appending this to the hash path of a file or class use a colon to separate
         * them.
         *
         * Examples:
         *
         *		topic - Member
         *		topic + includeClass - Module.Module.Member
         */
        static public string HashPath(Engine.Topics.Topic topic, bool includeClass = true)
        {
            // We want to work from Topic.Title instead of Topic.Symbol so that we can use the separator characters as originally
            // written, as opposed to having them normalized and condensed in the anchor.

            int titleParametersIndex = ParameterString.GetParametersIndex(topic.Title);

            StringBuilder hashPath;

            if (titleParametersIndex == -1)
            {
                hashPath = new StringBuilder(topic.Title);
            }
            else
            {
                hashPath = new StringBuilder(titleParametersIndex);
                hashPath.Append(topic.Title, 0, titleParametersIndex);
            }

            hashPath.Replace('\t', ' ');

            // Remove all whitespace unless it separates two text characters.
            int i = 0;

            while (i < hashPath.Length)
            {
                if (hashPath[i] == ' ')
                {
                    if (i == 0 || i == hashPath.Length - 1)
                    {
                        hashPath.Remove(i, 1);
                    }
                    else if (Tokenizer.FundamentalTypeOf(hashPath[i - 1]) == FundamentalType.Text &&
                             Tokenizer.FundamentalTypeOf(hashPath[i + 1]) == FundamentalType.Text)
                    {
                        i++;
                    }
                    else
                    {
                        hashPath.Remove(i, 1);
                    }
                }
                else
                {
                    i++;
                }
            }

            // Add parentheses to distinguish between multiple symbols in the same file.
            // xxx this will be a problem when doing class hash paths as symboldefnumber is only unique to a file
            if (topic.SymbolDefinitionNumber != 1)
            {
                hashPath.Append('(');
                hashPath.Append(topic.SymbolDefinitionNumber);
                hashPath.Append(')');
            }

            // Add class if present and desired.
            // xxx when class id is included in topic test for that here, maybe instead of having a flag
            if (includeClass)
            {
                // Find the part of the symbol that isn't generated by the title, if any.
                string ignore;
                string titleSymbol = SymbolString.FromPlainText(topic.Title, out ignore).ToString();
                string fullSymbol  = topic.Symbol.ToString();

                if (titleSymbol.Length < fullSymbol.Length &&
                    fullSymbol.Substring(fullSymbol.Length - titleSymbol.Length) == titleSymbol)
                {
                    string classSymbol = fullSymbol.Substring(0, fullSymbol.Length - titleSymbol.Length);
                    classSymbol = classSymbol.Replace(SymbolString.SeparatorChar, '.');

                    // The class symbol should already have a trailing member operator.
                    hashPath.Insert(0, classSymbol);
                }
            }

            return(Utilities.Sanitize(hashPath.ToString()));
        }
示例#5
0
        // Group: Functions
        // __________________________________________________________________________


        public Topic(Engine.Topics.Topic topic, SearchIndex.Manager searchIndex) : base()
        {
            wrappedTopic = topic;

            var commentType = searchIndex.EngineInstance.CommentTypes.FromID(topic.CommentTypeID);
            var language    = searchIndex.EngineInstance.Languages.FromID(topic.LanguageID);

            // It's possible for language to be null if we're creating this to handle an OnDeleteTopic event for a language that
            // was deleted.
            var memberOperator = (language != null ? language.MemberOperator : ".");


            // Get the title without any parameters.  We don't want to include parameters in the index.  Multiple functions that
            // differ only by parameter will be treated as one entry.

            string title, ignore;

            ParameterString.SplitFromParameters(topic.Title, out title, out ignore);
            title = title.TrimEnd();


            // Figure out the extra scope text that should be added to the title to make it a fully resolved symbol.  We do this by
            // comparing the symbol from the topic to one generated from the title.  We don't just use the symbol to begin with
            // because we want to show the title as written; there's some normalization that occurs when generating symbols
            // that we want to bypass.

            string extraScope = null;

            SymbolString titleSymbol = SymbolString.FromPlainText_NoParameters(title);

            string titleSymbolString = titleSymbol.FormatWithSeparator(memberOperator);
            string symbolString      = topic.Symbol.FormatWithSeparator(memberOperator);

            if (symbolString.Length > titleSymbolString.Length)
            {
                // We have to go by LastIndexOf rather than EndsWith because "operator<string>" will have "<string>" cut off as a parameter.
                // We also have to go by LastIndexOf instead of IndexOf so constructors don't get cut off (Package.Class.Class).
                int titleIndex = symbolString.LastIndexOf(titleSymbolString);

                                #if DEBUG
                if (titleIndex == -1)
                {
                    throw new Exception("Title symbol string \"" + titleSymbolString + "\" isn't part of symbol string \"" + symbolString + "\" which " +
                                        "was assumed when creating a search index entry.");
                }
                                #endif

                extraScope = symbolString.Substring(0, titleIndex);
            }


            // Remove the space in "operator <".  This prevents them from appearing as two keywords, and also makes sure "operator <" and
            // "operator<" are always displayed consistently, which will be important for sorting.

            title = SpaceAfterOperatorKeywordRegex.Replace(title, "");


            displayName = (extraScope == null ? title : extraScope + title);
            searchText  = Normalize(displayName);

            if (commentType.IsFile)
            {
                endOfDisplayNameQualifiers = FindEndOfQualifiers(displayName, FileSplitSymbolsRegex.Matches(displayName));
                endOfSearchTextQualifiers  = FindEndOfQualifiers(searchText, FileSplitSymbolsRegex.Matches(searchText));
            }
            else if (commentType.IsCode)
            {
                endOfDisplayNameQualifiers = FindEndOfQualifiers(displayName, CodeSplitSymbolsRegex.Matches(displayName));
                endOfSearchTextQualifiers  = FindEndOfQualifiers(searchText, CodeSplitSymbolsRegex.Matches(searchText));
            }
            else             // documentation topic
            {
                if (extraScope == null)
                {
                    endOfDisplayNameQualifiers = 0;
                    endOfSearchTextQualifiers  = 0;
                }
                else
                {
                    endOfDisplayNameQualifiers = extraScope.Length;

                    // Don't need +1 because only leading separators are removed.  The trailing separator will still be there.
                    endOfSearchTextQualifiers = Normalize(extraScope).Length;
                }
            }

            keywords = new List <string>();

            if (endOfDisplayNameQualifiers == 0)
            {
                AddKeywords(displayName, isDocumentation: commentType.IsDocumentation);
            }
            else
            {
                AddKeywords(displayName.Substring(endOfDisplayNameQualifiers), isDocumentation: commentType.IsDocumentation);
            }
        }
示例#6
0
        public void OnUpdateTopic(Engine.Topics.Topic oldTopic, Engine.Topics.Topic newTopic, Engine.Topics.Topic.ChangeFlags changeFlags,
                                  CodeDB.EventAccessor eventAccessor)
        {
                        #if DEBUG
            if (IncludeInIndex(newTopic) != IncludeInIndex(oldTopic))
            {
                throw new Exception("SearchIndex incorrectly assumes IncludeInIndex() will be the same for both the old and new topics in OnUpdateTopic().");
            }
                        #endif

            if (!IncludeInIndex(newTopic))
            {
                return;
            }

            if ((changeFlags & (Engine.Topics.Topic.ChangeFlags.Title |
                                Engine.Topics.Topic.ChangeFlags.CommentTypeID |
                                Engine.Topics.Topic.ChangeFlags.SymbolDefinitonNumber |
                                Engine.Topics.Topic.ChangeFlags.Symbol |
                                Engine.Topics.Topic.ChangeFlags.LanguageID |
                                Engine.Topics.Topic.ChangeFlags.FileID |
                                Engine.Topics.Topic.ChangeFlags.EffectiveAccessLevel |
                                Engine.Topics.Topic.ChangeFlags.Class)) == 0)
            {
                return;
            }


            // We assume that if the topics are similar enough to use OnUpdateTopic() instead of OnAdd/RemoveTopic() then they'll generate the exact
            // same keyword list, and they'll even be in the same order.  This allows for a nice optimization here, but test it in debug builds in case these
            // assumptions are wrong in the future.

                        #if DEBUG
            if (newTopic.TopicID != oldTopic.TopicID)
            {
                throw new Exception("SearchIndex incorrectly assumes both the old and new topics in OnUpdateTopic() have the same topic IDs.");
            }

            var newEntry = new SearchIndex.Entries.Topic(newTopic, this);
            var oldEntry = new SearchIndex.Entries.Topic(oldTopic, this);

            if (newEntry.Keywords.Count != oldEntry.Keywords.Count)
            {
                throw new Exception("SearchIndex incorrectly assumes both the old and new topics in OnUpdateTopic() have the same keywords.");
            }

            for (int i = 0; i < newEntry.Keywords.Count; i++)
            {
                if (newEntry.Keywords[i] != oldEntry.Keywords[i])
                {
                    throw new Exception("SearchIndex incorrectly assumes both the old and new topics in OnUpdateTopic() have the same keywords.");
                }
            }
                        #endif

            var entry = new SearchIndex.Entries.Topic(newTopic, this);

            // We use upgradeable in case one of the change handlers needs to do something that requires a write lock.
            accessLock.EnterUpgradeableReadLock();

            try
            {
                foreach (var keyword in entry.Keywords)
                {
                    string prefix = KeywordPrefix(keyword);

                    foreach (var changeWatcher in changeWatchers)
                    {
                        changeWatcher.OnUpdatePrefix(prefix, eventAccessor);
                    }
                }
            }
            finally
            {
                // We don't have to test to see if it was upgraded because if it was the write lock should have been released
                // by the change handler and we should recursively be back to an upgradeable read lock.
                accessLock.ExitUpgradeableReadLock();
            }
        }