const confirmation = await this.dialogService.confirm({
message: items.length > 1 && items.every(s => s.isRoot) ? localize('confirmRootsMove', "Are you sure you want to change the order of multiple root folders in your workspace?")
: items.length > 1 ? getConfirmMessage(localize('confirmMultiMove', "Are you sure you want to move the following {0} files into '{1}'?", items.length, target.name), items.map(s => s.resource))
: items[0].isRoot ? localize('confirmRootMove', "Are you sure you want to change the order of root folder '{0}' in your workspace?", items[0].name)
: localize('confirmMove', "Are you sure you want to move '{0}' into '{1}'?", items[0].name, target.name),
checkbox: {
label: localize('doNotAskAgain', "Do not ask me again")
},
type: 'question',
primaryButton: localize({ key: 'moveButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Move")
});
Introduction
上一篇文章介绍了 vscode 的依赖注入机制。
在 vscode 中,依赖注入主要用于将服务注入到消费者对象当中,将一些基础能力提供给业务代码使用。
我们来拆解一下这句话:
HTTPService
的服务当中,这样在使用 HTTP 的时候,我们就不需要再去担心具体的实现问题了。从这个例子也可以看出,服务实际上也是一种解耦(decoupling)和关注分离(SOC)的手段。如果你对上面的某些概念不是很理解,你可以稍后去学习它们,这里的铺垫已经足够你理解本文的全部内容。
到这里我们了解了服务的含义,接下来就来看看 vscode 中用到了哪些服务吧!
我们之后才会讲到 Electron 的双进程架构。这里画了一个简图来方便你理解下面文章的内容,仅仅用于表达各个对象间的层次关系。
主线程中的服务
CodeMain
CodeMain
是 vscode 主线程中最底层的类,它在初始化时会创建以下这些服务:InstantiationService
我们在上一篇文章中就知道了这个类是负责实现依赖注入的EnvironmentService
环境变量服务,保存了诸如根目录、用户目录、插件目录等信息MultiplexLogService
多级日志服务ConfigurationService
LifecycleMainSercice
生命周期服务,封装了 Electron 的一写生命周期事件,使得消费者能够在这些生命周期钩子里做一些事情StateService
状态服务,它负责对 vscode 的 db 的读写RequestMainService
请求服务,负责发起 http 请求,背后调用的是 node 的 https 和 http 等模块ThemeMainService
负责编辑器主题相关SignService
应用签名服务等。
CodeApplication
会创建以下服务:FileService
文件存取WindowMainService
用于管理 vscode 的所有的窗口(打开、关闭、激活等等)UpdateService
根据运行平台的不同分别注入Win32UpdateService
DarwinUpdateService
等,负责应用程序的更新DialogMainService
对话框管理ShareProcessMainService
用于跨进程通讯DiagnosticsService
应用运行性能诊断LaunchMainService
IssueMainService
ElectronMainService
WorkspacesService
工作区管理服务MenubarMainService
菜单栏管理服务StorageMainService
存储BackupMainService
备份WorkspacesHistoryMainService
工作区历史URLService
URL 解析TelementryService
等。
渲染进程中的服务
DesktopMain
DesktopMain
是 vscode 渲染进程中最底层的类,它虽然自己并没有创建InitailizationService
,但是它创建了一个service
集合并将这个集合传递给Workbench
,由Workbench
创建了InitializationService
。在这一层次上提供的服务有:
MainProcessService
用于和主进程进行通讯ElectronEnvironmentService
Electron 环境变量WorkbenchEnvironmentService
Workbench 环境变量ProductService
LogService
日志RemoteAuthorityService
SignService
RemoteAgentService
FileService
文件存储服务等。
Workbench
Workbench 实际上就是我们能看到的 vscode 工作区的 UI。它会创建一个
InstantiationService
,除了将从DesktopMain
传递来的依赖注入项保存起来之外,它还要将全局单例注入项保存到InstantiationService
当中,代码如下:我们在上一篇文章讲过 vscode 的全局单例注入。
那么究竟有哪些服务会被注入进来呢?这其实是在入口文件中确定的。
在桌面端的 vscode 中,入口文件为 workbench.js,从中可以看到引入了脚本 vs/workbench/workbench.desktop.main,而这个脚本在全局注册了很多服务(即
#region --- workbench services
里面的内容),另外通过引入 workbench.common.main.ts,还引入了很多服务(注意#region --- workbench parts
里面的内容也是依赖注入项且和 UI 相关)。而在浏览器端的 vscode 中,入口文件则为 workbench.html,引入的主要脚本则是 vs/workbench/workbench.web.main。由于
Workbench
引入的全局单例服务实在是太多了,这里我们仅仅列举几个,感兴趣的话可以到入口文件中去查看:NativeLifeCycleService
这个服务封装了 Electron 窗口onBeforeUnload
和onWillUnload
的回调,让 vscode 的其余部分可以在窗口即将关闭之前做一些 clean up 的工作。等等。
为什么是依赖注入?
到这里,我们就对 vscode 中常用到的服务有哪些,它们是如何注入的,以及它们被注入的位置等问题有了一个大致上的认识。接下来的问题是,为什么 vscode 要使用依赖注入的方式来组织代码呢?
对于 vscode 来说,使用依赖注入模式有以下这些好处:
一、繁杂的功能点借助依赖注入被合理划分到不同的服务中,在横向上降低了模块间的耦合度,提高了模块内聚性。如果想要修改某些功能,很容易就能知道去哪里查找相关代码;对某个模块的修改,不会影响其他模块的开发。
二、消费者和服务通过接口解耦,对于服务消费者来说,它只要求被注入的类符合它的接口要求就可以了,并用不关心注入项究竟是如何实现的,即在纵向上降低了耦合度(其实就是依赖反转 DIP),这使得 vscode 的架构十分灵活,能够通过提供不同的服务来做到一些神奇的事情。
如果你有关注 vscode 的动态,那么你肯定知道今年他们搞的一个大动作就是推出了在完全在浏览器环境中运行的 Visual Studio Code Online(你可以通过在 vscode 项目中执行
yarn web
脚本启动它)。vscode 基于 Electron,所以可以访问一些桌面端才有的 module,但是在浏览器环境下并没有这样的模块。以
FileService
为例,在 Electron 中它需要 fs module,因此它注册的是一个diskFileSystemProvider
,但是在浏览器中我们不能使用模块,所以,在 vscode online 中,
FileService
注册的是一个remoteFileSystemProvider
。但对于
FileService
的消费者即DesktopMain
来说,它并不需要(也不应该)知道这种差别,它只要按照自己的需要调用符合IFileService
接口的服务就好了。再以“拖动文件位置前弹出对话框”功能为例,它在 Electron 和浏览器中展现出不同的样式:
但是业务层不需要了解各个平台上如何创建 dialog,只需要调用 IDialogService 提供的方法就可以了:
在打包不同平台上的 vscode 时,注入不同的 IDialogService:
总的来说,想要让 vscode 在浏览器中运行,只需要修改被注入的服务,然后通过不同的打包入口(已在上文中介绍)引入这些服务,无须修改上层代码。
三、依赖注入模式也带来了软件工程方面的一些好处。
Conclusion
这篇文章展示了 vscode 如何利用依赖注入系统提供各种基础功能来服务业务代码,对需要支持多平台的大型应用提供了一个优秀的模板。