VSPaste 的持续改造

Paste from Visual Studio 是一个很好用的代码高亮插件,对于使用Visual Studio编写代码,用WLW写博客的我们来说实在是不二之选。但是作者从2007年11月22日之后就停止了该插件的更新。而到这时,这个插件还有一些不如人意的地方。

很多人都尝试过对这个插件的改造工作(参见《自己改造VSPaste插件》《定制Paste from Visual Studio插件(上)》《定制Paste from Visual Studio插件(下)》),在他们研究的过程中,都由于.NET Reflector无法完成工作而借助了IL。而我在尝试的时候,却发现只是用 .NET Reflector 就完成了对插件源代码的改造工作。以下是具体方法:

用.NET Reflector打开VSPaste.dll文件,选择Export:

image

导出后就会得到一个C#的项目:

image 

然后,用Visual Studio打开这个项目。我的目的是解决以下几个问题:

  1. 当IDE的颜色主题做了修改之后(主要是背景),粘贴时自动将背景颜色设置到<pre>标签而不是<span>标签(一般情况下,背景色的改变都不会是针对关键字而是对整个文本编辑器的);
  2. 如果从代码中间段复制(缩进层次比较多),粘贴时自动将多余的缩进去除。

这两个工作在《自己改造VSPaste插件》中其实都做了,唯一的区别就是DiryBoy是去除背景,而我要保留背景,只是换个地方放置。

第一步,参照DiryBoy的代码直接实现缩进的去除,在VSPaste.cs文件中找到Undent方法,替换其中的源代码:

public static string Undent(string s) {
    var beginSpaces = new System.Text.RegularExpressions.Regex("^(?:<span[^>]*>|)( +)");
    var depth = beginSpaces.Match(s).Groups[1].Value.Length;
    if (depth == 0) return s;

    var space2trim = new System.Text.RegularExpressions.Regex
                     (
                         "^(<span[^>]*>|) {" + depth + "}"
                         , System.Text.RegularExpressions.RegexOptions.Multiline
                     );
    var tmp = space2trim.Replace(s, "$1");
    var garbageSpan = new System.Text.RegularExpressions.Regex
                     (
                        "^<span[^>]*></span>"
                        , System.Text.RegularExpressions.RegexOptions.Multiline
                     );
    return garbageSpan.Replace(tmp, String.Empty);
}

接下来是要将背景设置在<pre>标签中,从源代码里可以发现,<pre>标签的生成是在VSPaste.cs的CreateContent方法中,而背景颜色的判断和设置却不在这里,于是我首先想到的是让具体负责背景颜色的代码将颜色值传递出来,所以对CreateContent的方法我做了这样的修改:

if (Clipboard.ContainsData(DataFormats.Rtf))
{
    string allBackColor = string.Empty;
    string htmlContent = Undent(HTMLRootProcessor.FromRTF(
        (string)Clipboard.GetData(DataFormats.Rtf),
        out allBackColor));
    if(string.IsNullOrEmpty(allBackColor)) {
        newContent = string.Format(
            "<pre class=\"code\">{0}</pre><p></p>",
            htmlContent);
    }
    else
    {
        newContent = string.Format(
            "<pre class=\"code\" style=\"background:{0};\">{1}</pre><p></p>",
            allBackColor,
            htmlContent);
    }
    return DialogResult.OK;
}

原来的代码是:

if (Clipboard.ContainsData(DataFormats.Rtf))
{
    newContent = "<pre class=\"code\">" + Undent(HTMLRootProcessor.FromRTF((string) Clipboard.GetData(DataFormats.Rtf))) + "</pre><p></p>";
    return DialogResult.OK;
}

我的改变就是用为HTMLRootProcessor类的FromRTF方法增加一个传出参数,通过这个参数来获取背景色,从而可以在生成<pre>标签时直接使用。接下来,打开HTMLRootProcessor这个方法,通过阅读其源代码,可以知道,由int类型的颜色转成string类型是通过ColorProcessor的CssColor方法实现的,而每个HTMLRootProcessor方法里面有一个colors的私有字段就是ColorProcessor的实例。所以,我将HTMLRootProcessor的FromRTF方法改造如下:

public static string FromRTF(string rtf, out string allBackColor)
{
    string str;
    using (StringWriter writer = new StringWriter())
    {
        using (StringReader reader = new StringReader(rtf))
        {
            ProcessorStack stack = new ProcessorStack();
            HTMLRootProcessor processor = new HTMLRootProcessor(stack, writer);
            stack.Push(processor);
            Scanner scanner = new Scanner(reader);
            new Parser(scanner, stack).Parse();
            allBackColor = processor.background.HasValue ? processor.colors.CssColor(processor.background.Value) : string.Empty;
            str = writer.ToString();
        }
    }
    return str;
}

其中高亮的代码就是重点,首先我利用HTMLRootProcessor自带的background属性来存储背景色,然后调用colors实例的CssColor方法来为其赋值,如果没有背景色,则返回空字符串。最后一步,就是修改对background属性赋值的语句,使其只需要判断一次即可(整个代码总共就一种背景色,没必要反复赋值了)。这段代码在HTMLRootProcessor的SyncColors方法中,改造后的代码如下:

private void SyncColors(bool bgOnly)
{
    int? nullable;
    int? nullable2;
    if ((this.background != this.nextBackground) || ((((nullable = this.color).GetValueOrDefault() != (nullable2 = this.nextColor).GetValueOrDefault()) || (nullable.HasValue != nullable2.HasValue)) && !bgOnly))
    {
        if (this.color.HasValue || this.background.HasValue)
        {
            this.writer.Write("</span>");
        }
        this.color = this.nextColor;
        if(this.background.HasValue == false)
            this.background = this.nextBackground;
        if (this.color.HasValue || this.background.HasValue)
        {
            this.writer.Write("<span style=\"");
            if (this.color.HasValue)
            {
                this.writer.Write("color:");
                this.writer.Write(this.colors.CssColor(this.color.Value));
            }
            //if (this.background.HasValue)
            //{
            //    if (this.color.HasValue)
            //    {
            //        this.writer.Write(';');
            //    }
            //    this.writer.Write("background:");
            //    this.writer.Write(this.colors.CssColor(this.background.Value));
            //}
            this.writer.Write("\">");
        }
    }
}

其中做了两处修改,第一处是在对background赋值前判断其是否已经有值,如果有了,则不再进行赋值操作。第二处是将原来给<span>标签增加背景色的代码注释掉。

至此已经全部改造完毕,然后进行编译,顺利通过。将得到的VSPaste.dll覆盖到C:\Program Files\Windows Live\Writer\Plugins 目录下,启动WLW,随便从Visual Studio中赋值一段代码,粘贴,得到了本文中代码显示的效果。为了确认,打开没有改变颜色主题的Visual Web Developer 2008 Exress,复制一段代码,粘贴到WLW中,得到了没有背景色的代码样式(这里就不演示了)。至此,大功告成。

最后,对编译修改后的源代码做一点补充说明:如果生成配置是Debug,则没有任何问题,改成Release则一旦使用该插件就会造成WLW崩溃。如果要使用Release配置编译,则应该做如下调整:

image

这是经过我反复调整测试后得出的,关键在于“定义Debug常量”,其它选项都可选可不选。

本人水平有限,虽然通过尝试得出了正确结果,但是对于编译时发生的事情却不知其所以然,也无法解释其原因。望高人指点。

==================================================

修改后的VSPaste.dll下载:vspaste.zip