代码改变世界

定制Paste from Visual Studio插件(下)

2009-12-16 14:27 Jeffrey Zhao 阅读(...) 评论(...) 编辑 收藏

上一篇文章里我们进行了简单的实验,验证了通过修改IL生成新插件的可行性,不过我们要做的事情还有很多,因为我们实际要做的事情其实是……插入行号。这需要我们补充新的逻辑,并且对CreateContent进行修改。那么我们又该如何写这大段大段的IL呢?没关系,其实这些事情不懂IL也可以做。

添加行号

首先,我们需要写一个AddLines方法,修改一下HTML:

public static string AddLines(string html)
{
    var lines = html.Trim().Split('\n');
    string pattern = 
        "<span style=\"color:black; font-weight:bold;\">{0:" + 
        new String('0', lines.Length.ToString().Length) +
        "}:  </span>";

    for (int i = 0; i < lines.Length; i++)
    {
        lines[i] = String.Format(pattern, i + 1) + lines[i];
    }

    return String.Join("\n", lines.ToArray());
}

这段方法的作用是在每行HTML之前加入一个<span />来显示行号,其中通过总行数来计算行号的“位数”,这样便可以在显示如“01”、“02”这样的行号。那么,这段方法又如何给CreateContent方法调用呢?

生成结构相同的IL

别急,还是先来简单观察一下目前CreateContent方法的IL代码——不求看懂,但求了解个两行内容:

.method public hidebysig virtual instance valuetype [System....
        CreateContent(class [System.Windows.Forms]System.Win...
                      string& newContent) cil managed...
{
  // Code size       101 (0x65)
  .maxstack  4
  .locals init (valuetype [System.Windows.Forms]System.Windo...
           bool V_1)
  IL_0000:  nop
  .try
  {
    IL_0001:  nop
    IL_0002:  ldsfld     string [System.Windows.Forms]System...
    IL_0007:  call       bool [System.Windows.Forms]System.W...
    IL_000c:  ldc.i4.0
    IL_000d:  ceq
    IL_000f:  stloc.1
    IL_0010:  ldloc.1
    IL_0011:  brtrue.s   IL_0042
    ...
    IL_0029:  call       string HTMLRootProcessor::FromRTF(string)
    IL_002e:  call       string VSPaste.VSPaste::Undent(string) 
    IL_0033:  ldstr      "</pre>"
    IL_0038:  call       string [mscorlib]System.String::Concat(string,
                                                              string,
                                                              string)
    ...

别的不管,看到两个call指令吗?相信即便您不懂IL,也可以推测出它们是在“调用方法”。从中我们也可以发现,这个call指令……不就是指定一个方法的名称(包括命名空间)和参数吗?既然如此,我们构建一个类似代码“结构”也实在太容易了:

public class HTMLRootProcessor
{
    public static string FromRTF(string s) { return null; }
}

namespace VSPaste
{
    public class VSPaste
    {
        public static string AddLines(string html) { /* ... */ }

        public static string Undent(string s) { return null; }

        public DialogResult CreateContent(
            IWin32Window dialogOwner,
            ref string newContent)
        {
            try
            {
                if (Clipboard.ContainsData(DataFormats.Rtf))
                {
                    var content = (string)Clipboard.GetData(DataFormats.Rtf);
                    var html = Undent(HTMLRootProcessor.FromRTF(content));
                    var withLines = AddLines(html);
                    newContent = "<pre class=\"code\">" + withLines + "</pre>";

                    return DialogResult.OK;
                }
            }
            catch
            {
                MessageBox.Show(
                    "VS Paste could not convert that content.",
                    "VS Paste Problem",
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Hand);
            }

            return DialogResult.Cancel;
        }
    }
}

于是,我们把这段代码随意放在某个程序集中,然后同样使用ildasm.exe获得其IL代码:

.class public auto ansi beforefieldinit VSPaste.VSPaste
       extends [mscorlib]System.Object
{
  .method public hidebysig static string 
          AddLines(string html) cil managed
  {
    // Code size       121 (0x79)
    ...
  } // end of method VSPaste::AddLines

  ...

  .method public hidebysig instance valuetype [System.Windows.Forms]Syste
          CreateContent(class [System.Windows.Forms]System.Windows.Forms.
                        string& newContent) cil managed
  {
    // Code size       97 (0x61)
    .maxstack  4
    ...
      IL_001d:  call       string HTMLRootProcessor::FromRTF(string)
      IL_0022:  call       string VSPaste.VSPaste::Undent(string)
      IL_0027:  stloc.1
      IL_0028:  ldloc.1
      IL_0029:  call       string VSPaste.VSPaste::AddLines(string)
      IL_002e:  stloc.2
      IL_002f:  ldarg.2
      IL_0030:  ldstr      "<pre class=\"code\">"
      IL_0035:  ldloc.2
      IL_0036:  ldstr      "</pre>"
      IL_003b:  call       string [mscorlib]System.String::Concat(string,
                                                                  string,
                                                                  string)
    ...
  } // end of method VSPaste::CreateContent

} // end of class VSPaste.VSPaste

可以看到,在CreateContent方法中,也有和之前相同的FromRTF方法与Undent方法的调用,以及我们新的AddLines方法。因此,我们只要打开VSPaste.il,先把AddLines方法的IL复制到VSPaste类中,然后用新的CreateContent方法体(即从.maxstack开始的部分)替换旧的实现即可。

成果

保存VSPaste.il,使用ilasm.exe生成dll,再把它复制到Windows Live Writer的Plugins目录中。那么我就第一个试用吧:

01:  public static string AddLines(string html)
02:  {
03:      var lines = html.Trim().Split('\n');
04:      string pattern = 
05:          "<span style=\"color:black; font-weight:bold;\">{0:" + 
06:          new String('0', lines.Length.ToString().Length) +
07:          "}:  </span>";
08:  
09:      for (int i = 0; i < lines.Length; i++)
10:      {
11:          lines[i] = String.Format(pattern, i + 1) + lines[i];
12:      }
13:  
14:      return String.Join("\n", lines.ToArray());
15:  }

效果如何,还不错吧?当然,这个逻辑其实还不够成熟,不过通过这个思路,您可以把VSPaste修改为任何需要的样子。当然,这两篇文章只是一种“体验”,这种修改方式并不“健康”也不值得提倡。而且我忽然意识到,如果我们真只是为了使用VSPaste中RTF转HTML的功能,其实也可以简单地引用它,然后调用其中的方法……反正都是public的……

所有代码

相关文章