原因
我已经一年多没更新博客了。最近用Arduino做了点有意思的东西,准备写个博客。打开尘封已久的博客公园,发现因为Windows Live Writer停止更新,博客公园推荐的客户端变成了Open Live Writer。

Arduino IDE语法突出显示如下:
VSCode中的语法突出显示效果如下:
如你所见,Arduino IDE语法高亮明显没有VSCode丰富。更何况Arduino IDE没有代码智能提示。这年头,如果写代码没有智能提示,你就少了半条命。所以,当我需要写很多Arduino代码的时候,我总是用VSCode来做。顺带一提,VSCode支持Arduino,请参考本文:用visual studio代码启用arduino intellisense)。
Arduino IDE被复制为HTML格式
Arduino IDE中有一个叫“复制成HTML格式”的功能。上述语法高亮函数签名复制的HTML代码如下:
void Matrix4::writeSprite
如您所见,使用了不推荐的字体标签。
如果你喜欢Arduino IDE的语法高亮,可以在写博客的时候把Open Live Writer切换到Source模式,把要复制的HTML代码粘贴到你想插入的地方。不过这个有点麻烦,会在编辑模式和源码模式之间切换。
VSCode代码编辑插件
但是网上找不到VSCode对应的代码编辑插件,要分享的东西就放在一边,开始折腾。
方案1: VSPaste
看到VSCode,马上想到VS,马上想到VSPaste。上面这句话是模仿鲁迅的。原话是:看到短袖,马上想到白胳膊,马上想到全裸。。。
VSCode和VS都是微软的,所以我觉得VSPaste应该也支持VSCode。我对VSPaste比较熟悉,开头还专门写过一篇文章:一次发现Windows Live Writer的VSPaste插件,丢失RTF格式信息的经历。
第一次尝试失败了。
然后找到Open Live Writer的安装目录。有很多方法可以找到安装目录,包括但不限于:使用任务管理器,使用一切,使用开始菜单快捷方式。我熟悉在安装目录下建立一个新的插件目录,并复制VSPaste.dll。然后放心重启Open Live Writer。纳尼,什么都没有!
第二次尝试失败了。
博客公园推荐的,应该有对应的官方插件。我将寻找官方Windows Live Writer和Open Live Writer之间的区别。找到一篇文章:OpenLiveWriter代码插件。
看完文章,我明白了,之所以不行,是因为接口不匹配。那么这个问题就解决了,反编译VSPaste的源代码,建立一个新的项目,把接口换成新的。
就在我准备自己动手的时候,无意中在GitHub上发现了一个项目:LiveWriter。VSPaste,将VSPaste移植到Open Live Writer。
于是我兴奋地下载了相应的文件,复制到插件目录,然后满怀信心地重启OLW。然而,这一次什么也没有。
第三次尝试失败了。
为了找出上次加载插件失败的原因,我下载了LiveWriter的源代码。粘贴并复制生成的文件。奇怪的是,无论是Debug还是Release,插件除了找不到图标外,都可以正确加载。
更神奇的是,下载的文件是用ILSpy反编译的,与源代码对比,没有发现明显的区别。这下真的呼机狗了,也引起了我的好奇心。所以我下载了OLW的源代码并编译了它。这一次,很顺利,没有风浪,一气呵成。
设置启动项目,找到生成的目录,将下载的文件复制到Plugins目录,正式开始查找原因。在解决方案管理器中寻找插件,我在其中找到了一个PluginLoader文件和一个名为LoadPluginsFromDirectory的函数。我的直觉告诉我,应该是它。点击断点并开始调试。果然断点中断,一步一步调试到案发现场:一个名为LoadFromWithRetry的函数。
修改LoadFromWithRetry函数的代码以获取异常详细信息,获得以下异常信息:试图从网络位置加载程序集,这将导致该程序集在以前版本的中受到沙盒保护。的。NET框架。此版本的。默认情况下,NET framework不启用CAS策略,因此这种加载可能是危险的。如果此加载不打算用沙盒保护程序集,请启用loadFromRemoteSources开关。更多信息请见file/tupian/20220929/ref=go&linkid=prd=&pver=&sbp=&os=0&over=&plcid=&olcid=0&clcid=&ar=&sar=&o1=&o2=&o3=&o4=&o5= linkId = 155569。
好像是从网络上加载的程序集,但也不对。我用ILSpy看过下载的文件,但是没有从网络上下载过。这确实是个大问题,问题暂时陷入了僵局。
第四次尝试失败了。
就在我一筹莫展的时候,无意中在点击下载文件的属性里发现了这样的东西:
选中unlock,单击OK,然后再次开始调试。果然在LoadFromWithRetry函数的异常处理上没有中断。真是山重水复疑无路,柳暗花明又一村。
我还没来得及高兴,又一个错误对话框跳了出来:
接下来就是前面漏图标的错误。导航到刚刚报告错误的行:
通过分析代码中位图的构造:
我们可以确定加载图片的路径是程序集名称加上导出设置中的ImagePath。通过ILSpy反编译生成的文件,查看汇编信息,可以发现里面没有资源。
打开项目文件,查看对应图片的属性,发现其生成操作为none,将其改为嵌入资源。
重新生成,复制文件,开始调试,一切顺利完成,插件显示正确。
终究没用。
再次自信,打开VSCode,复制代码,在OLW点击VSPaste。但是,视图中仍然没有任何内容。
最后一次折腾之后,我的第一反应是确定剪贴板中是否包含RTF数据,因为VSPaste的原理是确定剪贴板中是否有RTF数据,如果有,就转换成HTML。
创建一个新的WinForm项目,并使用剪贴板。确定剪贴板是否包含RTF数据。哦,不,路被堵住了。仅此而已。
尝试2:找到现有的插件。
寻找信息
在搜索引擎中搜索VSCode Open Live Writer,没有找到现有的插件。我以为Arduino IDE有复制HTML的功能。我上网搜了一下有没有把VSCode复制成HTML的资料,就找到了这篇文章:从VSCode复制成HTML。
设置VSCode
在复制代码之间,需要设置VSCode。具体来说,您可以打开编辑器的带语法高亮显示的复制功能。有关详细信息,请参考从VSCode复制为HTML。
向…学习
从VSCode看了Copy As HTML中的代码后,基本理解了代码的思想。写的功能比较全面,包括行号显示,还有文件名、展开、折叠等一些选项。选项如下:
在使用过程中,还发现了一个小bug。具体来说,当VSCode的代码中有换行符时,生成的html代码会出现br后跟p的情况,这样会导致空行前后的行粘在一起,然后代码会高于行号。
艰难的决定
这个是基于html和js的。我不想每次插入代码都要通过浏览器打开网页,然后复制回OLW的源代码。而且功能丰富,我用不了那么多。所以我最终决定自己写一个VSCode插件,在OLW点击就可以插入。
尝试三:自己写插件。
新项目
去做吧。当然,第一步是建立新的项目。如下所示:
创建一个新的类库项目,命名为VSCodePaste,并添加对OpenLiveWriter的引用。Api在里面。
OpenLiveWriter.Api.dll位于OLW安装目录中。
添加一个VSCodePaste类并设置相应的功能。
除了GUID和PublisherUrl,我修改了LiveWriter的所有其他元数据。虚拟粘贴..
[insertableContentSource,writer插件]从ContentSource继承VSCodePaste,并重写CreateContent方法。

公共重写dialog result create content { return dialog result。取消;}此时,只返回一个值。
将作为图标的文件复制到项目中,并将生成的操作设置为属性页中的嵌入资源。
我用尽一切办法在VSCode的安装目录的图标中寻找。
第一代错误
根据错误详情,可以得知项目框架版本号低于OpenLiveWriter.Api.dll,因此将项目版本号改为4.6.1。
图标再也找不到了。
图标的名字是VSCodePaste.icon.png,这个名字是指live writer VSPaste.icon.png。VSPaste但是,将生成的文件复制到OLW的插件目录后,OLW重新打开,插件图标没有找到。
打开ILSpy,检查程序集,发现资源中的图标名为VSCodePaste.VSCodePaste.icon.png,而在LiveWriter中。这个图标被命名为VSCode.icon.png。奇怪的是,属性中的设置都完全相同。那么问题出在哪里?一定有什么不同。
网上搜嵌入资源也找不到什么干货。换成嵌入式资源做Bing搜索,找到一篇讲的很清楚的文章:了解可视化studio.net中的嵌入式资源,引用原文如下:
检查LiveWriter的默认名称。VSPaste 空,而且是空。原因是众所周知的:
项目的文件名称为VSCodePaste.icon.pngVSVS在生成文件名时,文件名前自动添加了默认的VSCodePaste between 空名称,这样生成的程序集中的资源名就变成了vscodepaste.vscodepaste.icon.png位图构造函数寻找VSCodePaste.icon.png,但是程序集中没有相应名称的资源,问题就出现了。所以有一个解决办法,要么把默认的名字空改成空,要么把文件改成icon.png。我当然选择第二种。
在VS中,当默认名称空不是空时,除非卸载项目,直接更改项目文件,否则不能将默认名称空更改为空。猜猜那个LiveWriter。VSPaste将默认名称空设置为空,以链接文件并避免复制图标。
HTML不一致
继续写插件,很快就发现了第一个问题:用剪贴板获取的Html。项目中的GetData与js中VSCode的onpaste事件中Copy As HTML得到的Html不一致。
js中获取的Html如下,可以明显看出是一个Html文档:
WinForm项目中获取的Html如下。可以看到,除了Html文档,前面还有一些奇怪的字符:
从图中我感觉前面的group: separated key-value pairs挺像Html的描述。于是我找相关资料确认,又找了HTML剪贴板格式来验证我的猜测。通过使用visual c++将html代码添加到剪贴板,它解释了如何使用VC++将Html代码设置到剪贴板。所以,你猜,是浏览器做了相应的处理,把描述去掉了。
提取HTML片段
既然浏览器可以处理,我们自己也可以处理,可以处理的更彻底,直接从片段中提取内容。因为只有Fragment中的内容才有意义,它的外层永远是body和html。
从HTML剪贴板格式可以得出结论,只要得到StartFragment和EndFragment的偏移量,就可以直接找到子串。代码如下:
私有静态只读Regex description Regex = new Regex+";内部静态字符串提取{ var matches = DescriptionRegex。火柴;if {返回字符串。空的;} int startIndex =-1;int endIndex =-1;var descriptions = matches[0]. value . split;foreach { var pairs = description。分裂;var key = pairs[0];var值= pairs[1];if { startIndex = int。解析;继续;} if { endIndex = int。解析;继续;} } if {返回字符串。空的;}返回html。子串;}代码很简单。用正则表达式提取描述部分,然后找到段的开头和结尾,再提取子串。
停在这里还是前进?
子串已经提取出来了,好像可以放在一起用了。毕竟保持了基本风格。但是,VSPaste和blog park生成的代码包含在pre标签中。想了想,还是保持一致吧。将代码放在pre标记中,并在中间使用span来指示样式。
接下来的问题是直接把片段代码转换成pre风格?经过思考,我决定增加一个中间层,原因如下:
直接代码转换必然会保存层次信息,因为HTML片段本身就是一个将数据和展现分离的层次结构,代码片段和pre标签都可以看作是同一数据的不同展现。此外,前标签的表达也可能改变。选择XElement作为中间层,因为HTML也可以看作是一种非标准的XML。
转换为XElement
转换的关键点是识别HTML元素的结构,即属性、内部文本以及何时开始和结束。幸运的是,我们针对的是VSCode code代码的这种专有结构。所以逻辑可以写的很简单。
分层结构处理
层次结构可以用栈来表示。其处理包括以下情况:
开始:将标记元素添加到堆栈顶部,并在标记结束之前合并到堆栈顶部:将内部文本添加到堆栈顶部。标签结束:层次结构的处理代码如下:
private void begin tag { var matches = TagStyleRegex。火柴;合同。断言;var tagName = matches[0]。组["tagName"]。价值;var style = matches[0]。组[“样式”]。价值;//虽然没有样式,但值是字符串。空的;var tagElement = new x element);if { _ rootElement = tagElement} else { tagStack。Peek . Add}标记堆栈。推;} private void AppendTag { var tag element = new x element;标记堆栈。Peek . Add} private void EndTag { var tag element = tag stack。偷看;合同。断言;标记堆栈。Pop} private void AppendTagText { var tagElement = tag stack。偷看;if ) { tagElement。添加);}}其中使用的正则表达式如下:
private static readonly Regex TagStyleRegex = new Regex " ")";正则表达式的样式部分是可选的。没有样式时,通过组[“样式”]获得的值。值为空。
相应的文本处理
遇到的字符:表示标签块声明完成或者正式结束,需要匹配遇到的字符':{ if { end tag };某人(somebody的简写)清晰;}else{ var tagInfo = sb。ToStringif { AppendTag} else { BeginTag} sb。清晰;} isTagEnd = false打破;}默认:sb。追加;打破;} num = reader。阅读;}}将XElement转换为pre
有三层的东西
该部分将XElement的层次结构生成代码,VSCode的代码包括三层:
第一层的P是全局样式,包括背景和字体设置。第二层的p和br,代表第一行第三层的跨度,代表该行的一部分,即颜色相同的文字代码如下:
公共静态字符串转换{ using ) { writer。写));ConvertFragment作家。写;回归作家。ToString} } private static void convert fragment { foreach)//丢弃包含style { ConvertLine作家。写;} }私有静态void convert line { foreach){ if { convert span;}}}处理span
值得一提的是,根据包含的空单元的数量,span的处理可以分为三种类型:
不包含空单元格:按原样添加。所有空单元格:添加相同数量的空单元格。部分空单元格:在文本前添加空单元格,删除span节中的空单元格并添加span节,在文本后添加空单元格。代码如下:
私有静态void converts pan { var value = span element。价值;var替换=值。替换;int startSpaceCount = 0;对于{ if { writer。写;//writestartspacecount++;} else { break} } //#160;长度为6,空间长度为1,如果替换一个,长度将减5 int endSpaceCount =/5-startSpaceCount;如果{作家。写);} else { if//会有其他文字{ writer。写,替换。trim));对于{作家。写;}}}}一招用于计算结束空字符数:结束空字符=/ 5-开始空字符。其原理如下:
End 空字符数= total 空字符数-start 空字符数。空单元的总数=/ 5。即每替换一个空单元格,其长度由6变为1,总共减少5个字符。风格展示
void matrix 4::write sprite { int column count = sprite[0];introwCount = sprite[1];if { for { intc = start column+I;ifsetColumn} } else { for for { intc = start column+I;intr = startRow+j;ifsetDot);}}}完整代码
博客花园:VSCodePaste
复活节彩蛋
使用VSCode的语法突出显示开关进行复制
通过对比两个图,我们可以知道,启用开关后,剪贴板中支持的HTML格式更多了。
复制为VS的Html
在博客中插入VS代码的时候,我随便研究了一下VS复制的Html剪贴板格式,发现和我们生成的样式非常接近。
所以,其实不用VSPaste也可以自己写一个VSHtmlPaste。其实我也写了一个。有兴趣的话可以参考前面的代码链接。写的时候有个问题,就是VS生成的Html中的end Html,EndFragment,EndSelection比实际多了6个。这个问题我就不在这里追究了,不然真的成了老太太的裹脚布。
补充:VS的Copy As Html功能需要安装插件。我的是VS2019,装的是生产力电动工具2017/2019。
参考链接
使用Visual Studio代码启用Arduino智能感知
一次发现Windows Live Writer的VSPaste插件丢失RTF信息的经历
OpenLiveWriter代码插件
直播写手。虚拟粘贴

GitHub-OpenLiveWriter/OpenLiveWriter:Windows Live Writer的开源分支
从VSCode复制为HTML
HTML剪贴板格式
使用Visual C++将HTML代码添加到剪贴板
了解Visual Studio中的嵌入式资源。网


