Files
cpptripleslash/TripleSlashCompletionCommandHandler.cs
T

329 lines
14 KiB
C#

namespace CppTripleSlash
{
using EnvDTE;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Language.Intellisense;
using Microsoft.VisualStudio.OLE.Interop;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.TextManager.Interop;
using System;
using System.Runtime.InteropServices;
using System.Text;
public class TripleSlashCompletionCommandHandler : IOleCommandTarget
{
public const string CppTypeName = "C/C++";
private IOleCommandTarget m_nextCommandHandler;
private IWpfTextView m_textView;
private TripleSlashCompletionHandlerProvider m_provider;
private ICompletionSession m_session;
DTE m_dte;
public TripleSlashCompletionCommandHandler(
IVsTextView textViewAdapter,
IWpfTextView textView,
TripleSlashCompletionHandlerProvider provider,
DTE dte)
{
this.m_textView = textView;
this.m_provider = provider;
this.m_dte = dte;
// add the command to the command chain
if (textViewAdapter != null &&
textView != null &&
textView.TextBuffer != null &&
textView.TextBuffer.ContentType.TypeName == CppTypeName)
{
textViewAdapter.AddCommandFilter(this, out m_nextCommandHandler);
}
}
public int QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText)
{
return m_nextCommandHandler.QueryStatus(ref pguidCmdGroup, cCmds, prgCmds, pCmdText);
}
public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
{
try
{
if (VsShellUtilities.IsInAutomationFunction(m_provider.ServiceProvider))
{
return m_nextCommandHandler.Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut);
}
// make a copy of this so we can look at it after forwarding some commands
uint commandID = nCmdID;
char typedChar = char.MinValue;
// make sure the input is a char before getting it
if (pguidCmdGroup == VSConstants.VSStd2K && nCmdID == (uint)VSConstants.VSStd2KCmdID.TYPECHAR)
{
typedChar = (char)(ushort)Marshal.GetObjectForNativeVariant(pvaIn);
}
// check for the triple slash
if (typedChar == '/' && m_dte != null)
{
string currentLine = GetCurrentLine();
if ((currentLine + "/").Trim() == "///")
{
// Calculate how many spaces
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)
{
codeElement = fcm.CodeElementFromPoint(ts.ActivePoint, vsCMElement.vsCMElementFunction);
}
// Process the function
Function function = new Function();
if (codeElement is CodeFunction codeFunction)
{
foreach (CodeElement child in codeElement.Children)
{
if (child is CodeParameter parameter)
{
function.Arguments.Add(parameter.Name);
}
}
function.ReturnType = codeFunction.Type.AsString;
}
else
{
try
{
function.Parse(GetCurrentLine());
}
catch
{
}
}
// Add the XML comments to the file
StringBuilder sb = new StringBuilder($"/ <summary>\r\n{prefix}\r\n{prefix}</summary>");
if(!string.IsNullOrEmpty(function.ReturnType))
{
foreach (string argument in function.Arguments)
{
sb.Append($"\r\n{prefix}<param name=\"{argument}\"></param>");
}
if (function.ReturnType != "void")
{
sb.AppendFormat($"\r\n{prefix}<returns></returns>");
}
}
ts.MoveToLineAndOffset(oldLine, oldOffset);
ts.Insert(sb.ToString());
ts.MoveToLineAndOffset(oldLine, oldOffset);
ts.LineDown();
ts.EndOfLine();
return VSConstants.S_OK;
}
}
if (m_session != null && !m_session.IsDismissed)
{
// check for a commit character
if (nCmdID == (uint)VSConstants.VSStd2KCmdID.RETURN
|| nCmdID == (uint)VSConstants.VSStd2KCmdID.TAB
|| typedChar == '>')
{
// check for a selection
// if the selection is fully selected, commit the current session
if (m_session.SelectedCompletionSet.SelectionStatus.IsSelected)
{
string selectedCompletion = m_session.SelectedCompletionSet.SelectionStatus.Completion.DisplayText;
m_session.Commit();
TextSelection ts = m_dte.ActiveDocument.Selection as TextSelection;
switch (selectedCompletion)
{
case "<!-->":
ts.CharLeft(false, 3);
break;
case "<![CDATA[>":
ts.CharLeft(false, 3);
break;
case "<c>":
ts.CharLeft(false, 4);
break;
case "<code>":
ts.CharLeft(false, 7);
break;
case "<example>":
ts.CharLeft(false, 10);
break;
case "<exception>":
ts.CharLeft(false, 14);
break;
case "<include>":
ts.CharLeft(false, 21);
break;
case "<list>":
ts.CharLeft(false, 7);
break;
case "<para>":
ts.CharLeft(false, 7);
break;
case "<param>":
ts.CharLeft(false, 10);
break;
case "<paramref>":
ts.CharLeft(false, 13);
break;
case "<permission>":
ts.CharLeft(false, 15);
break;
case "<remarks>":
ts.CharLeft(false, 10);
break;
case "<returns>":
ts.CharLeft(false, 10);
break;
case "<see>":
ts.CharLeft(false, 3);
break;
case "<seealso>":
ts.CharLeft(false, 3);
break;
case "<typeparam>":
ts.CharLeft(false, 14);
break;
case "<typeparamref>":
ts.CharLeft(false, 3);
break;
case "<value>":
ts.CharLeft(false, 8);
break;
default:
break;
}
// also, don't add the character to the buffer
return VSConstants.S_OK;
}
else
{
// if there is no selection, dismiss the session
m_session.Dismiss();
}
}
}
else
{
if (pguidCmdGroup == VSConstants.VSStd2K && nCmdID == (uint)VSConstants.VSStd2KCmdID.RETURN)
{
string currentLine = GetCurrentLine();
if (currentLine.TrimStart().StartsWith("///"))
{
TextSelection ts = m_dte.ActiveDocument.Selection as TextSelection;
string spaces = currentLine.Replace(currentLine.TrimStart(), "");
ts.Insert("\r\n" + spaces + "/// ");
return VSConstants.S_OK;
}
}
}
// pass along the command so the char is added to the buffer
int retVal = m_nextCommandHandler.Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut);
if (typedChar == '<')
{
string currentLine = GetCurrentLine();
if (currentLine.TrimStart().StartsWith("///"))
{
if (m_session == null || m_session.IsDismissed) // If there is no active session, bring up completion
{
if (this.TriggerCompletion())
{
m_session.SelectedCompletionSet.SelectBestMatch();
m_session.SelectedCompletionSet.Recalculate();
return VSConstants.S_OK;
}
}
}
}
else if (
commandID == (uint)VSConstants.VSStd2KCmdID.BACKSPACE ||
commandID == (uint)VSConstants.VSStd2KCmdID.DELETE ||
char.IsLetter(typedChar))
{
if (m_session != null && !m_session.IsDismissed) // the completion session is already active, so just filter
{
m_session.SelectedCompletionSet.SelectBestMatch();
m_session.SelectedCompletionSet.Recalculate();
return VSConstants.S_OK;
}
}
return retVal;
}
catch
{
}
return VSConstants.E_FAIL;
}
private bool TriggerCompletion()
{
try
{
if (m_session != null)
{
return false;
}
// the caret must be in a non-projection location
SnapshotPoint? caretPoint =
m_textView.Caret.Position.Point.GetPoint(
textBuffer => (!textBuffer.ContentType.IsOfType("projection")), PositionAffinity.Predecessor);
if (!caretPoint.HasValue)
{
return false;
}
m_session = m_provider.CompletionBroker.CreateCompletionSession(
m_textView,
caretPoint.Value.Snapshot.CreateTrackingPoint(caretPoint.Value.Position, PointTrackingMode.Positive),
true);
// subscribe to the Dismissed event on the session
m_session.Dismissed += this.OnSessionDismissed;
m_session.Start();
return true;
}
catch
{
}
return false;
}
private void OnSessionDismissed(object sender, EventArgs e)
{
if (m_session != null)
{
m_session.Dismissed -= this.OnSessionDismissed;
m_session = null;
}
}
private string GetCurrentLine()
{
return m_textView.TextSnapshot.GetLineFromPosition(
m_textView.Caret.Position.BufferPosition.Position).GetText();
}
}
}