private async void SearchTextBox_TextChanged(object sender, TextChangedEventArgs e)
        {
            // Skip default
            if (SearchTextBox.Text == "" || SearchTextBox.Text == App.DefaultTextboxText)
            {
                return;
            }

            // If InterfaceOption.ExtendedFunctions is enabled and we have selected web mode then don't do anything here
            if ((interfaceOption & InterfaceOption.ExtendedFunctions) == InterfaceOption.ExtendedFunctions && SearchModeComboBox.SelectedIndex == 1)
            {
                return;                                                                                                                                     // Don't do anything
            }
            // Do search depending on current setting
            Triplet <List <ClueFragment>, List <Document>, FormatIndicator> triplet = await SearchTextBox_TextChangedDoSeaerch();

            // Retrive return values
            List <ClueFragment> nextClues       = triplet.Key;
            List <Document>     foundDocuments  = triplet.Value;
            FormatIndicator     formatIndicator = triplet.Tag;

            // Set up return results
            ClueFragmentSelectionpane.ItemsSource = nextClues;
            DocumentSelectionPane.ItemsSource     = foundDocuments;

            // Validation: If ShowValidationSymbol is enabled then show whether we find anything
            if ((interfaceOption & InterfaceOption.ShowValidationSymbol) == InterfaceOption.ShowValidationSymbol)
            {
                if (foundDocuments != null && foundDocuments.Count > 0)
                {
                    ValidationSymbolText.Content = "✓";

                    // Also display format indicator of current input format in tooltip when it's a tick
                    switch (formatIndicator)
                    {
                    case FormatIndicator.ID:
                        ValidationSymbolText.ToolTip = "Perform a search using ID";
                        break;

                    case FormatIndicator.Constrained:
                        ValidationSymbolText.ToolTip = "Perform a search using constraints";
                        break;

                    case FormatIndicator.SimpleClue:
                        ValidationSymbolText.ToolTip = "Perform a search using simple clue format";
                        break;

                    case FormatIndicator.Ambiguous:
                        ValidationSymbolText.ToolTip = "Perform an ambiguous serach";
                        break;

                    case FormatIndicator.Initiation:
                        ValidationSymbolText.ToolTip = "Initializing a search";
                        break;

                    default:
                        ValidationSymbolText.ToolTip = "Unrecognized format";
                        break;
                    }
                }
                else
                {
                    ValidationSymbolText.Content = "!";
                }
            }

            // Show search popup
            SearchPopup.IsOpen = true;
        }
        /// <summary>
        /// Break an input for all three search modes (ExistingCluesOnly, AllClueCombinations, General) + all formats for general mode (ID, constraint, shorthand, ambiguous):
        ///  - Break input into clue segments, along with potential CA (without []) and other elements
        /// GUID form: ID0000[ContentElement]
        /// Constriant form: A-B-C;A-B;…#metaname#metaname@metavalue[ContentElement]"keyword"
        /// Shorthand clue form: A-B-C-clue or meta(value or name, can be partial) (where "clue or metavalue" part is stored as the last keyphrase, not as metavalues array)
        /// Ambiguous form: A B C
        /// </summary>
        /// <param name="input">User input, user might use [[ ]] and -- in search text to match []- in actual search target, which is not suggested to contain such but anyway</param>
        /// <param name="keyPhrases">Clue key phrases, the last one might indicuate a category or a target document meta</param>
        /// <param name="contentAddress"></param>
        private void BreakTextIntoFragments(string input, out string[] keyPhrases, out string[] metakeys, out string[] metavalues, out string contentAddress, out string keyword, out FormatIndicator formatIndicator)
        {
            // Trim
            input = input.Trim();

            // Set up default values
            keyPhrases     = null;
            metakeys       = null;
            metavalues     = null;
            contentAddress = null;
            keyword        = null;

            // Handle ambiguous form
            if (input.Contains('-') == false && input.Contains('[') == false && input.Contains(' ') == true)
            {
                // Setup format
                formatIndicator = FormatIndicator.Ambiguous;
                // Escape   (double space)
                string doubleSpaceEscapeSymbol = "~%S&!";
                keyPhrases = input.Replace("  ", doubleSpaceEscapeSymbol).Split(new char[] { ' ' });
                // Escape back
                for (int i = 0; i < keyPhrases.Length; i++)
                {
                    keyPhrases[i] = keyPhrases[i].Replace(doubleSpaceEscapeSymbol, " ");
                }
                return;
            }

            // Escape [[
            string openSquareEscapeSymbol = "~%O&!";
            string escaped = input.Replace("[[", openSquareEscapeSymbol);    // Notice input is trimmed, and later processing assumes no beginning and trailing spaces
            // Escape ]]
            string closeSquareEscapeSymbol = "~%C&!";

            escaped = escaped.Replace("]]", closeSquareEscapeSymbol);
            // Escape --
            string dashEscapeSymbol = Home.dashEscapeSymbol;

            escaped = escaped.Replace("--", dashEscapeSymbol);

            // Extract content address: on first and last occurence (so CA might make use of some embeded [] format?)
            int beginIndex = escaped.IndexOf('[');
            int endIndex   = escaped.LastIndexOf(']');

            if (beginIndex >= 0 && beginIndex < endIndex)
            {
                contentAddress = escaped.Substring(beginIndex, endIndex - beginIndex);
            }
            else
            {
                contentAddress = null;
            }

            // If we have anything left for actual clues, use it
            if (beginIndex > 0 || beginIndex == -1)
            {
                // Get the part before CEA
                if (beginIndex > 0)
                {
                    escaped = escaped.Substring(0, beginIndex);
                }
                // Extract ID
                if (escaped.Length > 2 && escaped.ToUpper().IndexOf("ID") == 0)
                {
                    keyPhrases    = new string[1];
                    keyPhrases[0] = keyPhrases[0].Substring(2);
                }
                else if (escaped.Length <= 2)
                {
                    keyPhrases = new string[] { escaped };
                }
                else
                {
                    // Split: A-B-C;A-B;…#metaname#metaname@metavalue
                    string[] clues = escaped.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);

                    // Extract last clue: A-B-C#metaname#metaname@metavalue or #metaname#metaname@metavalue
                    string lastClue              = clues[clues.Length - 1];
                    string metaStrings           = lastClue;
                    int    nearestSeperatorIndex = Math.Min(lastClue.IndexOf('#') == -1 ? int.MaxValue : lastClue.IndexOf('#'), lastClue.IndexOf('@') == -1 ? int.MaxValue : lastClue.IndexOf('@'));
                    if (nearestSeperatorIndex != int.MaxValue)
                    {
                        metaStrings             = lastClue.Substring(nearestSeperatorIndex);
                        clues[clues.Length - 1] = lastClue.Substring(0, nearestSeperatorIndex); // Nearest seperator index could just be 0
                    }
                    else
                    {
                        metaStrings = null;
                    }
                    if (clues[clues.Length - 1] == "")
                    {
                        clues = clues.Take(clues.Length - 1).ToArray();                              // Remove empty clues
                    }
                    // Extract meta contraints: #metaname#metaname@metavalue, where for meta section order doesn’t matter
                    if (metaStrings != null)
                    {
                        List <string> metavaluesList = new List <string>();
                        metakeys = metaStrings.Split(new char[] { ' ', '#' }, StringSplitOptions.RemoveEmptyEntries);
                        for (int i = 0; i < metakeys.Length; i++)
                        {
                            int metaValueSeperatorIndex = metakeys[i].IndexOf('@');
                            if (metaValueSeperatorIndex != -1)
                            {
                                metavaluesList.Add(metakeys[i].Substring(metaValueSeperatorIndex));
                                metakeys[i] = metakeys[i].Substring(0, metaValueSeperatorIndex);
                            }
                        }
                        metakeys   = metakeys.Where(x => !string.IsNullOrEmpty(x)).ToArray(); // How expensive is such preparation work....
                        metavalues = metavaluesList.ToArray();
                    }

                    // Assign return values
                    keyPhrases = clues;
                }
            }

            // Extract keyword for contraints
            if (endIndex > 0 && endIndex != escaped.Length - 1)
            {
                keyword = escaped.Substring(endIndex + 1).Replace("\"", "");
            }

            // Return escape back to content
            if (contentAddress != null)
            {
                contentAddress = contentAddress.Replace(openSquareEscapeSymbol, "[");
                contentAddress = contentAddress.Replace(closeSquareEscapeSymbol, "]");
                contentAddress = contentAddress.Replace(dashEscapeSymbol, "-");
            }
            if (keyPhrases != null)
            {
                for (int i = 0; i < keyPhrases.Length; i++)
                {
                    keyPhrases[i] = keyPhrases[i].Replace(openSquareEscapeSymbol, "[");
                    keyPhrases[i] = keyPhrases[i].Replace(closeSquareEscapeSymbol, "]");
                    keyPhrases[i] = keyPhrases[i].Replace(dashEscapeSymbol, "-");
                }
            }

            // Decide on format
            if (keyPhrases != null && keyPhrases[0].ToUpper().IndexOf("ID") == 0)
            {
                formatIndicator = FormatIndicator.ID;
            }
            else if (metakeys != null || metavalues != null)
            {
                formatIndicator = FormatIndicator.Constrained;
            }
            else if (input.IndexOf('-') > 0 || input.IndexOf('[') > 0)
            {
                formatIndicator = FormatIndicator.SimpleClue;
            }
            else if (input.Contains('-') == false && input.Contains(' ') == false)
            {
                formatIndicator = FormatIndicator.Initiation;
            }
            else
            {
                formatIndicator = FormatIndicator.Unrecognized;
            }
        }
        private async void SearchTextBox_TextChanged(object sender, TextChangedEventArgs e)
        {
            //<Development> Consider porting Web mode with Lightning's recent urls to boost usability

            // Skip default
            if (SearchTextBox.Text == "" || SearchTextBox.Text == App.DefaultTextboxText)
            {
                return;
            }

            // If InterfaceOption.ExtendedFunctions is enabled and we have selected web mode then don't do anything here
            if ((InterfaceOption & InterfaceOption.ExtendedFunctions) == InterfaceOption.ExtendedFunctions && ComboBoxSelection == SearchComboBoxEnum.Web)
            {
                return;                                                                                                                                            // Don't do anything
            }
            // Select only the line current cursor in on for search (e.g. in case of multiline)
            // Ref: https://stackoverflow.com/questions/17909651/c-sharp-get-cursor-line-in-richtextbox
            // Ref: https://stackoverflow.com/questions/31651305/how-to-get-textboxs-line-from-mouse-position
            string searchString = string.Empty;

            try
            {
                int lineIndex = SearchTextBox.GetLineIndexFromCharacterIndex(SearchTextBox.CaretIndex);
                if (lineIndex == -1)
                {
                    return;
                }
                searchString = SearchTextBox.GetLineText(lineIndex);
            }
            catch (Exception) { return; }
            if (string.IsNullOrWhiteSpace(searchString))
            {
                return;
            }

            // Do search depending on current setting
            Triplet <List <ClueFragment>, List <Document>, FormatIndicator> triplet = await Task.Run(() => SearchTextBox_TextChangedDoSeaerch(searchString));

            // Retrive return values
            List <ClueFragment> nextClues       = triplet.Key;
            List <Document>     foundDocuments  = triplet.Value;
            FormatIndicator     formatIndicator = triplet.Tag;

            // Set up return results
            if (nextClues != null)
            {
                for (int i = 0; i < nextClues.Count; i++)
                {
                    if (i < 10)
                    {
                        nextClues[i].Index = "F" + (i + 1); // Use functional keys to quickly select a suggested clue
                    }
                    else
                    {
                        nextClues[i].Index = string.Empty;
                    }
                }
            }
            ClueFragmentSelectionpane.ItemsSource = nextClues;
            DocumentSelectionPane.ItemsSource     = foundDocuments;

            // Validation: If ShowValidationSymbol is enabled then show whether we find anything
            if ((InterfaceOption & InterfaceOption.ShowValidationSymbol) == InterfaceOption.ShowValidationSymbol)
            {
                if (foundDocuments != null && foundDocuments.Count > 0)
                {
                    ValidationSymbolText.Content = "✓";

                    // Also display format indicator of current input format in tooltip when it's a tick
                    switch (formatIndicator)
                    {
                    case FormatIndicator.ID:
                        ValidationSymbolText.ToolTip = "Perform a search using ID";
                        break;

                    case FormatIndicator.Constrained:
                        ValidationSymbolText.ToolTip = "Perform a search using constraints";
                        break;

                    case FormatIndicator.SimpleClue:
                        ValidationSymbolText.ToolTip = "Perform a search using simple clue format";
                        break;

                    case FormatIndicator.Ambiguous:
                        ValidationSymbolText.ToolTip = "Perform an ambiguous serach";
                        break;

                    case FormatIndicator.Initiation:
                        ValidationSymbolText.ToolTip = "Initializing a search";
                        break;

                    default:
                        ValidationSymbolText.ToolTip = "Unrecognized format";
                        break;
                    }
                }
                else
                {
                    ValidationSymbolText.Content = "!";
                }
            }
        }