2016-12-31 00:46:52 +00:00
using System ;
using System.ComponentModel.Composition ;
using System.Runtime.InteropServices ;
using Microsoft.VisualStudio ;
using Microsoft.VisualStudio.Editor ;
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 Microsoft.VisualStudio.Utilities ;
using Microsoft.VisualStudio.Text.Operations ;
namespace VSMerlin32
{
#region Command Filter
[Export(typeof(IVsTextViewCreationListener))]
[Name("Merlin32CompletionController")]
[ContentType("Merlin32")]
[TextViewRole(PredefinedTextViewRoles.Editable)]
internal sealed class VsTextViewCreationListener : IVsTextViewCreationListener
{
[Import]
internal IVsEditorAdaptersFactoryService AdaptersFactory = null ;
[Import]
internal ICompletionBroker CompletionBroker { get ; set ; }
[Import]
internal SVsServiceProvider ServiceProvider { get ; set ; }
public void VsTextViewCreated ( IVsTextView textViewAdapter )
{
ITextView textView = AdaptersFactory . GetWpfTextView ( textViewAdapter ) ;
if ( textView = = null )
return ;
2017-01-15 22:44:43 +00:00
Func < CommandFilter > createCommandHandler = delegate { return new CommandFilter ( textViewAdapter , textView , this ) ; } ;
2016-12-31 00:46:52 +00:00
textView . Properties . GetOrCreateSingletonProperty ( createCommandHandler ) ;
}
}
internal sealed class CommandFilter : IOleCommandTarget
{
2017-01-15 22:44:43 +00:00
private IOleCommandTarget _nextCommandHandler ;
private ITextView _textView ;
private VsTextViewCreationListener _provider ;
private ICompletionSession _session ;
2016-12-31 00:46:52 +00:00
internal CommandFilter ( IVsTextView textViewAdapter , ITextView textView , VsTextViewCreationListener provider )
{
2017-01-15 22:44:43 +00:00
_textView = textView ;
_provider = provider ;
2016-12-31 00:46:52 +00:00
//add the command to the command chain
2017-01-15 22:44:43 +00:00
textViewAdapter . AddCommandFilter ( this , out _nextCommandHandler ) ;
2016-12-31 00:46:52 +00:00
}
public int QueryStatus ( ref Guid pguidCmdGroup , uint cCmds , OLECMD [ ] prgCmds , IntPtr pCmdText )
{
2017-01-15 22:44:43 +00:00
return _nextCommandHandler . QueryStatus ( ref pguidCmdGroup , cCmds , prgCmds , pCmdText ) ;
2016-12-31 00:46:52 +00:00
}
public int Exec ( ref Guid pguidCmdGroup , uint nCmdID , uint nCmdexecopt , IntPtr pvaIn , IntPtr pvaOut )
{
2017-01-15 22:44:43 +00:00
if ( VsShellUtilities . IsInAutomationFunction ( _provider . ServiceProvider ) )
2016-12-31 00:46:52 +00:00
{
2017-01-15 22:44:43 +00:00
return _nextCommandHandler . Exec ( ref pguidCmdGroup , nCmdID , nCmdexecopt , pvaIn , pvaOut ) ;
2016-12-31 00:46:52 +00:00
}
//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 a commit character
if ( nCmdID = = ( uint ) VSConstants . VSStd2KCmdID . RETURN
| | nCmdID = = ( uint ) VSConstants . VSStd2KCmdID . TAB
| | char . IsWhiteSpace ( typedChar )
| | char . IsPunctuation ( typedChar ) )
{
//check for a a selection
2017-01-15 22:44:43 +00:00
if ( _session ! = null & & ! _session . IsDismissed )
2016-12-31 00:46:52 +00:00
{
//if the selection is fully selected, commit the current session
2017-01-15 22:44:43 +00:00
if ( _session . SelectedCompletionSet . SelectionStatus . IsSelected )
2016-12-31 00:46:52 +00:00
{
2017-01-15 22:44:43 +00:00
_session . Commit ( ) ;
2016-12-31 00:46:52 +00:00
//also, don't add the character to the buffer
return VSConstants . S_OK ;
}
else
{
//if there is no selection, dismiss the session
2017-01-15 22:44:43 +00:00
_session . Dismiss ( ) ;
2016-12-31 00:46:52 +00:00
}
}
}
//pass along the command so the char is added to the buffer
2017-01-15 22:44:43 +00:00
int retVal = _nextCommandHandler . Exec ( ref pguidCmdGroup , nCmdID , nCmdexecopt , pvaIn , pvaOut ) ;
2016-12-31 00:46:52 +00:00
bool handled = false ;
2017-01-15 02:56:21 +00:00
// Test for '-' is to catch ELUP (--^)
if ( ! typedChar . Equals ( char . MinValue ) & & ( ( char . IsLetterOrDigit ( typedChar ) ) | | ( ( typedChar = = '\'' ) | | ( typedChar = = '"' ) | | ( typedChar = = '-' ) ) ) )
2016-12-31 00:46:52 +00:00
{
2017-01-15 22:44:43 +00:00
if ( _session = = null | | _session . IsDismissed ) // If there is no active session, bring up completion
2016-12-31 00:46:52 +00:00
{
2017-01-15 22:44:43 +00:00
TriggerCompletion ( ) ;
2016-12-31 00:46:52 +00:00
// No need to filter for single and double-quotes, the choice IS the characted, just doubled, and already populated in a single completionset if we're here...
if ( ( typedChar = = '\'' ) | | ( typedChar = = '"' ) )
{
// We need to save the currect caret position because we'll position it in between the single/double quotes after the commit...
2017-01-15 22:44:43 +00:00
ITextCaret caretBeforeCommit = _session . TextView . Caret ;
_session . Commit ( ) ;
_textView . Caret . MoveTo ( caretBeforeCommit . Position . BufferPosition - 1 ) ;
2016-12-31 00:46:52 +00:00
}
2017-01-15 22:44:43 +00:00
else if ( ! _session . IsDismissed )
2016-12-31 00:46:52 +00:00
{
2017-01-15 22:44:43 +00:00
_session . Filter ( ) ;
2016-12-31 00:46:52 +00:00
}
}
else //the completion session is already active, so just filter
{
2017-01-15 22:44:43 +00:00
_session . Filter ( ) ;
2016-12-31 00:46:52 +00:00
}
handled = true ;
}
else if ( commandID = = ( uint ) VSConstants . VSStd2KCmdID . BACKSPACE //redo the filter if there is a deletion
| | commandID = = ( uint ) VSConstants . VSStd2KCmdID . DELETE )
{
2017-01-15 22:44:43 +00:00
if ( _session ! = null & & ! _session . IsDismissed )
_session . Filter ( ) ;
2016-12-31 00:46:52 +00:00
handled = true ;
}
if ( handled ) return VSConstants . S_OK ;
return retVal ;
}
private bool TriggerCompletion ( )
{
//the caret must be in a non-projection location
SnapshotPoint ? caretPoint =
2017-01-15 22:44:43 +00:00
_textView . Caret . Position . Point . GetPoint (
2016-12-31 00:46:52 +00:00
textBuffer = > ( ! textBuffer . ContentType . IsOfType ( "projection" ) ) , PositionAffinity . Predecessor ) ;
if ( ! caretPoint . HasValue )
{
return false ;
}
2017-01-15 22:44:43 +00:00
_session = _provider . CompletionBroker . CreateCompletionSession ( _textView ,
2016-12-31 00:46:52 +00:00
caretPoint . Value . Snapshot . CreateTrackingPoint ( caretPoint . Value . Position , PointTrackingMode . Positive ) ,
true ) ;
2017-01-09 02:10:10 +00:00
// We need to check now whether we are in a comment or not, because if we are, we don't want to provide a completion list to the user
ITextSnapshot snapshot = caretPoint . Value . Snapshot ;
2017-01-15 22:44:43 +00:00
var triggerPoint = ( SnapshotPoint ) _session . GetTriggerPoint ( snapshot ) ;
2017-01-09 02:10:10 +00:00
var snapshotSpan = new SnapshotSpan ( triggerPoint , 0 ) ;
foreach ( VSMerlin32 . Coloring . Data . SnapshotHelper item in VSMerlin32 . Coloring . Merlin32CodeHelper . GetTokens ( snapshotSpan ) )
{
if ( item . Snapshot . IntersectsWith ( snapshotSpan ) )
{
if ( item . TokenType = = Merlin32TokenTypes . Merlin32Comment )
{
2017-01-15 22:44:43 +00:00
_session . Dismiss ( ) ;
2017-01-09 02:10:10 +00:00
break ;
}
}
}
2017-01-15 22:44:43 +00:00
if ( ! _session . IsDismissed )
2017-01-09 02:10:10 +00:00
{
//subscribe to the Dismissed event on the session
2017-01-15 22:44:43 +00:00
_session . Dismissed + = OnSessionDismissed ;
_session . Start ( ) ;
2017-01-09 02:10:10 +00:00
}
2016-12-31 00:46:52 +00:00
return true ;
}
private void OnSessionDismissed ( object sender , EventArgs e )
{
2017-01-15 22:44:43 +00:00
_session . Dismissed - = OnSessionDismissed ;
_session = null ;
2016-12-31 00:46:52 +00:00
}
}
#endregion
}