diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..07b26ed --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.vs/ +bin/ +obj/ diff --git a/CppTripleSlash.csproj b/CppTripleSlash.csproj index 87f2d7e..68a5fe1 100644 --- a/CppTripleSlash.csproj +++ b/CppTripleSlash.csproj @@ -102,6 +102,7 @@ + diff --git a/Function.cs b/Function.cs new file mode 100644 index 0000000..b672cb4 --- /dev/null +++ b/Function.cs @@ -0,0 +1,202 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; + +namespace CppTripleSlash +{ + public static class StringExtensions + { + public static string SuperTrim(this string str) + { + var sb = new StringBuilder(str.Length); + var space = false; + for (var i = 0; i < str.Length; i++) + { + if (!space && str[i] == ' ') + { + space = true; + sb.Append(' '); + } + else + { + space = false; + sb.Append(str[i]); + } + } + return sb.ToString().Trim(); + } + + public static string ReplaceWord(this string str, string pattern, string replacement) + { + return Regex.Replace(str, "\\b" + pattern + "\\b", replacement); + } + } + + class Function + { + class ParseException : Exception + { + public ParseException(string message) : base(message) { } + } + + public string ReturnType; + public List Arguments = new List(); + + private static string GetArgumentName(string arg) + { + if (arg == "...") //variadic + { + return arg; + } + if (arg.Contains("=")) //default value => "int n = 42" + { + int eqIndex = arg.IndexOf('='); + return GetArgumentName(arg.Substring(0, eqIndex)); + } + Match match = Regex.Match(arg, @"\( *\* *([a-zA-Z0-9_]+) *\)"); + if (match.Success) //function parameter => "int (*name)(...)" + { + return match.Groups[1].Value; + } + //char name[size] + var blockOpen = arg.IndexOf('['); + if (blockOpen != -1) + arg = arg.Substring(0, blockOpen); + //normal argument + arg = arg + .Replace('&', ' ') + .Replace('*', ' ') + .ReplaceWord("const", "") + .ReplaceWord("volatile", "") + .SuperTrim() + //http://en.cppreference.com/w/cpp/language/types + .ReplaceWord("unsigned long long int", "int") + .ReplaceWord("signed long long int", "int") + .ReplaceWord("unsigned long long", "int") + .ReplaceWord("unsigned short int", "int") + .ReplaceWord("unsigned long int", "int") + .ReplaceWord("signed long long", "int") + .ReplaceWord("signed short int", "int") + .ReplaceWord("signed long int", "int") + .ReplaceWord("unsigned short", "int") + .ReplaceWord("unsigned long", "int") + .ReplaceWord("long long int", "int") + .ReplaceWord("unsigned int", "int") + .ReplaceWord("signed short", "int") + .ReplaceWord("signed long", "int") + .ReplaceWord("long double", "int") + .ReplaceWord("signed int", "int") + .ReplaceWord("short int", "int") + .ReplaceWord("long long", "int") + .ReplaceWord("long int", "int") + .ReplaceWord("unsigned", "int") + .ReplaceWord("signed", "int") + .ReplaceWord("short", "int") + .ReplaceWord("long", "int"); + int lastSpace = arg.LastIndexOf(' '); + if (lastSpace == -1) + { + return ""; + } + return arg.Substring(lastSpace + 1).SuperTrim(); + } + + private static IEnumerable SplitArgs(string args) + { + List result = new List(); + int parenDepth = 0; + int templateDepth = 0; + StringBuilder sb = new StringBuilder(); + foreach (char ch in args) + { + switch (ch) + { + case '<': + templateDepth++; + break; + case '>': + templateDepth--; + if (templateDepth == 0) + { + sb.Append(' '); + } + break; + case '(': + parenDepth++; + if (templateDepth == 0) + { + sb.Append(ch); + } + break; + case ')': + parenDepth--; + if (templateDepth == 0) + { + sb.Append(ch); + } + break; + default: + if (parenDepth == 0 && templateDepth == 0 && ch == ',') + { + result.Add(sb.ToString()); + sb.Clear(); + } + else if (templateDepth == 0) + { + sb.Append(ch); + } + break; + } + } + result.Add(sb.ToString()); + return result.Where(s => !string.IsNullOrEmpty(s)).Select(s => s.SuperTrim()); + } + + public void Parse(string decl) + { + ReturnType = null; + Arguments.Clear(); + if (string.IsNullOrEmpty(decl)) + { + throw new ParseException("NullOrEmpty"); + } + decl = decl.SuperTrim(); + if (!decl.EndsWith(";")) + { + throw new ParseException("NotEndsWithSemicolon"); + } + int firstParen = decl.IndexOf('('); + if (firstParen == -1) + { + throw new ParseException("NoFirstParen"); + } + int lastParen = decl.LastIndexOf(')'); + if (lastParen == -1) + { + throw new ParseException("NoLastParen"); + } + string nameReturnType = decl.Substring(0, firstParen).SuperTrim(); + int lastSpace = nameReturnType.LastIndexOf(' '); + string args = decl.Substring(firstParen + 1, lastParen - firstParen - 1).SuperTrim(); + foreach (string arg in SplitArgs(args)) + { + Arguments.Add(GetArgumentName(arg).SuperTrim()); + } + ReturnType = nameReturnType.Substring(0, lastSpace).Replace("static", "").SuperTrim(); + } + + public override string ToString() + { + var sb = new StringBuilder(ReturnType + "("); + for (var i = 0; i < Arguments.Count; i++) + { + if (i > 0) + sb.Append(','); + sb.Append($"\"{Arguments[i]}\""); + } + return sb.ToString() + ")"; + } + } +} diff --git a/TripleSlashCompletionCommandHandler.cs b/TripleSlashCompletionCommandHandler.cs index 99881e0..3b5a81b 100644 --- a/TripleSlashCompletionCommandHandler.cs +++ b/TripleSlashCompletionCommandHandler.cs @@ -68,18 +68,18 @@ public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pv // check for the triple slash if (typedChar == '/' && m_dte != null) { - string currentLine = m_textView.TextSnapshot.GetLineFromPosition( - m_textView.Caret.Position.BufferPosition.Position).GetText(); + string currentLine = GetCurrentLine(); if ((currentLine + "/").Trim() == "///") { // Calculate how many spaces - string spaces = currentLine.Replace(currentLine.TrimStart(), ""); + string prefix = currentLine.Replace(currentLine.TrimStart(), "") + "/// "; TextSelection ts = m_dte.ActiveDocument.Selection as TextSelection; int oldLine = ts.ActivePoint.Line; int oldOffset = ts.ActivePoint.LineCharOffset; ts.LineDown(); ts.EndOfLine(); + // Try to retrieve the CodeElement CodeElement codeElement = null; FileCodeModel fcm = m_dte.ActiveDocument.ProjectItem.FileCodeModel; if (fcm != null) @@ -87,40 +87,50 @@ public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pv codeElement = fcm.CodeElementFromPoint(ts.ActivePoint, vsCMElement.vsCMElementFunction); } - if (codeElement != null && codeElement is CodeFunction) + // Process the function + Function function = new Function(); + if (codeElement is CodeFunction codeFunction) { - CodeFunction function = codeElement as CodeFunction; - StringBuilder sb = new StringBuilder("/ \r\n" + spaces + "/// \r\n" + spaces + "/// "); foreach (CodeElement child in codeElement.Children) { - CodeParameter parameter = child as CodeParameter; - if (parameter != null) + if (child is CodeParameter parameter) { - sb.AppendFormat("\r\n" + spaces + "/// ", parameter.Name); + function.Arguments.Add(parameter.Name); } } - - if (function.Type.AsString != "void") + function.ReturnType = codeFunction.Type.AsString; + } + else + { + try + { + function.Parse(GetCurrentLine()); + } + catch { - sb.AppendFormat("\r\n" + spaces + "/// "); } - - ts.MoveToLineAndOffset(oldLine, oldOffset); - ts.Insert(sb.ToString()); - ts.MoveToLineAndOffset(oldLine, oldOffset); - ts.LineDown(); - ts.EndOfLine(); - return VSConstants.S_OK; } - else + + // Add the XML comments to the file + StringBuilder sb = new StringBuilder($"/ \r\n{prefix}\r\n{prefix}"); + if(!string.IsNullOrEmpty(function.ReturnType)) { - ts.MoveToLineAndOffset(oldLine, oldOffset); - ts.Insert("/ \r\n" + spaces + "/// \r\n" + spaces + "/// "); - ts.MoveToLineAndOffset(oldLine, oldOffset); - ts.LineDown(); - ts.EndOfLine(); - return VSConstants.S_OK; + foreach (string argument in function.Arguments) + { + sb.Append($"\r\n{prefix}"); + } + if (function.ReturnType != "void") + { + sb.AppendFormat($"\r\n{prefix}"); + } } + + ts.MoveToLineAndOffset(oldLine, oldOffset); + ts.Insert(sb.ToString()); + ts.MoveToLineAndOffset(oldLine, oldOffset); + ts.LineDown(); + ts.EndOfLine(); + return VSConstants.S_OK; } } @@ -215,8 +225,7 @@ public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pv { if (pguidCmdGroup == VSConstants.VSStd2K && nCmdID == (uint)VSConstants.VSStd2KCmdID.RETURN) { - string currentLine = m_textView.TextSnapshot.GetLineFromPosition( - m_textView.Caret.Position.BufferPosition.Position).GetText(); + string currentLine = GetCurrentLine(); if (currentLine.TrimStart().StartsWith("///")) { TextSelection ts = m_dte.ActiveDocument.Selection as TextSelection; @@ -231,8 +240,7 @@ public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pv int retVal = m_nextCommandHandler.Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut); if (typedChar == '<') { - string currentLine = m_textView.TextSnapshot.GetLineFromPosition( - m_textView.Caret.Position.BufferPosition.Position).GetText(); + string currentLine = GetCurrentLine(); if (currentLine.TrimStart().StartsWith("///")) { if (m_session == null || m_session.IsDismissed) // If there is no active session, bring up completion @@ -311,5 +319,11 @@ private void OnSessionDismissed(object sender, EventArgs e) m_session = null; } } + + private string GetCurrentLine() + { + return m_textView.TextSnapshot.GetLineFromPosition( + m_textView.Caret.Position.BufferPosition.Position).GetText(); + } } } \ No newline at end of file