zoukankan      html  css  js  c++  java
  • VS Extension+NVelocity系列(三)——让VS支持 NVelocity的智能提示(中)

    一、定义

    我们知道,我们的插件是服务于NVelocity的,在你的项目当中,对于NVelocity的模板应当有一个统一的文件扩展名,以便于VS在打开指定扩展名的文件后,就能起到具体的作用。

    如果我没有记错,Castle Monorail MVC 的NVelocity模板一律为.vm文件,本例也以.vm为准。

    在项目上新建一个NVDefinition类,内容如下

    internal static class NVDefinition
    {
        public const string ContentType = "vm";
    
        public const string FileExtension = ".vm";
    
        [Export]
        [Name("vm")]
        [BaseDefinition("HTML")]
        internal static ContentTypeDefinition nvContentTypeDefinition = null;
    
        [Export]
        [FileExtension(FileExtension)]
        [ContentType("vm")]
        internal static FileExtensionToContentTypeDefinition nvFileExtensionDefinition = null;
    }

    其中ContentType和FileExtension是为方便我们在以后的特定中使用的,比如将来要将.vm扩展名改为.rails,仅更改此处即可.

    需要注意的是BaseDefinition这个特性,VS已经预定义了以下几种类型:

    Basic、C/C++、ConsoleOutput、CSharp、CSS、ENC、FindResults、F#、HTML、JScript、XAML、XML

    简单理解就是你的内容要建立在什么内容之上,对NVelocity的代码高亮,不仅要考虑NVelocity自身的语法,还要考虑HTML、JS、CSS等,我们可以通过工具->选项->文本编辑器->文件扩展名->添加一个扩展名为vm并选择HTML编辑器,这会解决我们一大部分问题.

    而类型FileExtensionToContentTypeDefinition的意思是指定一个内容类型和扩展名之间的映射。

    二、IVsTextViewCreationListener

    回想一下我们平时写代码时的情景,当你键入一个<符号时,VS就会弹出html的标签列表或者即将匹配的尾标签,当你在js的对象上键入.符号时,弹出的将是该对象的方法和属性

    因为NVelocity的变量符号均为$,关键字为#,目前我们更关心$符号,因为NVelocity的关键字没几个,能着色就够了.我们希望能键入$符号后,弹出来一组我们自定义的Helper方法,像这样:

    1

    如何捕捉到$符号,就要依靠下面这个接口了,在解决方案上新建一个目录,命名为Intellisense,在此文件夹下新建一个CompletionController类,删掉生成的CompletionController类代码和该类命名空间的.Intellisense部分

    引用以下组件

    Microsoft.VisualStudio.Editor

    Microsoft.VisualStudio.Language.Intellisense

    Microsoft.VisualStudio.TextManager.Interop(7.1.40304.0)

    添加以下类内容:

    [Export(typeof(IVsTextViewCreationListener))]
    [ContentType(NVExtension.ContentType)]
    [TextViewRole(PredefinedTextViewRoles.Interactive)]
    [FileExtension(NVExtension.FileExtension)]
    internal sealed class VsTextViewCreationListener : IVsTextViewCreationListener
    {
        [Import]
        IVsEditorAdaptersFactoryService AdaptersFactory = null;
    
        [Import]
        ICompletionBroker CompletionBroker = null;
    
        public void VsTextViewCreated(IVsTextView textViewAdapter)
        {
            IWpfTextView textView = AdaptersFactory.GetWpfTextView(textViewAdapter);
            IOleCommandTarget next = null;
            VMCommandFilter filter = new VMCommandFilter(textView, CompletionBroker, next);
            textViewAdapter.AddCommandFilter(filter, out next);
        }
    }
    IVsTextViewCreationListener便是我们需要的这样一个监听器接口,每当一个文件被打开时,VS会检查标记有[Export(typeof(IVsTextViewCreationListener))]特性的插件,一旦确认他所标记的ContentType和FileExtension后,就会触发实现该接口的VsTextViewCreated事件.
    在此类上导入了编辑器适配器工厂服务以获取IWpfTextView对象,该对象将帮助我们在后文能找到光标位置等内容.
    VsTextViewCreated事件上,我们给当前的IVsTextView(有别于IWpfTextView)增加了命令过滤.也就是后文要讲的VMCommandFilter类.

    三、IOleCommandTarget接口

    按MSDN线上帮助对此接口的summary:”启用计划在对象和容器之间的命令”,如果靠这个字面意思理解,今天的文就到这里了…

    实际上,这是一个对编辑器命令进行过滤、查询,对特定命令进行操作并返回执行结果的一个接口.

    在CompletionController文件上,继续新建一个类VMCommandFilter,全部内容如下:

    internal sealed class VMCommandFilter : IOleCommandTarget
    {
        /// <summary>
        /// 当前会话
        /// </summary>
        ICompletionSession _CurrentSession;
    
        /// <summary>
        /// TextView(WPF)
        /// </summary>
        IWpfTextView _TextView { get; private set; }
    
        /// <summary>
        /// 代理
        /// </summary>
        ICompletionBroker _Broker { get; private set; }
    
        /// <summary>
        /// 执行由VMCommandFilter未执行完的命令
        /// </summary>
        IOleCommandTarget _Next { get; set; }
    
        public VMCommandFilter(IWpfTextView textView, ICompletionBroker broker, IOleCommandTarget next)
        {
            this._TextView = textView;
            this._Broker = broker;
            this._Next = next;
        }
    
        /// <summary>
        /// 获取输入的字符
        /// </summary>
        /// <param name="pvaIn">输入指针</param>
        private char GetTypeChar(IntPtr pvaIn)
        {
            return (char)(ushort)Marshal.GetObjectForNativeVariant(pvaIn);
        }
    
        /// <summary>
        /// 执行指定的命令
        /// </summary>
        /// <param name="pguidCmdGroup">命令组的 GUID</param>
        /// <param name="nCmdID">命令 ID</param>
        /// <param name="nCmdexecopt">指定对象应如何执行命令</param>
        /// <param name="pvaIn">命令的输入参数</param>
        /// <param name="pvaOut">命令的输出参数</param>
        public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
        {
            int hresult = VSConstants.S_OK;
            char inputChar = Char.MinValue;
            if (pguidCmdGroup == VSConstants.VSStd2K)
            {
                VSConstants.VSStd2KCmdID cmd = (VSConstants.VSStd2KCmdID)nCmdID;
    
                if (cmd == VSConstants.VSStd2KCmdID.RETURN //按下回车
                    ||
                    cmd == VSConstants.VSStd2KCmdID.TAB //按下Tab
                    )
                {
                    Complete(true);
                }
                else//尚未被处理
                {
                    hresult = _Next.Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut); //由VS现行处理此条命令.
                    //确认hresult是否是成功处理的
                    if (ErrorHandler.Succeeded(hresult))
                    {                    
                        if (cmd == VSConstants.VSStd2KCmdID.TYPECHAR) //字符键入
                        {
    		    //获取输入值
                        inputChar = GetTypeChar(pvaIn);
                            //如果当前的会话已经被启动
                            if (_CurrentSession != null && _CurrentSession.IsStarted)
                            {
                                //执行过滤
                                _CurrentSession.SelectedCompletionSet.Filter();
                                //选择最佳匹配
                                _CurrentSession.SelectedCompletionSet.SelectBestMatch();
                                //重新计算CompletionSet
                                _CurrentSession.SelectedCompletionSet.Recalculate();
                            }
                            else if (inputChar == '$') //如果是键入了$字符
                            {
                                //获取插入符号,也就是光标位置.
                                SnapshotPoint caret = _TextView.Caret.Position.BufferPosition;
                                //文本快照
                                ITextSnapshot snapShot = caret.Snapshot;
                                //在当前插入符号位置创建积极的跟踪点
                                ITrackingPoint trackingPoint = snapShot.CreateTrackingPoint(caret, PointTrackingMode.Positive);
                                //由代理创建Completion会话
                                _CurrentSession = _Broker.CreateCompletionSession(_TextView, trackingPoint, true);
                                //启动该会话.
                                _CurrentSession.Start();
                                //添加放弃事件
                                _CurrentSession.Dismissed += (sender, args) => _CurrentSession = null;
                            }
                        }
                        else if (
                            cmd == VSConstants.VSStd2KCmdID.BACKSPACE //删除键
                            ||
                            cmd == VSConstants.VSStd2KCmdID.DELETE //删除键
                            )
                        {
                            //如果当前会话并未丢弃,执行过滤
                            if (_CurrentSession != null && !_CurrentSession.IsDismissed)
                                _CurrentSession.Filter();
                        }
                    }
                }
            }
            return hresult;
        }
    
        /// <summary>
        /// 查询该对象以获得由用户界面事件生成的一个或多个命令的状态
        /// </summary>
        /// <param name="pguidCmdGroup">命令组的 GUID</param>
        /// <param name="cCmds">命令数</param>
        /// <param name="prgCmds">数组指示命令调用方需要状态信息的 OLECMD 结构</param>
        /// <param name="pCmdText">由OLECMDTEXT返回的单个命令的状态信息</param>
        public int QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText)
        {
            return _Next.QueryStatus(ref pguidCmdGroup, cCmds, prgCmds, pCmdText);
        }
    
        /// <summary>
        /// 确认完成或者丢弃
        /// </summary>
        private bool Complete(bool force)
        {
            if (_CurrentSession == null || !_CurrentSession.IsStarted)
                return false;
    
            //如果用户没有选择并且主动丢弃
            if (!_CurrentSession.SelectedCompletionSet.SelectionStatus.IsSelected && !force)
            {
                _CurrentSession.Dismiss();
                return false;
            }
            else
            {
                _CurrentSession.Commit();
                return true;
            }
        }
    }

    引用以下组件:

    Microsoft.VisualStudio.OLE.Interop

    Microsoft.VisualStudio.Shell.11.0

    IOleCommandTarget接口有两个方法:

    int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)

    参数pguidCmdGroup是一个命令组,可接受的值定义在Microsoft.VisualStudio.VSConstants类上,有很多,目前我们仅关注VSConstants.VSStd2K,表示Win2000的标准命令组.数nCmdID则为某个命令组下的单个命令,比如输入(VSConstants.VSStd2KCmdID.TypeChar)、回车(VSConstants.VSStd2KCmdID.Return)、Tab(VSConstants.VSStd2KCmdID.Tab)等参数pvaIn是一个输入指针,IntPtr类型不常见,不过熟悉Win32 API的童鞋应当会有映像.

    其他参数本文不用,不多解释.

    方法内容分析:

    在我们选择弹出的智能提示的操作过程中,选择回车和Tab键完成我们选择正确项目的动作.并确认或者放弃Session的会话状态(Complete)方法,这是相当必要的.

    如果并非这两个键,让VS先行对命令进行操作,确认VS已经处理完毕后,如果会话已经启动,并且处于输入状态,那么我们要针对输入进行条目的过滤,并及时选择最佳的项目,重新计算条目集合.

    如果我们捕获到了一个$符号,那么我们就要实时的创建一个Completion会话,并启动它,创建会话的过程由代理完成

    如果捕捉到正在向编辑器写入删除操作,确认当前的会话还没有被丢弃以后,就要继续执行过滤.

    int QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText)

    查询状态我们并不需要过多操作,由VS完成即可.

    本方法在执行完毕后,如果确认执行并处理完毕了,要返回一个VSConstants.S_OK结果.

    不得已加入一个章节(中),未完待续…

  • 相关阅读:
    从零开始——PowerShell应用入门(全例子入门讲解)
    详解C# Tuple VS ValueTuple(元组类 VS 值元组)
    How To Configure VMware fencing using fence_vmware_soap in RHEL High Availability Add On——RHEL Pacemaker中配置STONITH
    DB太大?一键帮你收缩所有DB文件大小(Shrink Files for All Databases in SQL Server)
    SQL Server on Red Hat Enterprise Linux——RHEL上的SQL Server(全截图)
    SQL Server on Ubuntu——Ubuntu上的SQL Server(全截图)
    微软SQL Server认证最新信息(17年5月22日更新),感兴趣的进来看看哟
    Configure Always On Availability Group for SQL Server on RHEL——Red Hat Enterprise Linux上配置SQL Server Always On Availability Group
    3分钟带你了解PowerShell发展历程——PowerShell各版本资料整理
    由Find All References引发的思考。,
  • 原文地址:https://www.cnblogs.com/kevinchoi/p/3931016.html
Copyright © 2011-2022 走看看