/// <summary> /// Sends a new parsing request to the parser application, and returns its response. /// </summary> /// <param name="MethodSignature">The signature string of the method (the operation or /// function) to parse.</param> /// <returns>The parsed message signature response from the parser, or null if something /// went wrong.</returns> public MethodSignatureResponse RequestMethodSignatureParse(string MethodSignature) { if (Stream == null || !Stream.IsConnected) { Logger.Debug($"Tried to send a {nameof(MethodSignatureRequest)} to the parser but " + $"it isn't connected."); return(null); } try { // Create and wrap the request message MethodSignatureRequest request = new MethodSignatureRequest { MethodSignature = MethodSignature }; Message message = MessageManager.WrapMessage(request); // Send the message to the parser byte[] requestBuffer = message.ToByteArray(); byte[] bufferSize = BitConverter.GetBytes(requestBuffer.Length); Stream.Write(bufferSize, 0, bufferSize.Length); Stream.Flush(); Stream.Write(requestBuffer, 0, requestBuffer.Length); Stream.Flush(); // Wait for a response, and read how big it is byte[] lengthBuffer = new byte[sizeof(int)]; if (!ReadFromPipe(lengthBuffer, lengthBuffer.Length)) { Logger.Warn("Method parsing request failed, something went wrong while reading from the pipe."); return(null); } // Get the actual response from the parser int responseLength = BitConverter.ToInt32(lengthBuffer, 0); byte[] responseBuffer = new byte[responseLength]; if (!ReadFromPipe(responseBuffer, responseLength)) { Logger.Warn("Method parsing request failed, something went wrong while reading from the pipe."); return(null); } // Deserialize the response Message response = Message.Parser.ParseFrom(responseBuffer); switch (response.Type) { case MessageType.Error: Error error = Error.Parser.ParseFrom(response.MessageBody); Logger.Warn($"Parser failed during method signature processing: {error.ErrorType} - {error.Message}"); Logger.Trace(error.StackTrace); return(null); case MessageType.MethodSignatureResponse: MethodSignatureResponse signatureResponse = MethodSignatureResponse.Parser.ParseFrom(response.MessageBody); return(signatureResponse); default: throw new Exception($"Unexpected message response type: {response.Type}"); } } catch (Exception ex) { Logger.Error($"Error during method signature processing: {ex.GetType().Name} - {ex.Message}"); Logger.Trace(ex.StackTrace); return(null); } }
/// <summary> /// Creates and adds documentation comment blocks when the user types a triple slash. /// </summary> private void HandleTripleSlash() { ThreadHelper.ThrowIfNotOnUIThread(); // Get the original placement of the cursor in the code editor TextSelection ts = (TextSelection)Dte.ActiveDocument.Selection; int oldLine = ts.ActivePoint.Line; int oldOffset = ts.ActivePoint.LineCharOffset; // Check to see if the previous line starts with a triple-slash; if it does, we should probably // just return because there's most likely a docstring already in place. ts.LineUp(); ts.StartOfLine(); string previousLine = TextView.TextSnapshot.GetLineFromPosition( TextView.Caret.Position.BufferPosition.Position).GetText(); if (previousLine.TrimStart().StartsWith("///")) { ts.MoveToLineAndOffset(oldLine, oldOffset); ts.Insert("/"); // Add the slash that the user just typed return; } // Get the contents of the next line (the one following the original line) ts.LineDown(); ts.LineDown(); ts.StartOfLine(); int currentCharIndex = TextView.Caret.Position.BufferPosition.Position; string fullText = TextView.TextSnapshot.GetText(); // Check if we just triple-slashed a method (a function or an operation) Match methodMatch = GetMethodSignatureMatch(currentCharIndex, fullText); if (methodMatch != null) { Logger.Debug($"Found a potential method match: [{methodMatch.Value}]"); string signatureString = methodMatch.Groups["Signature"].Value; string leadingSpaces = methodMatch.Groups["Spaces"].Value; // Build the summary section, which is going to go in no matter what StringBuilder commentBuilder = new StringBuilder(); commentBuilder.AppendLine("/ # Summary"); commentBuilder.Append(leadingSpaces + "/// "); // Ask the Q# parser application to pull out all of the method details so we know what to // put into the documentation comments, and add them if parsing succeeded Logger.Debug("Sending a parse request to the Q# parser..."); try { MethodSignatureResponse signature = Messenger.RequestMethodSignatureParse(signatureString); if (signature != null) { Logger.Debug($"Parsing succeeded, method name = [{signature.Name}], " + $"{signature.ParameterNames.Count} parameters, returns something = {signature.HasReturnType}."); BuildMethodCommentBlock(signature, commentBuilder, leadingSpaces); } } catch (Exception ex) { Logger.Error($"Error during method signature request: {ex.GetType().Name} - {ex.Message}"); Logger.Trace(ex.StackTrace); } // Move to the original cursor position and add the comment block to the code ts.MoveToLineAndOffset(oldLine, oldOffset); ts.Insert(commentBuilder.ToString()); ts.MoveToLineAndOffset(oldLine, oldOffset); ts.LineDown(); ts.EndOfLine(); return; } // Check if we just triple-slashed a new type Match newtypeMatch = GetNewTypeMatch(currentCharIndex, fullText); if (newtypeMatch != null) { Logger.Debug($"Found a newtype match: [{newtypeMatch.Value}]"); string leadingSpaces = newtypeMatch.Groups["Spaces"].Value; // Build the summary section StringBuilder commentBuilder = new StringBuilder(); commentBuilder.AppendLine("/ # Summary"); commentBuilder.Append(leadingSpaces + "/// "); // Move to the original cursor position and add the comment block to the code ts.MoveToLineAndOffset(oldLine, oldOffset); ts.Insert(commentBuilder.ToString()); ts.MoveToLineAndOffset(oldLine, oldOffset); ts.LineDown(); ts.EndOfLine(); return; } // If this was a triple slash on something else, just add the slash and return. ts.MoveToLineAndOffset(oldLine, oldOffset); ts.Insert("/"); }
/// <summary> /// Adds the relevant Markdown sections for the given method signature to the provided /// comment block builder. /// </summary> /// <param name="MethodSignature">The method's signature</param> /// <param name="CommentBuilder">The <see cref="StringBuilder"/> used to generate the /// comment block</param> /// <param name="LeadingSpaces">A string containing the leading spaces used for /// indenting the comment block</param> private void BuildMethodCommentBlock(MethodSignatureResponse MethodSignature, StringBuilder CommentBuilder, string LeadingSpaces) { // Add sections for the input parameters if (MethodSignature.ParameterNames.Count > 0) { CommentBuilder.AppendLine(); for (int i = 0; i < LinesBetweenSections - 1; i++) { CommentBuilder.AppendLine(LeadingSpaces + "/// "); } CommentBuilder.Append(LeadingSpaces + "/// # Input"); bool isFirstParameter = true; foreach (string parameterName in MethodSignature.ParameterNames) { CommentBuilder.AppendLine(); if (!isFirstParameter) { for (int i = 0; i < LinesBetweenParameters - 1; i++) { CommentBuilder.AppendLine(LeadingSpaces + "/// "); } } isFirstParameter = false; CommentBuilder.AppendLine(LeadingSpaces + $"/// ## {parameterName}"); CommentBuilder.Append(LeadingSpaces + "/// "); } } // Add sections for the type parameters if (MethodSignature.TypeParameterNames.Count > 0) { CommentBuilder.AppendLine(); for (int i = 0; i < LinesBetweenSections - 1; i++) { CommentBuilder.AppendLine(LeadingSpaces + "/// "); } CommentBuilder.Append(LeadingSpaces + "/// # Type Parameters"); bool isFirstParameter = true; foreach (string typeParameterName in MethodSignature.TypeParameterNames) { CommentBuilder.AppendLine(); if (!isFirstParameter) { for (int i = 0; i < LinesBetweenParameters - 1; i++) { CommentBuilder.AppendLine(LeadingSpaces + "/// "); } } isFirstParameter = false; CommentBuilder.AppendLine(LeadingSpaces + $"/// ## '{typeParameterName}"); CommentBuilder.Append(LeadingSpaces + "/// "); } } // Add the output section if it has a return type if (MethodSignature.HasReturnType) { CommentBuilder.AppendLine(); for (int i = 0; i < LinesBetweenSections - 1; i++) { CommentBuilder.AppendLine(LeadingSpaces + "/// "); } CommentBuilder.AppendLine(LeadingSpaces + "/// # Output"); CommentBuilder.Append(LeadingSpaces + "/// "); } }
/// <summary> /// Parses a raw Q# method signature and extracts the important details for documentation purposes. /// </summary> /// <param name="MethodSignature">The signature of the method, in plaintext form</param> /// <returns>A <see cref="MethodSignatureResponse"/> with the relevant information extracted from /// the raw signature, or an <see cref="ErrorMessage"/> if parsing failed.</returns> public IMessage ParseMethodSignature(string MethodSignature) { try { string name = null; List <string> parameterNames = null; List <string> typeParameterNames = null; bool hasReturnType = false; // Parse the details out of the signature text QsFragment[] fragments = Parsing.ProcessCodeFragment(MethodSignature); if (fragments[0].Kind is QsFragmentKind.OperationDeclaration operationDeclaration) { name = GetSymbolName(operationDeclaration.Item1); parameterNames = GetParameterNames(operationDeclaration.Item2); typeParameterNames = GetTypeParameterNames(operationDeclaration.Item2); hasReturnType = CheckForReturnType(operationDeclaration.Item2); } else if (fragments[0].Kind is QsFragmentKind.FunctionDeclaration functionDeclaration) { name = GetSymbolName(functionDeclaration.Item1); parameterNames = GetParameterNames(functionDeclaration.Item2); typeParameterNames = GetTypeParameterNames(functionDeclaration.Item2); hasReturnType = CheckForReturnType(functionDeclaration.Item2); } else { string warning = $"Warning: tried to parse a method signature, but it wasn't a function or operation. Contents: {MethodSignature}"; Logger.Warn(warning); return(new Error { Message = warning }); } // Log and return them Logger.Debug($"Method name: {name ?? string.Empty}"); Logger.Debug("Parameter names:"); foreach (string parameter in parameterNames) { Logger.Debug($"\t{parameter}"); } Logger.Debug($"Returns something: {hasReturnType}"); MethodSignatureResponse response = new MethodSignatureResponse { Name = name, HasReturnType = hasReturnType }; response.ParameterNames.AddRange(parameterNames); response.TypeParameterNames.AddRange(typeParameterNames); return(response); } catch (Exception ex) { Logger.Error($"Error processing method signature: {ex.GetType().Name} - {ex.Message}."); Logger.Trace(ex.StackTrace); return(new Error { ErrorType = ex.GetType().Name, Message = ex.Message, StackTrace = ex.StackTrace }); } }