/* Function: RemoveDuplicateTopics * Removes topics from the results which have the same letter for letter display names. An exception is made if * they have different languages. */ protected void RemoveDuplicateTopics(SearchIndex.Entries.Keyword keywordEntry) { List <SearchIndex.Entries.Topic> topicEntries = keywordEntry.TopicEntries; for (int i = 1; i < topicEntries.Count; /* no auto-increment */) { var current = topicEntries[i]; var previous = topicEntries[i - 1]; if (current.DisplayName == previous.DisplayName && current.WrappedTopic.LanguageID == previous.WrappedTopic.LanguageID) { topicEntries.RemoveAt(i); } else { i++; } } }
/* Function: AppendKeyword * Appends the keyword entry as a JSON array. */ protected void AppendKeyword(SearchIndex.Entries.Keyword keywordEntry, StringBuilder output) { bool addWhitespace = !EngineInstance.Config.ShrinkFiles; if (addWhitespace) { output.Append("\n\n "); } string keywordHTMLName = keywordEntry.DisplayName.ToHTML(); string keywordSearchText = keywordEntry.SearchText; output.Append("[\""); output.StringEscapeAndAppend(keywordHTMLName); output.Append("\","); if (keywordSearchText != keywordHTMLName.ToLower()) { output.Append('"'); output.StringEscapeAndAppend(keywordSearchText); output.Append('"'); } // Otherwise leave an empty spot before the comma. We don't have to write out "undefined". output.Append(",["); for (int i = 0; i < keywordEntry.TopicEntries.Count; i++) { if (i > 0) { output.Append(','); } if (addWhitespace) { output.Append("\n "); } var topicEntry = keywordEntry.TopicEntries[i]; bool includeLanguage = false; if (i < keywordEntry.TopicEntries.Count - 1) { var other = keywordEntry.TopicEntries[i + 1]; if (topicEntry.DisplayName == other.DisplayName && topicEntry.WrappedTopic.LanguageID != other.WrappedTopic.LanguageID) { includeLanguage = true; } } if (i > 0 && !includeLanguage) { var other = keywordEntry.TopicEntries[i - 1]; if (topicEntry.DisplayName == other.DisplayName && topicEntry.WrappedTopic.LanguageID != other.WrappedTopic.LanguageID) { includeLanguage = true; } } AppendTopic(topicEntry, keywordHTMLName, includeLanguage, output); } if (addWhitespace) { output.Append("\n "); } output.Append("]]"); }
/* Function: SortTopicEntries */ protected void SortTopicEntries(SearchIndex.Entries.Keyword keywordEntry) { List <SearchIndex.Entries.Topic> topicEntries = keywordEntry.TopicEntries; topicEntries.Sort( delegate(SearchIndex.Entries.Topic a, SearchIndex.Entries.Topic b) { // Sort by the non-qualifier part first since they'll be displayed in "Name, Class" format. The below code is just // an elaborate way of doing that without allocating intermediate strings for each comparison. int aNonQualifierLength = a.DisplayName.Length - a.EndOfDisplayNameQualifiers; int bNonQualifierLength = b.DisplayName.Length - b.EndOfDisplayNameQualifiers; int shorterNonQualifierLength = (aNonQualifierLength < bNonQualifierLength ? aNonQualifierLength : bNonQualifierLength); // Compare non-qualifiers in a case-insensitive way first. int result = string.Compare(a.DisplayName, a.EndOfDisplayNameQualifiers, b.DisplayName, b.EndOfDisplayNameQualifiers, shorterNonQualifierLength, true); if (result != 0) { return(result); } result = (aNonQualifierLength - bNonQualifierLength); if (result != 0) { return(result); } // Before comparing in a case-sensitive way, compare based on hierarchy membership. We want class "Token" // to appear before variable "token" even though normally we want lowercase to go first. var aCommentType = EngineInstance.CommentTypes.FromID(a.WrappedTopic.CommentTypeID); var bCommentType = EngineInstance.CommentTypes.FromID(b.WrappedTopic.CommentTypeID); if (aCommentType.InClassHierarchy != bCommentType.InClassHierarchy) { return(aCommentType.InClassHierarchy ? -1 : 1); } if (aCommentType.InDatabaseHierarchy != bCommentType.InDatabaseHierarchy) { return(aCommentType.InDatabaseHierarchy ? -1 : 1); } // Still equal, now compare the qualifiers in a case-sensitive way to break ties. result = string.Compare(a.DisplayName, a.EndOfDisplayNameQualifiers, b.DisplayName, b.EndOfDisplayNameQualifiers, shorterNonQualifierLength, false); if (result != 0) { return(result); } int shorterQualifierLength = (a.EndOfDisplayNameQualifiers < b.EndOfDisplayNameQualifiers ? a.EndOfDisplayNameQualifiers : b.EndOfDisplayNameQualifiers); // Still equal so do a case-insensitive comparison of qualifiers, so "Name, ClassA" comes before "Name, ClassB". result = string.Compare(a.DisplayName, 0, b.DisplayName, 0, shorterQualifierLength, true); if (result != 0) { return(result); } result = (a.EndOfDisplayNameQualifiers - b.EndOfDisplayNameQualifiers); if (result != 0) { return(result); } // Case-sensitive comparison of qualifiers to break ties. result = string.Compare(a.DisplayName, 0, b.DisplayName, 0, shorterQualifierLength, false); if (result != 0) { return(result); } // So now we have two symbols that are equal letter for letter. Sort by language name first. if (a.WrappedTopic.LanguageID != b.WrappedTopic.LanguageID) { return(string.Compare(EngineInstance.Languages.FromID(a.WrappedTopic.LanguageID).Name, EngineInstance.Languages.FromID(b.WrappedTopic.LanguageID).Name, true)); } // and by file name next. if (a.WrappedTopic.FileID != b.WrappedTopic.FileID) { return(string.Compare(EngineInstance.Files.FromID(a.WrappedTopic.FileID).FileName, EngineInstance.Files.FromID(b.WrappedTopic.FileID).FileName, true)); } // If we're here then they're two overloaded functions in the same source file. Go by symbol definition number. return(a.WrappedTopic.SymbolDefinitionNumber - b.WrappedTopic.SymbolDefinitionNumber); } ); // We're not done yet. Now reorder the sorted list so that topics that begin with the keyword appear before those that // don't, even if they're otherwise ahead of it in the sort order. Right now we have this: // // Thread // - Not Thread Safe // - thread // - Thread // - Thread Safe // - Thread Safety Notes // - Window Threads // // but most of the time someone typing in "thread" will want the Thread class, so convert it into this: // // Thread // - thread // - Thread // - Thread Safe // - Thread Safety Notes // - Not Thread Safe // - Window Threads int firstKeywordStart = -1; for (int i = 0; i < topicEntries.Count; i++) { int keywordIndex = topicEntries[i].SearchText.IndexOf(keywordEntry.SearchText, topicEntries[i].EndOfSearchTextQualifiers); if (keywordIndex == topicEntries[i].EndOfSearchTextQualifiers) { firstKeywordStart = i; break; } } // If the first one already starts with the keyword, or none of them do, there's nothing we need to do. if (firstKeywordStart > 0) { int endOfKeywordStart = topicEntries.Count; for (int i = topicEntries.Count - 1; i >= 0; i--) { int keywordIndex = topicEntries[i].SearchText.IndexOf(keywordEntry.SearchText, topicEntries[i].EndOfSearchTextQualifiers); if (keywordIndex == topicEntries[i].EndOfSearchTextQualifiers) { break; } else { endOfKeywordStart = i; } } var tempList = topicEntries.GetRange(0, firstKeywordStart); topicEntries.RemoveRange(0, firstKeywordStart); // end - first because it shifted down after we removed the range topicEntries.InsertRange(endOfKeywordStart - firstKeywordStart, tempList); } }