/// <summary> /// Get the grammatical categories and value being completed at the given query position. /// This is the pure grammar part of IntelliSense determination. /// </summary> /// <param name="queryBeforeCursor">Query up to where the cursor is placed</param> /// <param name="lastTerm">The TermExpression in progress as parsed</param> /// <returns>IntelliSenseGuidance showing the token in progress and possible grammar categories for it</returns> internal IntelliSenseGuidance GetCurrentTokenOptions(string queryBeforeCursor, out TermExpression lastTerm, out IExpression query) { lastTerm = null; query = null; IntelliSenseGuidance defaultGuidance = new IntelliSenseGuidance(String.Empty, QueryTokenCategory.Term); // If the query is empty, return the guidance for the beginning of the first term if (String.IsNullOrEmpty(queryBeforeCursor)) { return(defaultGuidance); } // Parse the query, asking for hint terms query = QueryParser.Parse(queryBeforeCursor, true); // If the query had parse errors, return empty guidance if (query is EmptyExpression) { return(new IntelliSenseGuidance(String.Empty, QueryTokenCategory.None)); } // Get the last query term to look at the IntelliSense guidance lastTerm = query.GetLastTerm(); // If no last term, return first term guidance (ex: inside new '(' if (lastTerm == null) { return(defaultGuidance); } // Otherwise, grab the last term guidance IntelliSenseGuidance guidance = lastTerm.Guidance; return(guidance); }
private static void AddSuggestionsForTerm(IReadOnlyCollection <Table> targetTables, IntelliSenseResult result, TermExpression lastTerm, IntelliSenseGuidance guidance, List <IntelliSenseItem> suggestions) { if (lastTerm != null && lastTerm.ColumnName == "*") { object termValue = lastTerm.Value; List <Tuple <string, int, int> > columnsForTerm = new List <Tuple <string, int, int> >(); foreach (Table table in targetTables) { DataBlockResult columns = table.Query(new TermInColumnsQuery(termValue.ToString(), GetCompleteQueryPrefix(result))); for (int i = 0; i < columns.Values.RowCount; ++i) { columnsForTerm.Add(new Tuple <string, int, int>((string)columns.Values[i, 0], (int)columns.Values[i, 1], (int)columns.Total)); } } // Sort overall set by frequency descending columnsForTerm.Sort((left, right) => ((double)right.Item2 / (double)right.Item3).CompareTo((double)left.Item2 / (double)left.Item3)); // Add top 10 suggestions int countToReturn = Math.Min(10, columnsForTerm.Count); for (int i = 0; i < countToReturn; ++i) { suggestions.Add(new IntelliSenseItem(QueryTokenCategory.ColumnName, QueryParser.WrapColumnName(columnsForTerm[i].Item1) + " : " + QueryParser.WrapValue(termValue), columnsForTerm[i].Item2, columnsForTerm[i].Item3)); } } }
private static void AddTopColumnValues(IntelliSenseResult result, TermExpression lastTerm, IntelliSenseGuidance guidance, List <IntelliSenseItem> suggestions, Table singleTable, ColumnDetails singleColumn) { string completeQuery = GetCompleteQueryPrefix(result); // Recommend the top ten values in the column with the prefix typed so far DistinctResult topValues = singleTable.Query(new DistinctQueryTop(singleColumn.Name, lastTerm.Value.ToString(), completeQuery, 10)); int total = (int)topValues.Total; if (topValues.Total == 0) { return; } // Walk values in order for ==, :, ::, backwards with inverse percentages for != bool isNotEquals = lastTerm.Operator == Operator.NotEquals; int start = isNotEquals ? topValues.Values.RowCount - 1 : 0; int end = isNotEquals ? -1 : topValues.Values.RowCount; int step = isNotEquals ? -1 : 1; for (int i = start; i != end; i += step) { string value = topValues.Values[i, 0].ToString(); int countForValue = (int)topValues.Values[i, 1]; if (isNotEquals) { countForValue = (int)topValues.Total - countForValue; } if ((countForValue > 1 || total <= 10) && value.StartsWith(guidance.Value, StringComparison.OrdinalIgnoreCase) && value.Length < 100) { suggestions.Add(new IntelliSenseItem(QueryTokenCategory.Value, QueryParser.WrapValue(topValues.Values[i, 0]), countForValue, topValues.Total)); } } }
private static void AddValueDistribution(IntelliSenseResult result, TermExpression lastTerm, IntelliSenseGuidance guidance, List <IntelliSenseItem> suggestions, Table singleTable, ColumnDetails singleColumn) { string completeQuery = GetCompleteQueryPrefix(result); bool inclusive = (lastTerm.Operator == Operator.LessThanOrEqual || lastTerm.Operator == Operator.GreaterThan); bool reverse = (lastTerm.Operator == Operator.GreaterThan || lastTerm.Operator == Operator.GreaterThanOrEqual); // Recommend the top ten values in the column with the prefix typed so far DataBlockResult distribution = singleTable.Query(new DistributionQuery(singleColumn.Name, completeQuery, inclusive)); if (distribution.Total == 0 || distribution.Details.Succeeded == false) { return; } int countSoFar; if (reverse) { countSoFar = (int)distribution.Values[distribution.Values.RowCount - 1, 1]; for (int i = distribution.Values.RowCount - 2; i >= 0; --i) { string value = distribution.Values[i, 0].ToString(); double frequency = (double)countSoFar / (double)(distribution.Total); int countForRange = (int)distribution.Values[i, 1]; if ((distribution.Values.RowCount == 2 || (int)distribution.Values[i + 1, 1] > 0) && value.StartsWith(guidance.Value, StringComparison.OrdinalIgnoreCase)) { suggestions.Add(new IntelliSenseItem(QueryTokenCategory.Value, QueryParser.WrapValue(distribution.Values[i, 0]), countSoFar, distribution.Total)); } countSoFar += countForRange; } } else { countSoFar = 0; for (int i = 0; i < distribution.Values.RowCount - 1; ++i) { string value = distribution.Values[i, 0].ToString(); int countForRange = (int)distribution.Values[i, 1]; countSoFar += countForRange; double frequency = (double)countSoFar / (double)(distribution.Total); if ((distribution.Values.RowCount == 2 || countForRange > 0) && value.StartsWith(guidance.Value, StringComparison.OrdinalIgnoreCase)) { suggestions.Add(new IntelliSenseItem(QueryTokenCategory.Value, QueryParser.WrapValue(distribution.Values[i, 0]), countSoFar, distribution.Total)); } } } }
private static void AddSuggestionsForValue(IReadOnlyCollection <Table> targetTables, IntelliSenseResult result, TermExpression lastTerm, IntelliSenseGuidance guidance, ref bool spaceIsSafeCompletionCharacter, List <IntelliSenseItem> suggestions) { Table singleTable; ColumnDetails singleColumn; if (!TryFindSingleMatchingColumn(targetTables, lastTerm, out singleTable, out singleColumn)) { // If more than one table has the column, we can't recommend anything result.SyntaxHint = Value; } else { if (lastTerm.Operator == Operator.Equals || lastTerm.Operator == Operator.NotEquals || lastTerm.Operator == Operator.Matches || lastTerm.Operator == Operator.MatchesExact) { AddTopColumnValues(result, lastTerm, guidance, suggestions, singleTable, singleColumn); } else if (lastTerm.Operator == Operator.LessThan || lastTerm.Operator == Operator.LessThanOrEqual || lastTerm.Operator == Operator.GreaterThan || lastTerm.Operator == Operator.GreaterThanOrEqual) { AddValueDistribution(result, lastTerm, guidance, suggestions, singleTable, singleColumn); } Type columnType = singleTable.GetColumnType(singleColumn.Name); if (columnType == typeof(ByteBlock)) { result.SyntaxHint = StringValue; } else if (columnType == typeof(bool)) { if (suggestions.Count == 0) { AddWhenPrefixes(BooleanValues, guidance.Value, suggestions); } spaceIsSafeCompletionCharacter = true; } else if (columnType == typeof(DateTime)) { result.SyntaxHint = DateTimeValue; } else if (columnType == typeof(TimeSpan)) { result.SyntaxHint = TimeSpanValue; } else if (columnType == typeof(float) || columnType == typeof(double)) { result.SyntaxHint = FloatValue; } else if (columnType == typeof(byte) || columnType == typeof(sbyte) || columnType == typeof(short) || columnType == typeof(ushort) || columnType == typeof(int) || columnType == typeof(uint) || columnType == typeof(long) || columnType == typeof(ulong)) { result.SyntaxHint = IntegerValue; } else { result.SyntaxHint = String.Format("<{0}>", columnType.Name); } } }
private static void AddSuggestionsForColumnNames(IReadOnlyCollection <Table> targetTables, IntelliSenseGuidance guidance, ref bool spaceIsSafeCompletionCharacter, List <IntelliSenseItem> suggestions) { List <IntelliSenseItem> selectedColumns = new List <IntelliSenseItem>(); foreach (Table table in targetTables) { foreach (ColumnDetails column in table.ColumnDetails) { if (column.Name.StartsWith(guidance.Value, StringComparison.OrdinalIgnoreCase)) { // Add the matching column // Hack: Set the Display value to the bare column name and CompleteAs to the wrapped [ColumnName], so sort order is right selectedColumns.Add(new IntelliSenseItem(QueryTokenCategory.ColumnName, column.Name, String.Format("{0}, {1}", column.Type, table.Name), "[" + column.Name + "]")); if (column.Name.Length > guidance.Value.Length && column.Name[guidance.Value.Length] == ' ') { // Space is unsafe to complete with if a suggest column has a space next in the value spaceIsSafeCompletionCharacter = false; } } } } // Sort selected columns alphabetically *by bare column name*, so [Count] will be before [Count of Options]. selectedColumns.Sort((left, right) => left.Display.CompareTo(right.Display)); // Unhack: Set the display value back to the wrapped [ColumnName] foreach (IntelliSenseItem item in selectedColumns) { item.Display = item.CompleteAs; } // Remove entries where the same column name was suggested by multiple tables (set the hint to '<Multiple Tables>') for (int i = 1; i < selectedColumns.Count; ++i) { if (selectedColumns[i - 1].Display == selectedColumns[i].Display) { selectedColumns[i - 1].Hint = MultipleTables; selectedColumns.RemoveAt(i); --i; } } // Add 'All Columns' hint (last, only if nothing or '*' typed so far) if ("*".StartsWith(guidance.Value)) { selectedColumns.Add(AllColumnNames); } // Add results to IntelliSense suggestions suggestions.AddRange(selectedColumns); }
private static void AddSuggestionsForCompareOperator(IReadOnlyCollection <Table> targetTables, TermExpression lastTerm, IntelliSenseGuidance guidance, List <IntelliSenseItem> suggestions) { Type columnType = null; Table singleTable; ColumnDetails singleColumn; if (TryFindSingleMatchingColumn(targetTables, lastTerm, out singleTable, out singleColumn)) { columnType = singleTable.GetColumnType(singleColumn.Name); } if (columnType == null) { AddWhenPrefixes(CompareOperatorsForString, guidance.Value, suggestions); } else if (columnType == typeof(ByteBlock)) { AddWhenPrefixes(CompareOperatorsForString, guidance.Value, suggestions); } else if (columnType == typeof(bool)) { AddWhenPrefixes(CompareOperatorsForBoolean, guidance.Value, suggestions); } else { AddWhenPrefixes(CompareOperatorsForOther, guidance.Value, suggestions); } }
/// <summary> /// Get the set of IntelliSense suggestions valid at the current position. /// It's filtered to the set of valid query parts are the current position, /// as well as the set of values the current partial value is a prefix for. /// </summary> /// <param name="queryBeforeCursor">Current Arriba Query up to the cursor position</param> /// <param name="targetTables">Table[s] which are valid for the current query</param> /// <returns>IntelliSenseResult reporting what to show</returns> public IntelliSenseResult GetIntelliSenseItems(string queryBeforeCursor, IReadOnlyCollection <Table> targetTables) { IntelliSenseResult result = new IntelliSenseResult() { Query = queryBeforeCursor, Incomplete = "", Complete = "", SyntaxHint = "", CompletionCharacters = new char[0], Suggestions = new List <IntelliSenseItem>() }; // If no tables were passed, show no IntelliSense (hint that there's an error blocking all tables) if (queryBeforeCursor == null || targetTables == null || targetTables.Count == 0) { return(result); } // Filter the set of tables to those valid for the query so far targetTables = FilterToValidTablesForQuery(targetTables, queryBeforeCursor); // If no tables remain valid, show no IntelliSense (hint that there's an error blocking all tables) if (targetTables == null || targetTables.Count == 0) { return(result); } // Get grammatical categories valid after the query prefix TermExpression lastTerm; IExpression query; IntelliSenseGuidance guidance = GetCurrentTokenOptions(queryBeforeCursor, out lastTerm, out query); bool spaceIsSafeCompletionCharacter = !String.IsNullOrEmpty(guidance.Value); // If there are no tokens suggested here, return empty completion if (guidance.Options == QueryTokenCategory.None) { return(result); } // Compute the CurrentCompleteValue, *before* considering values, so value IntelliSense can use them string queryWithoutIncompleteValue = queryBeforeCursor; if (!queryWithoutIncompleteValue.EndsWith(guidance.Value)) { throw new ArribaException("Error: IntelliSense suggestion couldn't be applied."); } queryWithoutIncompleteValue = queryWithoutIncompleteValue.Substring(0, queryWithoutIncompleteValue.Length - guidance.Value.Length); // If the CurrentIncompleteValue is an explicit column name, remove and re-complete that, also if (queryWithoutIncompleteValue.EndsWith("[")) { queryWithoutIncompleteValue = queryWithoutIncompleteValue.Substring(0, queryWithoutIncompleteValue.Length - 1); } result.Complete = queryWithoutIncompleteValue; result.Incomplete = guidance.Value; // Build a ranked list of suggestions - preferred token categories, filtered to the prefix already typed List <IntelliSenseItem> suggestions = new List <IntelliSenseItem>(); if (guidance.Options.HasFlag(QueryTokenCategory.BooleanOperator)) { AddWhenPrefixes(BooleanOperators, guidance.Value, suggestions); } if (guidance.Options.HasFlag(QueryTokenCategory.CompareOperator)) { AddSuggestionsForCompareOperator(targetTables, lastTerm, guidance, suggestions); } if (guidance.Options.HasFlag(QueryTokenCategory.ColumnName)) { AddSuggestionsForColumnNames(targetTables, guidance, ref spaceIsSafeCompletionCharacter, suggestions); } // Space isn't safe to complete values (except when all explicit values shown, bool below) if (guidance.Options.HasFlag(QueryTokenCategory.Value)) { spaceIsSafeCompletionCharacter = false; AddSuggestionsForTerm(targetTables, result, lastTerm, guidance, suggestions); } // If *only* a value is valid here, provide a syntax hint for the value type (and reconsider if space is safe to complete) if (guidance.Options == QueryTokenCategory.Value) { AddSuggestionsForValue(targetTables, result, lastTerm, guidance, ref spaceIsSafeCompletionCharacter, suggestions); } if (guidance.Options.HasFlag(QueryTokenCategory.TermPrefixes)) { AddWhenPrefixes(TermPrefixes, guidance.Value, suggestions); } // Build a list of valid completion characters List <char> completionCharacters = new List <char>(); completionCharacters.Add('\t'); if (spaceIsSafeCompletionCharacter) { completionCharacters.Add(' '); } // If column names are valid here but term prefixes or compare operators, operator start characters are valid completion characters if (guidance.Options.HasFlag(QueryTokenCategory.ColumnName) && !guidance.Options.HasFlag(QueryTokenCategory.CompareOperator) && !guidance.Options.HasFlag(QueryTokenCategory.TermPrefixes)) { completionCharacters.AddRange(ColumnNameCompletionCharacters); } // If there's only one suggestion and it's been fully typed, remove it if (suggestions.Count == 1 && suggestions[0].Display == guidance.Value) { suggestions.Clear(); } // Finish populating the result result.Suggestions = suggestions; result.CompletionCharacters = completionCharacters; return(result); }
private static void AddTopColumnValues(IntelliSenseResult result, TermExpression lastTerm, IntelliSenseGuidance guidance, List <IntelliSenseItem> suggestions, Table singleTable, ColumnDetails singleColumn) { // Lame, to turn single terms into AllQuery [normally they return nothing] string completeQuery = QueryParser.Parse(result.Complete).ToString(); // Recommend the top ten values in the column with the prefix typed so far DistinctResult topValues = singleTable.Query(new DistinctQueryTop(singleColumn.Name, completeQuery, 10)); int total = (int)topValues.Total; if (topValues.Total == 0) { return; } // Walk values in order for ==, :, ::, backwards with inverse percentages for != bool isNotEquals = lastTerm.Operator == Operator.NotEquals; int start = isNotEquals ? topValues.Values.RowCount - 1 : 0; int end = isNotEquals ? -1 : topValues.Values.RowCount; int step = isNotEquals ? -1 : 1; for (int i = start; i != end; i += step) { string value = topValues.Values[i, 0].ToString(); int countForValue = (int)topValues.Values[i, 1]; if (isNotEquals) { countForValue = (int)topValues.Total - countForValue; } double frequency = (double)countForValue / (double)(topValues.Total); if ((countForValue > 1 || total <= 10) && value.StartsWith(guidance.Value, StringComparison.OrdinalIgnoreCase)) { string hint = (countForValue == topValues.Total ? "all" : frequency.ToString("P0")); suggestions.Add(new IntelliSenseItem(QueryTokenCategory.Value, QueryScanner.WrapValue(value), hint)); } } }