/// <summary> /// Identifies the source code region at the given offset. /// </summary> /// <param name="document">The document.</param> /// <param name="targetOffset">The target offset where the region shall be evaluated.</param> /// <returns>The identified source code region.</returns> public ShaderRegion IdentifyRegion(ITextSource document, int targetOffset) { // Parse file from start (offset == 0). ShaderRegion region = IdentifyRegion(document, 0, targetOffset, null, null); return((region == ShaderRegion.Default) ? ShaderRegion.Global : region); }
/// <summary> /// Gets the lookup tables that provide the insight info. /// </summary> /// <param name="region">The shader region.</param> /// <returns>The lookup tables for the specified region.</returns> private NamedObjectCollection <NamedCompletionData>[] GetInsightLookupTables(ShaderRegion region) { NamedObjectCollection <NamedCompletionData>[] lookupTables; switch (region) { case ShaderRegion.Default: case ShaderRegion.Global: lookupTables = new[] { Functions, Methods, EffectFunctions }; break; case ShaderRegion.StructureOrInterface: case ShaderRegion.Code: lookupTables = new[] { Functions, Methods }; break; case ShaderRegion.TechniqueOrPass: case ShaderRegion.TechniqueOrPass10: lookupTables = new[] { EffectFunctions }; break; default: lookupTables = null; break; } return(lookupTables); }
/// <summary> /// Identifies the source code region at the given offset and collect all identifiers up to this /// offset. /// </summary> /// <param name="document">The document.</param> /// <param name="targetOffset">The target offset where the region shall be evaluated.</param> /// <param name="collectedIdentifiers">The collected identifiers.</param> /// <param name="collectedFields">The collected field identifiers.</param> /// <returns>The identified source code region.</returns> /// <remarks> /// The method returns the identified source code region. While parsing the source code up to /// <paramref name="targetOffset"/> it collects all unknown identifiers in /// <paramref name="collectedIdentifiers"/> and all identifiers that look like fields of structs /// in <paramref name="collectedFields"/>. /// </remarks> public ShaderRegion IdentifyRegion(ITextSource document, int targetOffset, out IList <NamedCompletionData> collectedIdentifiers, out IList <NamedCompletionData> collectedFields) { NamedObjectCollection <NamedCompletionData> identifiers = new NamedObjectCollection <NamedCompletionData>(); NamedObjectCollection <NamedCompletionData> fields = new NamedObjectCollection <NamedCompletionData>(); // Parse file from start (offset == 0). ShaderRegion region = IdentifyRegion(document, 0, targetOffset, identifiers, fields); collectedIdentifiers = identifiers; collectedFields = fields; return((region == ShaderRegion.Default) ? ShaderRegion.Global : region); }
/// <summary> /// Gets the lookup-tables that provide the tooltip info. /// </summary> /// <param name="region">The region of the shader file.</param> /// <returns>The lookup-table containing the symbols for the given region.</returns> private NamedObjectCollection <NamedCompletionData>[] GetToolTipLookupTables(ShaderRegion region) { NamedObjectCollection <NamedCompletionData>[] lookupTables; switch (region) { case ShaderRegion.Default: case ShaderRegion.Global: lookupTables = new[] { Functions, Methods, EffectFunctions, Macros }; break; case ShaderRegion.StructureOrInterface: case ShaderRegion.Code: lookupTables = new[] { Functions, Methods, Macros }; break; case ShaderRegion.TechniqueOrPass: case ShaderRegion.TechniqueOrPass10: lookupTables = new[] { EffectFunctions, EffectStates, Macros }; break; case ShaderRegion.BlendState10: lookupTables = new[] { BlendStates, EffectStates, Macros }; break; case ShaderRegion.DepthStencilState10: lookupTables = new[] { DepthStencilStates, EffectStates, Macros }; break; case ShaderRegion.RasterizerState10: lookupTables = new[] { RasterizerStates, EffectStates, Macros }; break; case ShaderRegion.SamplerState: lookupTables = new[] { SamplerStates, EffectStates, Macros }; break; case ShaderRegion.SamplerState10: lookupTables = new[] { SamplerStates10, EffectStates, Macros }; break; case ShaderRegion.StateBlock: lookupTables = new[] { EffectStates, Macros }; break; default: lookupTables = null; break; } return(lookupTables); }
private void OnTextEditorMouseHover(object sender, MouseEventArgs eventArgs) { // Find offset under mouse cursor. var mousePos = eventArgs.GetPosition(_textEditor.TextArea.TextView); double verticalOffset = _textEditor.VerticalOffset; VisualLine visualLine = _textEditor.TextArea.TextView.GetVisualLineFromVisualTop(mousePos.Y + verticalOffset); if (visualLine == null) { return; } double horizontalOffset = _textEditor.HorizontalOffset; int visualColumn = visualLine.GetVisualColumn(mousePos + new Vector(horizontalOffset, verticalOffset)); int relativeOffset = visualLine.GetRelativeOffset(visualColumn); int offset = visualLine.FirstDocumentLine.Offset + relativeOffset; // Get word at mouse cursor. var text = TextUtilities.GetIdentifierAt(_textEditor.Document, offset); // Get region at mouse cursor. ShaderRegion region = _parser.IdentifyRegion(_textEditor.Document, offset); NamedObjectCollection <NamedCompletionData>[] lookupTables = GetToolTipLookupTables(region); if (lookupTables != null) { // Look up symbol in lookup tables. NamedCompletionData info = null; foreach (NamedObjectCollection <NamedCompletionData> lookupTable in lookupTables) { if (lookupTable.TryGet <NamedCompletionData>(text, out info)) { break; } } // Show tooltip if lookup was successful and description for the symbol is available. if (info != null && !string.IsNullOrEmpty(info.Description as string)) { // TODO: Use s better tooltip style. _toolTip = new ToolTip { Content = info.Description, Placement = PlacementMode.Mouse, IsOpen = true, }; } } }
public bool IsStateGroup(ShaderRegion region) { switch (region) { case ShaderRegion.SamplerState: case ShaderRegion.StateBlock: case ShaderRegion.TechniqueOrPass: case ShaderRegion.BlendState10: case ShaderRegion.DepthStencilState10: case ShaderRegion.RasterizerState10: case ShaderRegion.SamplerState10: case ShaderRegion.TechniqueOrPass10: return(true); default: return(false); } }
/// <summary> /// Identifies the source code region at the given offset. /// </summary> /// <param name="document">The document.</param> /// <param name="startOffset">The start offset where to start the search.</param> /// <param name="targetOffset">The target offset where the region shall be evaluated.</param> /// <param name="identifiers">The collected identifiers.</param> /// <param name="fields">The collected field identifiers.</param> /// <returns> /// The method returns the identified source code region. /// </returns> /// <remarks> /// <para> /// If there is no region at <paramref name="targetOffset"/> the method returns /// <see cref="ShaderRegion.Default"/>. In this the caller needs to decide what region it /// actually is. (When <see cref="IdentifyRegion(ITextSource,int,int,NamedObjectCollection{NamedCompletionData},NamedObjectCollection{NamedCompletionData})"/> /// is called with a <paramref name="startOffset"/> of 0 then it is <see cref="ShaderRegion.Global"/>.) /// </para> /// <para> /// This method calls itself recursively. First identifies the outermost region, then it /// recursively refines the search and returns the innermost region. /// </para> /// </remarks> private ShaderRegion IdentifyRegion(ITextSource document, int startOffset, int targetOffset, NamedObjectCollection <NamedCompletionData> identifiers, NamedObjectCollection <NamedCompletionData> fields) { int offset = startOffset; bool collectIdentifiers = (identifiers != null); while (offset < document.TextLength && offset < targetOffset) { char c = document.GetCharAt(offset); switch (c) { case '/': // Skip comments if (offset + 1 < document.TextLength) { char nextChar = document.GetCharAt(offset + 1); if (nextChar == '/') { // Line comment offset = SkipLineComment(document, offset + 2); if (targetOffset <= offset) { return(ShaderRegion.LineComment); } } else if (nextChar == '*') { // Block comment offset = SkipBlockComment(document, offset + 2); if (targetOffset < offset) { return(ShaderRegion.BlockComment); } } else { // No comment ++offset; } } else { // End of file -> Skip past end to terminate algorithm. ++offset; } break; case '"': // Skip strings offset = SkipString(document, offset + 1); if (targetOffset < offset) { return(ShaderRegion.String); } break; case '\'': // Skip character literals offset = SkipCharacterLiteral(document, offset + 1); if (targetOffset < offset) { return(ShaderRegion.CharacterLiteral); } break; case '{': // Identify the current block int startOffsetOfBlock = offset; ShaderRegion region = IdentifyBlockAt(document, offset); offset = TextUtilities.FindClosingBracket(document, offset + 1, '{', '}'); if (offset == -1 || targetOffset < offset) { // Let's identify the region inside this block. (Recursion!) ShaderRegion innerRegion = IdentifyRegion(document, startOffsetOfBlock + 1, targetOffset, identifiers, fields); if (region == ShaderRegion.TechniqueOrPass10 && innerRegion == ShaderRegion.TechniqueOrPass) { // Return the more specific return(ShaderRegion.TechniqueOrPass10); } if (innerRegion == ShaderRegion.Default || innerRegion == ShaderRegion.Unknown) { // The inner region is unknown or same as outer region return(region); } // Return the more specific inner region. return(innerRegion); } ++offset; break; case '<': // Check whether this is an annotation if (IsStartOfAnnotation(document, offset)) { int startOffsetOfAnnotation = offset; offset = TextUtilities.FindClosingBracket(document, offset + 1, '<', '>'); if (offset == -1 || targetOffset <= offset) { // Let's identify the region inside the annotation. (Recursion!) ShaderRegion innerRegion = IdentifyRegion(document, startOffsetOfAnnotation + 1, targetOffset, identifiers, fields); if (innerRegion == ShaderRegion.Default || innerRegion == ShaderRegion.Unknown) { // The inner region is unknown or same as outer region return(ShaderRegion.Annotation); } // Return the more specific inner region. return(innerRegion); } ++offset; } else { ++offset; } break; default: if (Char.IsLetter(c) || c == '_') { if (collectIdentifiers) { string identifier = TextUtilities.GetIdentifierAt(document, offset); if (!String.IsNullOrEmpty(identifier) && !_intelliSense.FullLookupTable.Contains(identifier)) { if (offset > 0 && document.GetCharAt(offset - 1) == '.') { if (!fields.Contains(identifier)) { fields.Add(new GuessCompletionData(identifier)); } } else { if (!identifiers.Contains(identifier)) { identifiers.Add(new GuessCompletionData(identifier)); } } } } offset = SkipIdentifier(document, offset); } else if (Char.IsDigit(c)) { offset = SkipNumber(document, offset); if (targetOffset <= offset) { return(ShaderRegion.Default); } } else { ++offset; } break; } } return(ShaderRegion.Default); }
/// <summary> /// Requests the insight window. /// </summary> /// <param name="key"> /// The currently pressed key which is not yet inserted in the document. /// </param> /// <remarks> /// This method should be called whenever the user types a new character. /// </remarks> private void RequestInsightWindow(char key) { // The insight window is shown when '(' or a ',' of a function signature is typed. if (key != '(' && key != ',') { return; } // Identify the region at the cursor position var document = _textEditor.Document; int caretOffset = _textEditor.TextArea.Caret.Offset; ShaderRegion region = _parser.IdentifyRegion(document, caretOffset); // Fetch lookup tables for the given region. NamedObjectCollection <NamedCompletionData>[] lookupTables = GetInsightLookupTables(region); if (lookupTables == null) { return; } // Find the name of the function. int offset = caretOffset; if (offset == 0) { return; } string identifier = null; if (key == '(') { // Code should look like this: // "Function|" int startOfIdentifier = TextUtilities.FindStartOfIdentifier(document, offset - 1); if (startOfIdentifier >= 0) { identifier = document.GetText(startOfIdentifier, offset - startOfIdentifier); } } else if (key == ',') { // Check whether we are inside a parameter list. // "Function(param1, param2|" offset = TextUtilities.FindOpeningBracket(document, offset - 1, '(', ')'); identifier = TextUtilities.GetIdentifierAt(document, offset - 1); } // Fetch the function description. FunctionCompletionData function = null; if (!string.IsNullOrEmpty(identifier)) { foreach (NamedObjectCollection <NamedCompletionData> lookupTable in lookupTables) { if (lookupTable.TryGet(identifier, out function)) { break; } } } ShowOverloadInsightWindow(function); }
/// <summary> /// Requests the completion window. /// </summary> /// <param name="key">The last typed character that was not yet inserted in the document or '\0'.</param> /// <param name="explicitRequest"> /// If set to <see langword="true"/> this is an explicit request to show the window; if <see langword="false"/> /// the user is typing. /// </param> private void RequestCompletionWindow(char key, bool explicitRequest) { // ----- Determine ShaderRegion of current caret position. var document = _textEditor.Document; int caretOffset = _textEditor.CaretOffset; IList <NamedCompletionData> collectedIdentifiers; IList <NamedCompletionData> collectedFields; ShaderRegion region = _parser.IdentifyRegion(document, caretOffset, out collectedIdentifiers, out collectedFields); // ----- Abort if current region has no IntelliSense. switch (region) { case ShaderRegion.Unknown: case ShaderRegion.Assembler: case ShaderRegion.LineComment: case ShaderRegion.BlockComment: case ShaderRegion.String: case ShaderRegion.CharacterLiteral: // No IntelliSense for these regions. return; } // ----- # --> PreprocessorCompletionData if (key == '#') { char characterBeforeHash = (caretOffset > 0) ? document.GetCharAt(caretOffset - 1) : '\0'; if (characterBeforeHash == '\0' || char.IsWhiteSpace(characterBeforeHash)) { ShowCompletionWindow(PreprocessorCompletionData, null, '#'); } return; } // ----- Abort if a key is set and it is not part of an identifier. bool letterDigitOrUnderscorePressed = (TextUtilities.GetCharacterClass(key) == CharacterClass.IdentifierPart); if (key != '\0' && !letterDigitOrUnderscorePressed) { _completionWindow?.Close(); return; } // ----- Find the symbol immediately before the caret. string symbol; int offset = TextUtilities.FindStartOfIdentifier(document, caretOffset - 1); if (offset >= 0) { symbol = document.GetText(offset, caretOffset - offset); } else { symbol = string.Empty; offset = caretOffset; } // ----- Find the character before the symbol. --offset; char previousCharacter = (offset >= 0) ? document.GetCharAt(offset) : '\0'; // ----- #xxxx --> PreprocessorCompletionData // Check for preprocessor directives if (previousCharacter == '#') { --offset; char characterBeforeHash = (0 <= offset) ? document.GetCharAt(offset) : '\0'; if (characterBeforeHash == '\0' || char.IsWhiteSpace(characterBeforeHash)) { symbol = '#' + symbol; ShowCompletionWindow(PreprocessorCompletionData, symbol, key); } return; } // ----- xxxxx. --> MemberCompletionData and guessed fields. // Check for object members such as "myTexture.|" if (previousCharacter == '.') { if (region == ShaderRegion.StructureOrInterface || region == ShaderRegion.Code) { --offset; bool showCompletionWindow = false; char characterBeforeDot = (0 <= offset) ? document.GetCharAt(offset) : '\0'; if (characterBeforeDot == ']' || characterBeforeDot == ')') { // Looks like something similar to "myArray[n].|" or "GetTexture(n).|". // We assume that a member is requested. showCompletionWindow = true; } else { // Check if we have something like "myVariable.|" string objectName = TextUtilities.GetIdentifierAt(document, offset); if (!string.IsNullOrEmpty(objectName) && !Keywords.Contains(objectName) && !_parser.IsType(objectName)) { showCompletionWindow = true; } } if (showCompletionWindow) { ShowCompletionWindow(MergeCompletionData(MemberCompletionData, collectedFields), symbol, key); } } return; } // ----- Get non-whitespace character before symbol. char previousNonWhitespaceChar = previousCharacter; if (char.IsWhiteSpace(previousNonWhitespaceChar) && offset > 0) { offset = _parser.SkipWhiteSpaceBackwards(document, offset - 1); previousNonWhitespaceChar = document.GetCharAt(offset); } // ----- Check for effect state assignments if (previousNonWhitespaceChar == '=' && _parser.IsStateGroup(region)) { // The line should look like this: // "EffectState = |" --offset; offset = _parser.SkipWhiteSpaceBackwards(document, offset); // Skip index if (document.GetCharAt(offset) == ']') { offset = TextUtilities.FindOpeningBracket(document, offset - 1, '[', ']') - 1; } string stateName = TextUtilities.GetIdentifierAt(document, offset); if (!string.IsNullOrEmpty(stateName)) { // Lookup the state and show the allowed values. NamedObjectCollection <NamedCompletionData> lookupTable = null; switch (region) { case ShaderRegion.BlendState10: lookupTable = BlendStates; break; case ShaderRegion.DepthStencilState10: lookupTable = DepthStencilStates; break; case ShaderRegion.RasterizerState10: lookupTable = RasterizerStates; break; case ShaderRegion.SamplerState: lookupTable = SamplerStates; break; case ShaderRegion.SamplerState10: lookupTable = SamplerStates10; break; case ShaderRegion.TechniqueOrPass: case ShaderRegion.TechniqueOrPass10: case ShaderRegion.StateBlock: lookupTable = EffectStates; break; default: break; } NamedCompletionData state; if (lookupTable != null && lookupTable.TryGet(stateName, out state)) { List <NamedCompletionData> stateValueCompletionData; string[] values = ((StateCompletionData)state).AllowedValues; if (values.Length > 0) { // Add the allowed values for this state to completion data. stateValueCompletionData = new List <NamedCompletionData>(values.Length); foreach (string value in values) { stateValueCompletionData.Add(EffectStateValues[value]); } } else { // This effect state has a generic parameter. // Add types ("bool", "int", etc.) to the completion data. stateValueCompletionData = new List <NamedCompletionData>(ScalarTypes.Count + Types.Count); foreach (NamedCompletionData type in ScalarTypes) { stateValueCompletionData.Add(type); } foreach (NamedCompletionData type in Types) { stateValueCompletionData.Add(type); } } // Add the collected identifiers foreach (NamedCompletionData collectedIdentifier in collectedIdentifiers) { stateValueCompletionData.Add(collectedIdentifier); } ShowCompletionWindow(stateValueCompletionData.ToArray(), symbol, key); return; } } } // If we know nothing about the key, and we are after a blank, we don't want to open // a completion window. // If we have a completion window, we want to continue. // If the user has explicitly request info (e.g. Ctrl+Space) we want to continue. if (key == '\0' && previousCharacter == ' ' && !explicitRequest && _completionWindow == null) { return; } // Show default completion data for each region. ICompletionData[] completionData; switch (region) { case ShaderRegion.Global: completionData = GlobalCompletionData; break; case ShaderRegion.StructureOrInterface: case ShaderRegion.Code: completionData = CodeCompletionData; break; case ShaderRegion.Annotation: completionData = AnnotationCompletionData; break; case ShaderRegion.TechniqueOrPass: case ShaderRegion.TechniqueOrPass10: completionData = TechniqueCompletionData; break; case ShaderRegion.BlendState10: completionData = BlendStateCompletionData; break; case ShaderRegion.DepthStencilState10: completionData = DepthStencilStateCompletionData; break; case ShaderRegion.RasterizerState10: completionData = RasterizerStateCompletionData; break; case ShaderRegion.SamplerState: completionData = SamplerStateCompletionData; break; case ShaderRegion.SamplerState10: completionData = SamplerState10CompletionData; break; case ShaderRegion.StateBlock: completionData = StateBlockCompletionData; break; default: completionData = null; break; } // No data --> close window. if (completionData == null) { _completionWindow?.Close(); return; } // Combine static completion data with guessed identifiers List <ICompletionData> entireCompletionData = new List <ICompletionData>(); foreach (ICompletionData completionEntry in completionData) { entireCompletionData.Add(completionEntry); } foreach (NamedCompletionData collectedIdentifier in collectedIdentifiers) { entireCompletionData.Add(collectedIdentifier); } // Show completion window ShowCompletionWindow(MergeCompletionData(completionData, collectedIdentifiers), symbol, key); }
public bool IsStateGroup(ShaderRegion region) { switch (region) { case ShaderRegion.SamplerState: case ShaderRegion.StateBlock: case ShaderRegion.TechniqueOrPass: case ShaderRegion.BlendState10: case ShaderRegion.DepthStencilState10: case ShaderRegion.RasterizerState10: case ShaderRegion.SamplerState10: case ShaderRegion.TechniqueOrPass10: return true; default: return false; } }
/// <summary> /// Gets the lookup-tables that provide the tooltip info. /// </summary> /// <param name="region">The region of the shader file.</param> /// <returns>The lookup-table containing the symbols for the given region.</returns> private NamedObjectCollection<NamedCompletionData>[] GetToolTipLookupTables(ShaderRegion region) { NamedObjectCollection<NamedCompletionData>[] lookupTables; switch (region) { case ShaderRegion.Default: case ShaderRegion.Global: lookupTables = new[] { Functions, Methods, EffectFunctions, Macros }; break; case ShaderRegion.StructureOrInterface: case ShaderRegion.Code: lookupTables = new[] { Functions, Methods, Macros }; break; case ShaderRegion.TechniqueOrPass: case ShaderRegion.TechniqueOrPass10: lookupTables = new[] { EffectFunctions, EffectStates, Macros }; break; case ShaderRegion.BlendState10: lookupTables = new[] { BlendStates, EffectStates, Macros }; break; case ShaderRegion.DepthStencilState10: lookupTables = new[] { DepthStencilStates, EffectStates, Macros }; break; case ShaderRegion.RasterizerState10: lookupTables = new[] { RasterizerStates, EffectStates, Macros }; break; case ShaderRegion.SamplerState: lookupTables = new[] { SamplerStates, EffectStates, Macros }; break; case ShaderRegion.SamplerState10: lookupTables = new[] { SamplerStates10, EffectStates, Macros }; break; case ShaderRegion.StateBlock: lookupTables = new[] { EffectStates, Macros }; break; default: lookupTables = null; break; } return lookupTables; }
/// <summary> /// Gets the lookup tables that provide the insight info. /// </summary> /// <param name="region">The shader region.</param> /// <returns>The lookup tables for the specified region.</returns> private NamedObjectCollection<NamedCompletionData>[] GetInsightLookupTables(ShaderRegion region) { NamedObjectCollection<NamedCompletionData>[] lookupTables; switch (region) { case ShaderRegion.Default: case ShaderRegion.Global: lookupTables = new[] { Functions, Methods, EffectFunctions }; break; case ShaderRegion.StructureOrInterface: case ShaderRegion.Code: lookupTables = new[] { Functions, Methods }; break; case ShaderRegion.TechniqueOrPass: case ShaderRegion.TechniqueOrPass10: lookupTables = new[] { EffectFunctions }; break; default: lookupTables = null; break; } return lookupTables; }