///////////////////////////////////////////////////////////////////////////////////////////////////// // OBJECT ///////////////////////////////////////////////////////////////////////////////////////////////////// /// <summary> /// Initializes a new instance of the <c>SimpleOutliningSource</c> class. /// </summary> /// <param name="snapshot">The <see cref="ITextSnapshot"/> to use for this outlining source.</param> /// <param name="parseData">The <see cref="ILLParseData"/> containing AST data.</param> public SimpleOutliningSource(ITextSnapshot snapshot, ILLParseData parseData) : base(snapshot) { if (parseData == null) { throw new ArgumentNullException("parseData"); } // Create a 'Function' outlining node definition if one hasn't yet been created if (functionDefinition == null) { functionDefinition = new OutliningNodeDefinition("Function"); functionDefinition.IsImplementation = true; } Step4d.CompilationUnit compilationUnit = parseData.Ast as Step4d.CompilationUnit; if ((compilationUnit != null) && (compilationUnit.HasMembers)) { // Loop through AST nodes foreach (Step4d.FunctionDeclaration functionAstNode in compilationUnit.Members) { // If the function declaration has a body with a text range... if ((functionAstNode.Body != null) && (functionAstNode.Body.StartOffset.HasValue) && (functionAstNode.Body.EndOffset.HasValue)) { // Add an outlining node this.AddNode(new TextRange(functionAstNode.Body.StartOffset.Value, functionAstNode.Body.EndOffset.Value), functionDefinition); } } } }
/// <summary> /// Occurs when the document's parse data has changed. /// </summary> /// <param name="sender">The sender of the event.</param> /// <param name="e">The <c>EventArgs</c> that contains data related to this event.</param> private void OnEditorDocumentParseDataChanged(object sender, EventArgs e) { // // NOTE: The parse data here is generated in a worker thread... this event handler is called // back in the UI thread though so any processing done below could slow down UI if // the processing is lengthy // ILLParseData parseData = editor.Document.ParseData as ILLParseData; if (parseData != null) { // Show the AST if (parseData.Ast != null) { astOutputEditor.Document.SetText(parseData.Ast.ToTreeString(0).Replace("\t", " ")); } else { astOutputEditor.Document.SetText(null); } // Output errors errorListView.ItemsSource = parseData.Errors; } }
private void OnCodeEditorUserInterfaceUpdate(object sender, RoutedEventArgs e) { // If there is a pending parse data change... if (hasPendingParseData) { // Clear flag hasPendingParseData = false; ILLParseData parseData = codeEditor.Document.ParseData as ILLParseData; if (parseData != null) { //if (codeEditor.Document.CurrentSnapshot.Length < 10000) //{ // // Show the AST // if (parseData.Ast != null) // astOutputEditor.Document.SetText(parseData.Ast.ToTreeString(0)); // else // astOutputEditor.Document.SetText(null); //} //else // astOutputEditor.Document.SetText("(Not displaying large AST for performance reasons)"); //// Output errors //errorListView.ItemsSource = parseData.Errors; } else { // Clear UI //astOutputEditor.Document.SetText(null); //errorListView.ItemsSource = null; //messagePanel.Content = null; } } }
/// <summary> /// Examines the AST data to determine the context type and insert the containing function declaration. /// </summary> /// <param name="context">The <see cref="SimpleContext"/> to update.</param> private static void UpdateFromAst(SimpleContext context) { // Get the snapshot offset TextSnapshotOffset snapshotOffset = context.SnapshotOffset; // Get the document ICodeDocument document = snapshotOffset.Snapshot.Document as ICodeDocument; if (document == null) { return; } ILLParseData parseData = document.ParseData as ILLParseData; if (parseData != null) { CompilationUnit compilationUnit = parseData.Ast as CompilationUnit; if ((compilationUnit != null) && (compilationUnit.HasMembers)) { // Translate the snapshot offset to the AST's snapshot if (parseData.Snapshot != null) { snapshotOffset = snapshotOffset.TranslateTo(parseData.Snapshot, TextOffsetTrackingMode.Negative); } // Loop through AST nodes foreach (FunctionDeclaration functionAstNode in compilationUnit.Members) { // If the child node is a function declaration with valid offsets... if ((functionAstNode.StartOffset.HasValue) && (functionAstNode.EndOffset.HasValue)) { // If the function's text range contains the offset... TextRange functionTextRange = new TextRange(functionAstNode.StartOffset.Value, functionAstNode.EndOffset.Value); if (functionTextRange.Contains(snapshotOffset.Offset)) { // Initially assume we are in a header context.Type = SimpleContextType.FunctionDeclarationHeader; context.ContainingFunctionDeclaration = functionAstNode; // If the function has a body with a range... if ((functionAstNode.Body != null) && (functionAstNode.Body.StartOffset.HasValue) && (functionAstNode.Body.EndOffset.HasValue)) { // If the block's text range contains the offset... TextRange blockTextRange = new TextRange(functionAstNode.Body.StartOffset.Value + 1, functionAstNode.Body.EndOffset.Value - 1); if (blockTextRange.Contains(snapshotOffset.Offset)) { // Mark that we are in a block instead context.Type = SimpleContextType.FunctionDeclarationBlock; } } break; } } } } } }
/// <summary> /// Occurs after a brief delay following any document text, parse data, or view selection update, allowing consumers to update the user interface during an idle period. /// </summary> /// <param name="sender">The sender of the event.</param> /// <param name="e">The <see cref="RoutedEventArgs"/> that contains data related to this event.</param> private void OnEditorUserInterfaceUpdate(object sender, RoutedEventArgs e) { // If there is a pending parse data change... if (hasPendingParseData) { // Clear flag hasPendingParseData = false; ILLParseData parseData = editor.Document.ParseData as ILLParseData; if (parseData != null) { // Output errors errorListView.ItemsSource = parseData.Errors; } else { // Clear UI errorListView.ItemsSource = null; } } }
/// <summary> /// Occurs after a brief delay following any document text, parse data, or view selection update, allowing consumers to update the user interface during an idle period. /// </summary> /// <param name="sender">The sender of the event.</param> /// <param name="e">The <see cref="RoutedEventArgs"/> that contains data related to this event.</param> private void OnSyntaxEditorUserInterfaceUpdate(object sender, RoutedEventArgs e) { // If there is a pending parse data change... if (hasPendingParseData) { // Clear flag hasPendingParseData = false; ILLParseData parseData = editor.Document.ParseData as ILLParseData; if (parseData != null) { if (editor.Document.CurrentSnapshot.Length < 10000) { // Show the AST if (parseData.Ast != null) { astOutputEditor.Document.SetText(parseData.Ast.ToTreeString(0)); } else { astOutputEditor.Document.SetText(null); } } else { astOutputEditor.Document.SetText("(Not displaying large AST for performance reasons)"); } // Output errors errorListView.ItemsSource = parseData.Errors; } else { // Clear UI astOutputEditor.Document.SetText("(Language may not have AST building features)"); errorListView.ItemsSource = null; } } }
///////////////////////////////////////////////////////////////////////////////////////////////////// // PUBLIC PROCEDURES ///////////////////////////////////////////////////////////////////////////////////////////////////// /// <summary> /// Returns an <see cref="IOutliningSource"/> for the specified <see cref="ITextSnapshot"/>. /// </summary> /// <param name="snapshot">The <see cref="ITextSnapshot"/> for which to return an outlining source.</param> /// <returns>An <see cref="IOutliningSource"/> for the specified <see cref="ITextSnapshot"/>.</returns> public IOutliningSource GetOutliningSource(ITextSnapshot snapshot) { if (snapshot != null) { ICodeDocument document = snapshot.Document as ICodeDocument; if (document != null) { // Get the parse data ILLParseData parseData = document.ParseData as ILLParseData; if (parseData != null) { // Create an outlining source based on the parse data SimpleOutliningSource source = new SimpleOutliningSource(snapshot, parseData); // Translate the data to the desired snapshot, which could be slightly newer than the parsed source source.TranslateTo(snapshot); return(source); } } } return(null); }
/// <summary> /// Examines the snapshot text to determine more detail about the context. /// </summary> /// <param name="context">The <see cref="SimpleContext"/> to update.</param> /// <param name="includeArgumentInfo">Whether to populate the argument-related context properties, for use with parameter info.</param> private static void UpdateFromSnapshotText(SimpleContext context, bool includeArgumentInfo) { // Get the snapshot offset TextSnapshotOffset snapshotOffset = context.SnapshotOffset; // Create a default initialization range context.InitializationSnapshotRange = new TextSnapshotRange(snapshotOffset); // Get a snapshot reader ITextSnapshotReader reader = snapshotOffset.Snapshot.GetReader(snapshotOffset.Offset); if (reader == null) { return; } IToken token = reader.ReadToken(); if (token != null) { // If the current token is a comment, flag no context switch (token.Id) { case SimpleTokenId.MultiLineCommentEndDelimiter: case SimpleTokenId.MultiLineCommentLineTerminator: case SimpleTokenId.MultiLineCommentStartDelimiter: case SimpleTokenId.MultiLineCommentText: case SimpleTokenId.SingleLineCommentEndDelimiter: case SimpleTokenId.SingleLineCommentStartDelimiter: case SimpleTokenId.SingleLineCommentText: context.Type = SimpleContextType.None; return; } } // If we are in a function declaration block... if (context.Type == SimpleContextType.FunctionDeclarationBlock) { // Get the AST ICodeDocument document = snapshotOffset.Snapshot.Document as ICodeDocument; if (document == null) { return; } ILLParseData parseData = document.ParseData as ILLParseData; if (parseData == null) { return; } CompilationUnit compilationUnit = parseData.Ast as CompilationUnit; if ((compilationUnit == null) || (!compilationUnit.HasMembers)) { return; } // If argument data should be scanned... if (includeArgumentInfo) { // Scan backward to look for argument data reader.Offset = snapshotOffset.Offset; reader.GoToCurrentTokenStart(); var startOffset = reader.Offset; token = reader.ReadTokenReverse(); while (token != null) { // Quit if we look behind too far (for performance reasons) - 500 is arbitrary number and could be tweaked if ((!context.ArgumentIndex.HasValue) && (startOffset - reader.Offset > 500)) { return; } switch (token.Id) { case SimpleTokenId.CloseParenthesis: // Skip over ( ... ) pair if (!reader.GoToPreviousMatchingTokenById(SimpleTokenId.CloseParenthesis, SimpleTokenId.OpenParenthesis)) { return; } break; case SimpleTokenId.Comma: // Update the context data if (context.ArgumentIndex.HasValue) { context.ArgumentIndex++; } else { context.ArgumentIndex = 1; context.ArgumentSnapshotOffset = new TextSnapshotOffset(reader.Snapshot, token.EndOffset); } break; case SimpleTokenId.OpenParenthesis: // Update the context data context.ArgumentListSnapshotOffset = new TextSnapshotOffset(reader.Snapshot, token.EndOffset); if (!context.ArgumentIndex.HasValue) { context.ArgumentIndex = 0; context.ArgumentSnapshotOffset = context.ArgumentListSnapshotOffset; } // Go to the previous token reader.GoToPreviousToken(); token = reader.Token; if ((token != null) && (token.Id == SimpleTokenId.Identifier)) { // Loop through the AST nodes to ensure that the identifier text matches a declared function name string functionName = reader.TokenText; foreach (FunctionDeclaration functionAstNode in compilationUnit.Members) { // If the name matches the function's name... if (functionAstNode.Name == functionName) { // Update the target function context.TargetFunction = functionAstNode; break; } } } return; default: // Quit if any tokens are found that aren't allowed in invocations if (!IsTokenAllowedInInvocation(token.Id)) { return; } else { break; } } // Move back a token token = reader.ReadTokenReverse(); } } else { // If the current token is an identifier... if ((token != null) && (token.Id == SimpleTokenId.Identifier)) { // Store the identifier snapshot range in case this is a function reference token TextSnapshotRange identifierSnapshotRange = new TextSnapshotRange(reader.Snapshot, token.TextRange); // Next, check to ensure the next non-whitespace token is a '(' token = reader.ReadToken(); while (!reader.IsAtSnapshotEnd) { if (token != null) { switch (token.Id) { case SimpleTokenId.Whitespace: // Continue break; case SimpleTokenId.OpenParenthesis: { // Loop through the AST nodes to ensure that the identifier text matches a declared function name foreach (FunctionDeclaration functionAstNode in compilationUnit.Members) { // If the name matches the function's name... if (functionAstNode.Name == identifierSnapshotRange.Text) { // Update the context type context.Type = SimpleContextType.FunctionReference; context.InitializationSnapshotRange = identifierSnapshotRange; context.TargetFunction = functionAstNode; break; } } return; } default: // Quit return; } } // Advance token = reader.ReadToken(); } } } } }
///////////////////////////////////////////////////////////////////////////////////////////////////// // PUBLIC PROCEDURES ///////////////////////////////////////////////////////////////////////////////////////////////////// /// <summary> /// Requests that an <see cref="ICompletionSession"/> be opened for the specified <see cref="IEditorView"/>. /// </summary> /// <param name="view">The <see cref="IEditorView"/> that will host the session.</param> /// <param name="canCommitWithoutPopup">Whether the session can immediately commit if a single match is made when the session is opened, commonly known as "complete word" functionality.</param> /// <returns> /// <c>true</c> if a session was opened; otherwise, <c>false</c>. /// </returns> public override bool RequestSession(IEditorView view, bool canCommitWithoutPopup) { // Get the context factory service SimpleContextFactory contextFactory = view.SyntaxEditor.Document.Language.GetService <SimpleContextFactory>(); if (contextFactory != null) { // Get a context SimpleContext context = contextFactory.CreateContext(view.Selection.EndSnapshotOffset, false); // Create a session CompletionSession session = new CompletionSession(); session.CanCommitWithoutPopup = canCommitWithoutPopup; switch (context.Type) { case SimpleContextType.Default: // Add items for keywords session.Items.Add(new CompletionItem("function", new CommonImageSourceProvider(CommonImageKind.Keyword), new PlainTextContentProvider("Declares a function."))); break; case SimpleContextType.FunctionDeclarationBlock: case SimpleContextType.FunctionReference: { // Add items for keywords session.Items.Add(new CompletionItem("var", new CommonImageSourceProvider(CommonImageKind.Keyword), new PlainTextContentProvider("Declares a variable."))); session.Items.Add(new CompletionItem("return", new CommonImageSourceProvider(CommonImageKind.Keyword), new PlainTextContentProvider("Returns a value."))); // Add items (one for each function name) ILLParseData parseData = view.SyntaxEditor.Document.ParseData as ILLParseData; if (parseData != null) { CompilationUnit compilationUnit = parseData.Ast as CompilationUnit; if ((compilationUnit != null) && (compilationUnit.HasMembers)) { // Loop through the AST nodes foreach (FunctionDeclaration functionAstNode in compilationUnit.Members) { session.Items.Add(new CompletionItem(functionAstNode.Name, new CommonImageSourceProvider(CommonImageKind.MethodPublic), new FunctionContentProvider(view.HighlightingStyleRegistry, functionAstNode, false, view.DefaultBackgroundColor))); } } } break; } } if (session.Items.Count > 0) { // Ensure the caret is visible view.Scroller.ScrollToCaret(); // Ensure the items are sorted and open the session session.SortItems(); session.Open(view); return(true); } } return(false); }