前言

  VScode 扩展插件 MayaPy 已经发布在应用市场上
  在这里我会记录一下一路开发遇到的坑,关于插件的使用可以参考 仓库内的readme.md文档

  7月因为要准备开发新生网站而因为下学期有数据库的实训要做,所以爆肝学习Vue.js
  哎。。。后来因为各种原因,新生网站不了了之了。
  到了8月为了能够入职大厂,开始爆肝 Unreal
  后来9月开始因为校招的settle down,unreal的学习就被搁置下来了。(果然还是得靠外力逼自己去学)
  在之后不久我就在网上找到了用 ptvsd 模块 在 VScode Debug python 代码的文章
  经过我自己摸索我也已经实现了文章的断点 Debug 效果,不过我想要简化设置的流程。
  另外也想实现更好的 Python 代码自动提示的效果,基于这些想法,开发一个自己的 VScode 插件就非常强烈了。
  于是 9 月份就花了很多时间在开发 VScode 插件上了。

Ptvsd 远程 Debug

  首先还是得先补充一下ptvsd是如何实现在 VScode 设置断点 Debug 的。
  这个包兼容 Python2 ,不需要安装就可以直接在 Maya 里面 import 使用。

  可以先去到 github 下载仓库
  先将文件下载下来,可以将src目录下的ptvsd目录拿出来,导入到Maya。
  github的官网其实有python的启动代码。

1
2
3
4
5
import ptvsd
# 默认情况开启 localhost:5678 端口
ptvsd.enable_attach()
# 可以传入元组来定义 地址和端口号
ptvsd.enable_attach(('localhost',3000))

  在Maya开启 ptvsd 模块之后,下一步就是利用 VScode 的 Python 模块进行连接。
  这里我们需要用到 Remote Debug 即 远程调试。
  VScode默认是带有这个 Debug 模式,按 F5 可以选择

下拉选项

  可以选择开启 Remote Debug , 不过直接点选可能会报错,因为没有开启相应的端口
  这里可以按 Esc 键,这样 VScode 会在当前的 workspace 下创建 lauch.json 文件
  json文件包含了 所有的调试设置,其中就有 远程调试 的设置内容

Remote Debug

  这里我们需要将 pathMappings 的属性修改一下,方便定位到测试文件目录。

Remote Debug

  为什么两个路径要设置成一样的?
  其实远程调试时用于给远程电脑进行调试使用的,不过我们其实是通过远程socket来访问Maya,因此两端都是在本地电脑上的。
  ${fileDirname}可以定位到当前执行文件的目录上

  下面只要在 Maya 里面开启 ptvsd 模块,就可以在 VScode 开启远程调试了。
  Maya开启 ptvsd 模块会出现命令行窗口闪烁出现的情况,这是正常现象。
  下面就可以开启 VScode 的 Debug 模式连接Maya了。

  连接成功之后 VScode 会一直处于 Debug 模式,并且暂定调试的按钮会变成断开连接的按钮。
  这种状态说明已经成功实现连接了。

Remote 连接

  下面就是设置断点,并且在Maya执行当前文件
  只要Maya运行到相关文件,断点就会被触发。

断点

  更多操作截图可以直接参考我的插件介绍页面。
  这里主要讲解 ptvsd Debug 的操作,有利于理解后续插件如何实现自动 Debug
  关于Debug配置,我的插件也添加了自动补全的代码块,非常方便

Remote 设置

VScode 插件开发

  VScode插件开发主要基于 Typescirpt 语言,有微软开发并维护
  Typescript 就是 Javascirpt 的进阶版,将弱类型的js变成强类型语言,写法更加规范,现在越来越受到大厂的青睐。
  因为我之前有做过前端开发,上手 typescript 其实没有什么压力,基本按照 ES6 的要求来写,ts 和 js 已经很接近了。
  当然用 JavaScript 开发或者其他语言都是可以的,但是配置会比较麻烦。

  开发第一步当然还是得从 hello world 教程开始,不过关于 VScode 开发网上并没有多少视频教程。
  因此只能依赖于官方的资料,不过好在很多插件都是开源的,也可以参考(借鉴)别人写好的插件进行学习。
  在开发的过程中,MayaCode 的插件就给了我不少的帮助。

VScode 文档

  在百度搜索 VScode API 可以找到 微软官方的插件开发入门的文档

Getting started

  根据官方教程认真阅读 Getting Started 的内容,基本就可以理解如何实现一个简单的 hello world 扩展了。

  需要补充的是 VScode 的基于 Yeoman 作为项目脚手架,方便快捷生成不同类型扩展的模板。
  一个 VScode 扩展插件其实本质就是一个 npm 包,都需要用到 package.json
  而且 VScode 很多配置多需要写在 package.json 里面,也需要依赖 vscode 相关的 npm 包。
  之前因为看过 node.js 开发相关的教程,对于这一步的理解还是没有问题的。

  最后推荐 VScode 官方维护的多个不同类型的小型插件案例 github仓库
  如果要开发不同类型的插件,这里的简单案例可以更快理解清楚要如何配置和开发。

  另外遇到一些开发问题要如何解决呢?
  这个时候就是 面向Stack Overflow开发的时候了,
  这个编程社区实在是太有用了,尽管关于 VScode 开发的问题数量不多,但是我的需求基本都能找到相关的问题来解决。

Getting started

  点击 vscode-extensions 标签可以将问题范围锁定在 VScode 开发上。
  然后再去找有回到绿色的问题去查看相关的问题解决方案,有官方维护人员在上面作答,还是挺不错的。
  有时候找不到想要的答案也可以在 bing 上搜索,说不定 github 的 issue 也有相关的解决方案。

MayaPy 开发之前

  其实 MayaPy 的开发可谓是一波三折,在开发这个插件之前,
  其实我还弄了一个 MayaDev 插件的开发方案,当时是想要不依赖于 Python 插件来实现自动补全。
  大家也可以去查看 MayaDev 的 github仓库,不过我暂时已经放弃开发这个项目了。
  为啥我想要跳过 Python 插件来实现补全?
  其实我在自动补全的文章里面就吐槽过,VScode 的自动补全生成实在是太慢了,经常会陷入 loading 的状态。
  这主要归咎于 Maya 各种库的模块实在太大,生成补全列表需要花费很多时间去加载。
  不过最让人诟病的是缓存记载没有弄好,因此每一次自动补全都需要等上十几秒,
  这种开发体验是不可忍受的,因此我打从一开始就打算自己来实现自动补全效果。
  而且官方的 VScode example 中也有相关的自动补全案例,所以我就铆足了劲去开发这个插件。
  而且MayaCode这个插件也实现了 Mel 语言的自动补全,因此有可以借鉴学习的案例,我觉得自己实现自动补全问题不大。

  我当时分析过 Python 插件加载缓慢的原因,主要是因为补全列表是动态生成的,毕竟模块的内容可能有变更。
  因此补全列表每次补全都需要读取相关的补全代码来确认是否有变化。
  但是其实对于Maya的模块来说是不必的,因为Maya模块都是写好的 API ,而且内置的都是闭源的(除了pymel),想要修改也做不到。
  因此我的想法是直接在插件加载的时候就将相关的数据加载到内存当中。
  后续调用只需要获取内存中的数组就可以了,果然这个触发的速度就非常的快。
  但是在什么地方触发相应的补全又难倒我了,最开始我在弄 cmds 补全,这个其实非常好弄,因为是 MEL 语句转过来的
  没有那么多类、返回值的补全,只需要用正则匹配到 cmds 对应的变量名称就可以了。
  因此我当时是实现了 cmds 的快速自动补全的,而且基于这个生成的 自动补全 还可以让用户设置显示的内容,可以说是非常灵活。
  但是当时遇到了一个不知道怎么解决的问题,我想要将 文档 的 网址链接显示出来并且保留可以超链接的状态。
  然而当时却没能在 completionItem 里面实现这个效果。。。

  最大的打击要算是写 OpenMaya 补全的时候,我发现 OpenMaya 有很多类,这个时候正则匹配已经远远不够了。
  我需要实现变量的追踪,这样才可以有正确的代码补全。
  为了解决这个问题,我特意研究了 Jedi 自动补全模块的代码,想弄清楚这个牛逼的自动补全是怎么实现的。
  当时有用断点简单测试过 Jedi 模块,不过 Jedi 的底层逻辑非常绕,因为他需要遍历代码,逐一生成代码树。
  Jedi内部定义了许多不同类型的 类 来区分不同代码代表的东西,这个东西非常难,我当时简单测试了一下,感觉要写成 Typescript 简直难于上青天。
  如果是用 Python 外调 Jedi 模块,那就和 Python Extension 所实现的自动补全是一样的,我担心还是会出现性能问题。
  于是到这里我开始研究 Extension 的依赖,因为既然 Python Extension 已经实现了自动补全的功能,难道就没有相关的 API 来调用这些补全状态吗?
  不过结果是令人失望的, Python Extension 只保留了两个 API,可以在源码中找到。

python API

  buildApi 函数会在 activate 函数作为返回值,activate 的返回值就是当前插件返回可调用的 API.
  最后是暴露了 readydebug 两个函数
  ready返回 Promise 对象用来触发插件完成加载之后的代码
  debug则是返回 ptvsd_launcher.py 文件的执行参数,还需要在 node.js 里面外调 python 进行执行。
  所以这里并没有补全相关的 API (:з」∠)

  这个时候我开始动摇了,因为这样开发下去, OpenMaya 的自动补全将会是非常大的工作量,感觉没必要搞到这么复杂。
  既然插件的依赖都研究了,何必就直接使用 Python 的 自动补全效果呢?
  说不定就真香了。。。

  不过我还是没有放弃,既然 Python 解析很可能效率低,而且等于重复实现 Python Extension 所做的东西,没有意义。
  难道 npm 就没有相关的 代码 解析的模块吗?
  没想到我真的找到了一个解析代码的模块 tree-sitter
  它其实就是一个语法树生成器,毕竟跨语言处理,也就只能实现将语法进行对应划分,类似Jedi的自动补全是没有的,但是辅助我实现自动补全绰绰有余了。
  可以去 tree-sitter 的 playground 查看代码语法树的生成效果,我觉得这个方案还是可行的。

playground

  而且 VScode 也有一个插件是基于 tree-sitter 的,简直是最好的教材。
  在这里还学习到了 wasm 文件格式,一种编译成二进制的js文件,牛逼有木不有!

  然而我始终还是逃不过真相定律。。。
  我平时在公司开发代码的时候也添加了自动补全,只是因为太慢,一直都是手打的。
  另外由于公司是断网的,因此我用不了微软官方的 Python Language Server 只能退而求其次使用 Jedi 补全。
  最近突然发现 pymel 的自动补全挺快的,明明 pymel 还有大量的说明,我开始疑惑为啥 cmds 的补全明明就没啥说明,咋就这么慢呢?
  我研究了 pymel 的补全结构发现是将模块拆成多个文件的形式,而官方的 cmds 就直接将上千个函数放到了一个文件里面。
  于是我花了一些时间将 cmds 切分成多个文件,奇迹出现了,cmds的补全明显加快了。
  这个时候真香开始了,我发现解决 cmds 的问题之后,在Jedi模式下只要经过最初的加载,后续的补全都挺快的,最多也就是等 1、2 秒
  嗯,想到 tree-sitter 浩大的工程量,我还是妥协了。

  于是全新的 MayaPy 方案才正式起航,这个过程真的是一波三折。

MayaPy 开发

  经过前面各种瞎折腾,我开发这个插件的时候目标就已经很明确了
  我不打算取代现有的插件,而是在现有的插件上做得更好
  因此我的插件就直接依赖了 MayaCodePython 两个插件。
  MayaCode 给我解决将代码发送到 Maya 以及 Mel 处理的问题。
  Python 插件给我解决自动补全的问题。

  剩下的坑就涉及到 VScode 开发的坑了。
  在这里真的很感谢 MayaCode 提供的 logger 方案,可以直接将信息输出到对应 output Panel 上
  如果用 console.log 的话,估计找输出得累死我了。

Python 模块修改属性

  Python 的自动补全其实非常好解决,只需要将补全代码的路径添加到 extraPath 设定里面就可以了。
  我可以设置成每一次激活插件就查询属性是否有相关的补全路径,如果没有就插入补全路径更新 json。

  获取路径这种操作不难, node.js 提供了 Path 模块,操作基本和 Python os.path 类似。
  问题是如何更新 setting.js ,我一度以为要用 fs 模块将数据写入到 setting.js 里面
  后来才发现 update 函数可以完成这个操作。

update

MayaCode 与 Maya 交互

  下面要实现如何自动完成 Ptvsd 模块加载并且开启 Debug 的方案。
  这个过程也是相当曲折,最后确定下来的方案是这样子的

  1. 先通过 socket 检测 maya 7001 端口是否开启
  2. socket 检测 ptvsd 模块是否开启
  3. 判断是否已经处于 远程调试 模式
  4. 如果上面操作都完成则将执行当前文件的代码

  MayaCode 可以利用 socket 将代码发送到maya进行执行,因此我方案是触发命令的一瞬间生成一份加载 ptvsd 模块的python代码发送到Maya进行ptvsd激活。
  而且 ptvsd 模块不需要安装也可以使用,因此只需要定位路径并且 import 就可以在 Maya 里面激活。
  因此我在插件包里面内置 ptvsd 模块,然后用下面的代码自动定位到相应的为位置执行。

ptvsd attach

  这里遇到了不少坑!!

如何在执行命令的时候获取当前文件的路径

  我在网上搜索到的时在回调函数的参数上返回相应 uri 路径

uri

  然而这个 uri 参数只有在右键菜单下才会传入,如果是敲 ctrl + P 的命令行则没有任何参数返回。
  这我就没办法直接执行命令来获取文件路径了,最后我只好在 package.json 文件配置上去掉命令行的显示。
  后来我在研究 Python Extension 的源码的时候 偶然发现 document.uri 选项
  而 document 可以通过 vscode.window.activeTextEditor 来获取,这样就完美解决我的需求。


maya 端口 socket 检测后 端口被占用

  最初socket测试我参考了 MayaCode 的内部代码,通过 net 模块 createConnection 进行访问,
  Maya 的端口是否开启正是通过这个方法,
  但是检测完成之后就会发现 mayaPort 一直被占用,再也无法通过 MayaCode 将可执行代码发送到 Maya 里面了
  这个问题还卡了我好久,因为这个占用导致的结果是,MayaCode 一直显示代码在发送,但是一直没有数据返回,Maya也没能够执行代码。
  后来我是看到 MayaCode 内部使用了 destroy 方法才明白,这个 Connection 会一直连接
  导致后面 MayaCode 发送代码的时候无法连接上了。因此后续都加入销毁 这个连接的操作.

port


ptvsd socket检测报错从而切换到不同的端口。

  最初检测 ptvsd 模块是否开启也是使用了上面的 createConnection
  但是ptvsd的端口一旦被非法访问,就会直接报错,并且自动生成一个新的端口。
  这让我无法检测 ptvsd 端口到底在哪里开启了。
  于是我只好查 Python Extension 的源码,看看它是如何实现代码 交接 的
  最后在 RemoteDebugServerv2.ts 找到了内部的连接方案,

ptvsd socket

  他是通过 socket.connect 的方式进行连接测试。
  通过这个方法的确可以判断 ptvsd 模块是否开启了。

ptvsd socket

  如果被拒绝说明没有开启,可以发送开启代码
  如果没有被拒绝,则说明已经开启了。


发送代码必须保存到本地

  这个坑是最坑的,我当时开发 Extension 的时候是打开一个 Untitle 文件,然后利用 MayaCode 发送代码的
  因此也理所应当需要关闭临时打开的文件,我在 插件开发模式下 关闭文件是不会弹出是否保存的文件的查询的。
  因此我开发很顺利,直到我发布到线上了,一测试才发现居然还有这种BUG。
  所以最后只好参照 MayaCode 的实现方案,在 os.tmpdir() 的系统临时目录里添加 python 代码文件。


ptvsd连接文件 和 代码执行文件必须分开

  本来我打算两个执行代码都写在一个文件里面实现的。
  没想到这样的话,第一次开启 ptvsd 是没有问题的,但是第二次执行命令的时候仍然还是执行 ptvsd 激活的代码。
  这里实在搞不懂为什么,我想到可能是还没有写入文件数据就执行了文件,因此代码还是上一次的代码。
  于是我加入了删除机制,每次执行完之后就删除文件,然而还是存在同样的问题。
  没有查到问题到底是出在哪里的,但是将代码执行分成两个文件就可以解决这个问题。
  于是我最后还是无奈分成两个不同的文件进行保存。

分文件


VScode 的 message 无法换行

  VScode 内置的在右下角显示消息的窗口不知道如何实现代码换行
  试验了 \n<br> 换行都不可以,最后只好估摸着文字的长度来换行 (:з」∠)

MayaPy 发布

  最后插件开发完成之后就是激动人心的发布到应用市场的环节了。
  发布到应用市场,人人都可以使用我开发的插件,这种感觉真的美滋滋~
  发布和打包插件其实也是有官方文档的,照着做就可以了。

  安装了 vsce 模块之后就可以本地打包自己的插件也可以注册之后将插件发布到线上。
  可以通过 .vscodeignore 实现打包过滤无关的文件,减少打包文件 .vsix 的大小。

总结

  历时半个月的插件开发,差不多就结束了。
  插件目前还需要加强自动补全的功能,基本的需求都已经完成了。
  真的非常感谢 开源 的 VScode ,很多模块不懂也可以查看别人插件的代码进行摸索。
  如果大家对插件有什么建议和想法的,欢迎到 Github 的仓库发布 issue ,也可以通过 820472580@qq.com 邮箱来联系我
  希望这款插件能够对大家有帮助!~