一、定义

我们知道,我们的插件是服务于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方法,像这样:

如何捕捉到$符号,就要依靠下面这个接口了,在解决方案上新建一个目录,命名为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结果.

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

VS Extension+NVelocity系列(三)——让VS支持 NVelocity的智能提示(中)的更多相关文章

  1. VS Extension+NVelocity系列(二)——让VS支持 NVelocity的智能提示(上)

    一.基础概念 应该庆幸的是,VS的插件是靠着MEF实现而不是MAF,这让你所做的工作减轻了许多.如果在这之前,您已经了解了MEF的原理,我想对于VS插件的编写,您应该是很容易就能理解的.看看几个VS2 ...

  2. vs2015 不支持javascript的智能提示高亮

    有些人安装了vs2015后发现居然不支持javascrpt的高亮功能,连工具-选项-文本编辑器里面的javascript也没有了,楼主也碰到这么个情况了,估计是有与装了多个版本的原因,楼主电脑安装了V ...

  3. vscode 支持 threejs 的智能提示

    VSCode Typings and Intellisense: Dummy Learning VS-Code 1 Jun 20, 2016 Updated on Jun 20 2016 for 1. ...

  4. 让NVelocity做更多的事,VS Extension+NVelocity系列

    我不知道园子里到底有多少人喜欢使用NVelocity这个模板引擎,其实说实话,如果现在让我选,我对Razor的喜好要比NVelocity或者T4等等的模板引擎更多一些,当然了,个人看法而已.只是我在公 ...

  5. Eclipse安装插件支持jQuery智能提示

    Eclipse安装插件支持jQuery智能提示 最近工作中用到jQuery插件,需要安装eclipse插件才能支持jQuery智能提示,在网上搜索了一下,常用的有三个插件支持jQuery的智能提示:1 ...

  6. 前端构建大法 Gulp 系列 (三):gulp的4个API 让你成为gulp专家

    系列目录 前端构建大法 Gulp 系列 (一):为什么需要前端构建 前端构建大法 Gulp 系列 (二):为什么选择gulp 前端构建大法 Gulp 系列 (三):gulp的4个API 让你成为gul ...

  7. Web 开发人员和设计师必读文章推荐【系列三十】

    <Web 前端开发精华文章推荐>2014年第9期(总第30期)和大家见面了.梦想天空博客关注 前端开发 技术,分享各类能够提升网站用户体验的优秀 jQuery 插件,展示前沿的 HTML5 ...

  8. MySQL并发复制系列三:MySQL和MariaDB实现对比

    http://blog.itpub.net/28218939/viewspace-1975856/ 并发复制(Parallel Replication) 系列三:MySQL 5.7 和MariaDB ...

  9. WCF编程系列(三)地址与绑定

    WCF编程系列(三)地址与绑定   地址     地址指定了接收消息的位置,WCF中地址以统一资源标识符(URI)的形式指定.URI由通讯协议和位置路径两部分组成,如示例一中的: http://loc ...

随机推荐

  1. Markdown快速上手指南

    Markdown快速上手指南 1.Markdown介绍 markdown可以实现快速html文档编辑,格式优没,并且不需要使用html元素. markdown采用普通文本的形式,例如读书笔记等易于使用 ...

  2. 超好用的一个JQUERY分页器-jpaginate

    jpaginate是一个自带滑动动画效果的jQuery分页插件,用户可以通过单击或只是悬停在箭头上的使页码滑动显示. 你可以用下面的方式调用插件: $(elementID).paginate() 您可 ...

  3. 笨办法学Python(三十三)

    习题 33: While 循环 接下来是一个更在你意料之外的概念: while-loop``(while 循环).``while-loop 会一直执行它下面的代码片段,直到它对应的布尔表达式为 Fal ...

  4. 如何查找BAPI SD_SALESDOCUMENT_CHANGE里的字段对应的数据库存储表

    BAPI函数SD_SALESDOCUMENT_CHANGE可以让我们很方便地通过ABAP代码来修改Sales Order. 其输入参数ORDER_HEADER_IN的类型是BAPISDHD1, 里面包 ...

  5. DOM笔记(十二):又谈原型对象

    因为之前谢过一篇关于原型对象的笔记:浅谈JavaScript中的原型模式.现在我又重新看到这个话题,对原型有了进一步的理解,所以,又要谈谈原型对象. 一.理解原型对象 创建的每一个函数都有一个prot ...

  6. @Inject 注入 还是报空指针

    @Inject 注入 还是报空指针  发布于 572天前  作者 子寒磊  1435 次浏览  复制  上一个帖子  下一个帖子  标签: 无 @IocBean@Service("userM ...

  7. http长链接

    之前说过http的请求是再tcp连接上面进行发送的,那么tcp连接就分为长连接 和 短连接这样的概念,那么什么是长链接呢?http请求发送的时候要先去创建一个tcp的连接,然后在tcp的连接上面发送h ...

  8. 项目部署到自己的IIS上

    一般我们只能在本机上才可以开到我们的项目,这个是不需要连网的 如果想让我们的项目在网站中打开,别人也可以看到,就需要把我们的项目部署到服务器上了,输入IP就可以看到我们的项目 发布项目 然后发布网站 ...

  9. rest_framwork组件

    rest framework介绍 (CBV(class base views)) 在开发REST API的视图中,虽然每个视图具体操作的数据不同,但增.删.改.查的实现流程基本套路化,所以这部分代码也 ...

  10. docker官方仓库下载镜像

    官方仓库镜像地址:https://hub.docker.com/search/ 以下载mysql为例 进入到详情页后我们看到有很多Tags 我们选择5.7.25版本进行下载 # docker pull ...