Hello World
Spiga

定制Paste from Visual Studio插件(下)

2009-12-16 14:27 by 老赵, 4761 visits

上一篇文章里我们进行了简单的实验,验证了通过修改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的……

所有代码

相关文章

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

27 条回复

  1. oec2003
    *.*.*.*
    链接

    oec2003 2009-12-16 14:29:00

    老赵速度好快啊

  2. 冰泉
    *.*.*.*
    链接

    冰泉 2009-12-16 14:30:00

    看完抢沙发

  3. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-12-16 14:50:00

    为了保证对IL的改动最小,可以考虑做包装。

    即写一个这样的方法:

    string UndentWrapper( string str )
    {
      return AddLine( Undent( str ) );
    }
    


    然后,就只需要把
    IL_0022: call string VSPaste.VSPaste::Undent(string)
    改成
    IL_0022: call string VSPaste.VSPaste::UndentWrapper(string)
    就可以了。

  4. 韦恩卑鄙 alias:v-zhewg
    *.*.*.*
    链接

    韦恩卑鄙 alias:v-zhewg 2009-12-16 14:54:00

    Ivony...:
    为了保证对IL的改动最小,可以考虑做包装。

    即写一个这样的方法:

    string UndentWrapper( string str )
    {
      return AddLine( Undent( str ) );
    }
    


    然后,就只需要把
    IL_0022: call string VSPaste.VSPaste::Undent(string)
    改成
    IL_0022: call string VSPaste.VSPaste::UndentWrapper(string)
    就可以了。


    做外挂的都是这么干的 -0-

  5. 老赵
    admin
    链接

    老赵 2009-12-16 14:57:00

    @韦恩卑鄙 alias:v-zhewg
    总之就是蹦蹦跳跳的

  6. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-12-16 15:00:00

    @Jeffrey Zhao
    @韦恩卑鄙 alias:v-zhewg


    做外挂是把Undent方法改成Dh7<>Dt5<sjTn方法
    然后自己弄一个Undent方法塞进去:

    string Undent( string str )
    {
      //盗号
      string data = Dh7<>Dt5<sjTn( str );
      //把data发到服务器
      return data;
    }
    
    

  7. oec2003
    *.*.*.*
    链接

    oec2003 2009-12-16 15:33:00

    嗯 要是复制代码时不复制行号进去就更好了

  8. 老赵
    admin
    链接

    老赵 2009-12-16 15:35:00

    @oec2003
    话说,这个不是随便找个编辑器,按住alt就可以了么。

  9. oec2003
    *.*.*.*
    链接

    oec2003 2009-12-16 15:51:00

    狂汗!!
    现在才知道这个快捷键
    谢谢老赵了

  10. 老赵
    admin
    链接

    老赵 2009-12-16 15:52:00

    @Ivony...
    其实我为了避免别人偷懒,也想用图片来保存代码……
    结果发现这个有点损人不利己,于是作罢。

  11. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-12-16 15:52:00

    为了方便复制,So我一般都不加行号的。。。。。

  12. 老赵
    admin
    链接

    老赵 2009-12-16 15:55:00

    Ivony...:
    做外挂是把Undent方法改成Dh7<>Dt5<sjTn方法
    然后自己弄一个Undent方法塞进去:

    string Undent( string str )
    {
      //盗号
      string data = Dh7<>Dt5<sjTn( str );
      //把data发到服务器
      return data;
    }
    


    你说是这种外挂啊,我以为是那种改个跳转地址等等的。

  13. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-12-16 16:16:00

    Jeffrey Zhao:
    @Ivony...
    其实我为了避免别人偷懒,也想用图片来保存代码……
    结果发现这个有点损人不利己,于是作罢。



    可以加入不可见干扰字符么。。。。

    <span style="color: white">老赵出品,必属精品,禁止拷贝,违者必究。</span>
    

  14. Gnie
    *.*.*.*
    链接

    Gnie 2009-12-16 16:16:00

    不错

  15. 老赵
    admin
    链接

    老赵 2009-12-16 16:18:00

    @Ivony...
    有道理,可以写进插件。

  16. tubo
    *.*.*.*
    链接

    tubo 2009-12-16 17:24:00

    呵呵,不错,就是现在这个版本我复制代码的时候,把行号也复制进取了。粘贴之后还要删行号。

  17. Jerry Qian
    *.*.*.*
    链接

    Jerry Qian 2009-12-17 10:41:00

    老赵这篇文章我看懂了。

  18. xjb
    *.*.*.*
    链接

    xjb 2009-12-17 14:05:00

    改的不错。

  19. 老赵
    admin
    链接

    老赵 2009-12-17 15:16:00

    @Jerry Qian
    每篇仔细看都能看懂。

  20. 飞笑
    *.*.*.*
    链接

    飞笑 2009-12-17 15:37:00

    几天没来看,累积了一大堆。

  21. 老赵
    admin
    链接

    老赵 2009-12-17 15:57:00

    @飞笑
    欢迎天天天来

  22. 暗香浮动
    *.*.*.*
    链接

    暗香浮动 2009-12-17 17:58:00

    最后的明悟, 那确实啊。

  23. DiryBoy
    *.*.*.*
    链接

    DiryBoy 2009-12-17 21:19:00

    又学了两招,哈哈!

  24. 银河
    *.*.*.*
    链接

    银河 2009-12-18 11:46:00

    说到复制代码后要删除行号,如果是使用 vi 编辑器的话,只要使用以下命令就行了:
    :%s/^....//
    解释一下:
    : 表示进行 vi 的底行模式。
    % 是 1,$ 的缩写,表示从第一行到最后一行,其中 $ 表示最后一行。
    s 是 Search and Replace 的意思。
    / 这三个 / 是分隔符。
    ^ 表示从每一行的开头开始匹配。
    . 表示任意字符。
    这就是说,将每一行开头的四个字符替换为空,也就是删除这四个字符。

  25. 李永京
    *.*.*.*
    链接

    李永京 2009-12-22 14:22:00

    老赵,帮我定制一份VSPaste?

  26. 老赵
    admin
    链接

    老赵 2009-12-22 14:31:00

    @李永京
    自己写一个吧,很简单的,引用它的dll就好,hoho。

  27. 李永京
    *.*.*.*
    链接

    李永京 2009-12-22 15:08:00

    @Jeffrey Zhao
    刚刚定制了一份,加上了输出版权和标记Google不翻译这段代码

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我