IntelliJ IDE 插件开发 | (五)VFS 与编辑器
系列文章
- IntelliJ IDE 插件开发 |(一)快速入门
- IntelliJ IDE 插件开发 |(二)UI 界面与数据持久化
- IntelliJ IDE 插件开发 |(三)消息通知与事件监听
- IntelliJ IDE 插件开发 |(四)来查收你的 IDEA 使用报告吧
- IntelliJ IDE 插件开发 |(五)VFS 与编辑器
前言
在前几篇文章中主要介绍了关于 IntelliJ IDE 插件开发的基础知识,这部分内容对开发一些小功能的插件的开发已经足够。不过,如果想要开发一些与具体编程语言相关的、提升开发效率的插件(例如MybatisX
),那么前几篇的内容就不足以支撑了。而从本篇开始,则会介绍实现相关功能所需要的知识: VFS、编辑器、PSI、自定义语言等,最后再以两三个插件的实战开发(例如老生常谈的代码生成)进行结尾,本文涉及到的完整代码已上传到GitHub。
VFS(Virtual File System)
VFS(虚拟文件系统)可以看作是 IntelliJ 平台对各种类型(本地磁盘的文件、DIFF 信息文件、远程文件)文件操作的封装,通过提供一致的 API 以及文件变更事件,让开发人员可以专注于对文件的处理。此外之所以使用虚拟
二字,也是因为我们在对文件操作时并没有直接修改源文件,而是修改了源文件所对应的快照,然后 VFS 会通过同步或者异步的方式去修改源文件。关于 VFS 主要有以下注意点:
-
文件快照是应用级别的,因此一个文件即使被多个项目使用也只会对应一份快照。
-
文件快照内容和源文件内容并非实时对应,例如文件已从资源管理器中删除,但 IntelliJ 平台只有接收并处理了文件删除事件后才会从快照中删除指定文件。
-
VFS 通过操作系统的 File Watcher 去感知文件的变化(基于时间戳),可以通过下述步骤去查看被监听的文件根节点:
-
如果在代码中访问了被忽略的文件(例如下图中配置的),VFS 就会直接加载并返回文件的内容而非是快照的内容:
-
如果需要保证代码是在文件刷新后完成,则可以使用以下方式:
val virtualFile = e.getData(PlatformDataKeys.VIRTUAL_FILE) virtualFile?.let {it.refresh(true, true) {// 文件刷新完成} }
refresh 的方法签名如下,第三个参数 postRunnable 用于传递我们要执行的代码:
/*** The same as {@link #refresh(boolean, boolean)} but also runs {@code postRunnable}* after the operation is completed. The runnable is executed on event dispatch thread inside write action.*/ public abstract void refresh(boolean asynchronous, boolean recursive, @Nullable Runnable postRunnable);
获取 VirtualFile 对象的方式
以下内容来自官网:
Context | API |
---|---|
Action | AnActionEvent.getData(PlatformDataKeys.VIRTUAL_FILE) AnActionEvent.getData(PlatformDataKeys.VIRTUAL_FILE_ARRAY) for multiple selection |
Document | FileDocumentManager.getFile() |
PSI File | PsiFile.getVirtualFile()(may return null if the PSI file exists only in memory) |
File Name | FilenameIndex.getVirtualFilesByName() |
Local File System Path | LocalFileSystem.findFileByIoFile() VirtualFileManager.findFileByNioPath()/ refreshAndFindFileByNioPath() (2020.2+) |
在上面的示例中就是使用的第一种方式:AnActionEvent.getData(PlatformDataKeys.VIRTUAL_FILE) 。
VirtualFile 在文件的基础内容上还增加了类型、文件系统等扩展信息:
class VFSAction : AnAction() {override fun actionPerformed(e: AnActionEvent) {val virtualFile = e.getData(PlatformDataKeys.VIRTUAL_FILE)virtualFile?.let {it.refresh(true, true) {// 文件刷新完成Utils.info("""文件路径: ${it.path}文件类型: ${it.fileType}文件系统: ${it.fileSystem}文件后缀: ${it.extension}文件时间戳: ${it.timeStamp}""".trimIndent())}}}
}
监听文件变更事件
只需要在项目启动监听中注册我们的文件变更监听事件即可:
class ProjectStartListener: ProjectActivity {override suspend fun execute(project: Project) {project.messageBus.connect().subscribe(VirtualFileManager.VFS_CHANGES, object : BulkFileListener {override fun after(events: MutableList<out VFileEvent>) {for (event in events) {Utils.info("变更的文件: ${event.file?.path}")}}})}}
VFS 的监听是应用级别,因此当打开多个项目的时候,每个项目中的文件变更事件都会被所有的项目共享,因此需要注意对文件进行过滤处理。
通过以上简单的代码我们就可以获取所有的文件变更事件,做我们想做的事了(例如统一在文件前增加版权信息)。
编辑器
在第四篇文章中为了实现对用户的编码活动的统计,我们简单使用了 Editor 对象,主要是对其文档和光标活动的监听。而本文文章则会详细介绍关于 Editor 中的一些基础概念,主要是几个模型对象和文档对象,在正式介绍前,先说一下获取 Editor 对象的两种方式:
// 通过 project 获取
var editor = FileEditorManager.getInstance(project).selectedEditor
// 通过 Action 的事件对象获取
val editor = e.getData(PlatformDataKeys.EDITOR)
下面正式开始介绍 Editor 中的一些对象
Document
document 对象主要提供了获取和替换文件文本的一些方法(还可以添加对文档内容的监听):
下面会结合模型对象介绍其使用。
CaretModel(插入符模型)
CaretModel(插入符模型) 用于获取编辑器中光标所处的位置,如果细分还可以分为 Logical Position(逻辑位置)和 Visual Position(视觉位置)。下面以一个简单的代码样例展示区别:
val editor = e.getData(PlatformDataKeys.EDITOR) ?: return
val caretModel = editor.caretModel
Utils.info("""逻辑位置:<br/>${"-".repeat(20)}<br/>行号: ${caretModel.logicalPosition.line + 1}<br/>列号: ${caretModel.logicalPosition.column + 1}<br/><br/>视觉位置:<br/>${"-".repeat(20)}<br/>行号: ${caretModel.visualPosition.line + 1}<br/>列号: ${caretModel.visualPosition.column + 1}<br/>
""".trimIndent())
可以看出来逻辑位置对应光标在文件中所处的真实位置(5 行 1 列),而视觉位置则如表面意思,由于存在折叠代码块(中间3行),所以行号为 3。除此之外,可以看到上述代码中,不管是caretModel.logicalPosition
还是caretModel.visualPosition
在获取行号和列号的时候都在结果加 1,这是因为行号和列号在代码中是从 0 开始计算。这里要注意的是,上面的折叠代码只是影响视觉位置结果的一种情况,如果文件开启自动断行(Soft-Wrap line),也同样会影响视觉位置的结果:
使用类似阿拉伯语这种右向文字也会影响逻辑位置和视觉位置的结果。
除了以上两种定位方式,我们还可以通过caretModel.offset
直接获取光标所在位置的全局偏移量:
这里可以看到第 3 行开始位置对应的偏移量是 22(20个显示的字符加上两个换行符,空白字符都会被统计)。
除了单光标,多光标的时候(例如按住 alt 结合鼠标左键进行多行选择),每一个光标的位置也是按照上述规则进行计算:
caretModel.allCarets.forEach { Utils.info("""偏移量:<br/>${"-".repeat(20)}<br/>偏移量: ${it.offset}<br/>""".trimIndent())
}
最后以 Doucument 和 CaretModel 结合使用的例子结尾:
// 写操作需要放入 WriteCommandAction.runWriteCommandAction 中
WriteCommandAction.runWriteCommandAction(e.project) {// 插入字符串并移动光标位置到结尾val msg = "庄周de蝴蝶"document.insertString(caretModel.offset, msg)caretModel.moveToOffset(caretModel.offset + msg.length)
}
上述代码实现了在光标处插入指定文本信息并移动光标位置的效果。
InlayModel(嵌入模型)
InlayModel(嵌入模型)用于在代码行中嵌入各种信息,例如下图中msg:
就属于嵌入信息,通过使用嵌入信息可以在不改变文本内容的情况下给用户一个直观的提示内容:
除了上面这种使用方式,我们还可以使用 InlayModel 模拟各种 gpt 插件生成代码的效果,代码及效果如下(使用到了 caretModel 中的偏移量和视觉位置等内容):
// 清除所有嵌入信息
val inlayModel = editor.inlayModel
inlayModel.getInlineElementsInRange(0, editor.document.textLength).forEach { Disposer.dispose(it) }
inlayModel.getBlockElementsInRange(0, editor.document.textLength).forEach { Disposer.dispose(it) }
// 分别增加单行和多行嵌入信息
val offset = caretModel.offset
val column = caretModel.visualPosition.column
inlayModel.addInlineElement(offset, DemoRender(editor, "庄周de蝴蝶"))
inlayModel.addBlockElement(offset, InlayProperties(),DemoRender(editor, mutableListOf("first line", "second line", "third line"), column.toFloat()))
// 移动光标位置到初始位置
caretModel.moveToVisualPosition(VisualPosition(caretModel.visualPosition.line, column))class DemoRender<T>(private val editor: Editor,private val renderText: T,private var wordCount: Float = 0f
): EditorCustomElementRenderer {// 设置字体private val font = Font("Microsoft YaHei", Font.ITALIC, editor.colorsScheme.editorFontSize)override fun calcWidthInPixels(p0: Inlay<*>): Int {// 获取渲染内容的宽度, 如果是多行文本则取最长文本行的宽度return when (renderText) {is String -> calcTextWidth(renderText)is MutableList<*> -> renderText.maxOfOrNull { calcTextWidth(it.toString()) } ?: 0else -> 0}}override fun calcHeightInPixels(inlay: Inlay<*>): Int {// 获取渲染内容的高度, 如果是多行文本则需要将行高乘以行数return when (renderText) {is MutableList<*> -> super.calcHeightInPixels(inlay) * renderText.sizeelse -> super.calcHeightInPixels(inlay)}}override fun paint(inlay: Inlay<*>, g: Graphics, targetRegion: Rectangle, textAttributes: TextAttributes) {val g2 = g.create() as Graphics2DGraphicsUtil.setupAAPainting(g2)textAttributes.foregroundColor = JBColor.GRAYval lineHeight = editor.lineHeight.toDouble()val fontBaseline = ceil(font.createGlyphVector(getFontMetrics().fontRenderContext, "中文").visualBounds.height)val linePadding = (lineHeight - fontBaseline) / 2.0val offsetX = targetRegion.xval offsetY = targetRegion.y + fontBaseline + linePaddingval lineOffset = 0g2.font = fontg2.color = JBColor.GRAYwhen (renderText) {is String -> {g2.drawString(renderText, offsetX.toFloat(), (offsetY + lineOffset).toFloat())}is MutableList<*> -> {// 多行文本渲染的时候设置缩进val tabSize = editor.settings.getTabSize(editor.project)val startOffset = calcTextWidth("Z") * (wordCount + tabSize)renderText.forEach {g2.drawString(it.toString(), startOffset, (offsetY + lineOffset).toFloat())g2.translate(0.0, lineHeight)}}else -> return}g2.dispose()}private fun calcTextWidth(text: String): Int {return getFontMetrics().stringWidth(text) }private fun getFontMetrics(): FontMetrics {val editorContext = FontInfo.getFontRenderContext(editor.contentComponent)val context = FontRenderContext(editorContext.transform, AntialiasingType.getKeyForCurrentScope(false), editorContext.fractionalMetricsHint)return FontInfo.getFontMetrics(font, context)}
}
这里需要知道 addInlineElement 用于添加单行的嵌入信息,addBlockElement 用于添加多行嵌入信息,渲染内容需要实现 EditorCustomElementRenderer 类中的 paint 方法即可。通过使用 Graphics2D 对象,我们可以添加文本,图形甚至是图片信息,这里就不再逐一介绍。
SoftWrapModel(自动断行模型)
SoftWrapModel(自动断行模型)主要用于获取自动断行相关信息,其包含的方法较少,也很少会用到,简单了解即可,如下是一个简单的使用及效果:
val softWrapModel = editor.softWrapModel
Utils.info("""是否开启自动断行: ${softWrapModel.isSoftWrappingEnabled}<br/>当前行是否存在自动断行: ${softWrapModel.getSoftWrapsForLine(caretModel.offset).isNotEmpty()}<br/>
""".trimIndent())
MarkupModel(标记模型)
MarkupModel(标记模型)主要用于用于将指定行(或者某个区间)设置为高亮,下面直接展示其使用方式和效果,很容易理解和使用:
val markupModel = editor.markupModel
markupModel.removeAllHighlighters()
// 设置当前行为红色高亮背景
val lineAttr = TextAttributes()
lineAttr.backgroundColor = JBColor.RED
val line = caretModel.logicalPosition.line
markupModel.addLineHighlighter(line, HighlighterLayer.ERROR, lineAttr)// 设置指定范围为绿色高亮背景
// 最后一个参数指定为 HighlighterTargetArea.EXACT_RANGE 则为精确范围
// 指定为 HighlighterTargetArea.LINES_IN_RANGE 则会将偏移量所在的整行都进行设置
val rangeAttr = TextAttributes()
rangeAttr.backgroundColor = JBColor.BLUE
markupModel.addRangeHighlighter(0, 6, HighlighterLayer.SELECTION, rangeAttr, HighlighterTargetArea.EXACT_RANGE)
SelectionModel(选中模型)
SelectionModel(选中模型)用于处理编辑器中文本选中相关的操作,下面是一个简单的使用样例:
// 先移除所有文本选中, 然后将偏移量为 0 ~ 6 的内容进行选中
val selectionModel = editor.selectionModel
selectionModel.removeSelection(true)
selectionModel.setSelection(0, 6)
Utils.info("""选中的文本内容: ${selectionModel.selectedText}<br/>选中文本的开始和结束位置: (${selectionModel.selectionStart}, ${selectionModel.selectionEnd})<br/>
""".trimIndent())
FoldingModel(折叠模型)
FoldingModel(折叠模型)用于对代码块进行折叠操作,通过结合 SelectionModel,可以很方便地对选中的文本进行折叠,下面是使用方式及效果:
val selectionModel = editor.selectionModel
val foldingModel = editor.foldingModel
// 批量折叠操作需要放在 runBatchFoldingOperation 中
foldingModel.runBatchFoldingOperation {// 移除所有的折叠foldingModel.allFoldRegions.forEach { foldingModel.removeFoldRegion(it) }// 对选中文本进行折叠并设置显示的提示文本foldingModel.addFoldRegion(selectionModel.selectionStart, selectionModel.selectionEnd, "庄周de蝴蝶")
}
IndentsModel(缩进模型)
IndentsModel(缩进模型)用于获取文本中的缩进信息,使用方式也很简单,如下所示:
val indentsModel = editor.indentsModel
val guide = indentsModel.caretIndentGuide ?: return
Utils.info("""缩进级别: ${guide.indentLevel}<br/>开始行: ${guide.startLine + 1}<br/>结束行: ${guide.endLine + 1}<br/>
""".trimIndent())
ScrollingModel(滚动模型)
ScrollingModel(滚动模型)可以用于获取编辑器的可视区域范围以及执行滚动操作,使用方法如下:
val scrollingModel = editor.scrollingModel
scrollingModel.scrollToCaret(ScrollType.CENTER)
scrollingModel.runActionOnScrollingFinished {scrollingModel.visibleArea.let {Utils.info("""可视区域左上角坐标: (${it.x}, ${it.y})<br/>可视区域宽度: ${it.width}<br/>可视区域高度: ${it.height}<br/>""".trimIndent())}
}
编辑器事件
在第三篇文章中我们简单了解了 IntelliJ 中的事件监听,当时只介绍了项目的启用与关闭事件,这里再简单介绍一下关于编辑器的事件。
EditorActionHandler
IntelliJ 平台默认为我们提供了一些编辑器事件,例如复制、粘贴等,可以在IdeActions
类中看到:
相应地,使用方式也很简单,只需要实现 EditorActionHandler 类重写其中的 doExecute 方法即可,下面代码展示了对编辑器的复制事件进行监听并提示:
class EditorCopyListener: EditorActionHandler(), ProjectActivity {override suspend fun execute(project: Project) {// 注册复制监听EditorActionManager.getInstance().setActionHandler(IdeActions.ACTION_EDITOR_COPY, this)}override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) {// 将选中的文本加入到粘贴板中, 这里使用前文中提到的 selectionModeleditor.selectionModel.copySelectionToClipboard()// 显示复制文本Messages.showInfoMessage("Copy text: ${CopyPasteManager.getInstance().getContents<String>(DataFlavor.stringFlavor)}", "Copy")}
}
这里需要注意的是,在重写了编辑器的事件后(这里是复制事件),则该事件就不再生效了,因此这里通过editor.selectionModel.copySelectionToClipboard()
这行代码手动了复制的操作,在重写其它编辑器事件的时候也需要注意这个问题。
TypedHandlerDelegate
除了使用 IntelliJ 中已经定义好的事件,还可以通过实现 TypedHandlerDelegate 来对键盘中输入的字符进行监听(无法监听空白字符,例如回车、TAB等,也无法在出现编码提示时进行监听):
class KeyboardListener: TypedHandlerDelegate() {override fun charTyped(c: Char, project: Project, editor: Editor, file: PsiFile): Result {Messages.showInfoMessage("Input character: $c", "Input")return Result.CONTINUE}}
同时需要在plugin.xml
中进行配置:
<extensions defaultExtensionNs="com.intellij"><typedHandler implementation="cn.butterfly.vfs.listener.KeyboardListener"/>
</extensions>
总结
本文主要介绍了关于 VFS 和编辑器相关的知识,其中编辑器相关的内容则需要重点关注,这是开发各类插件都离开的东西,毕竟我们开发的插件大多都要和编辑器里的代码打交道,同时如果有错误之处,也欢迎一起交流讨论。
题外话
本来准备下篇文章就开始介绍关于 PSI 的知识,不过想一下还是决定先写一篇在第三篇文章中提到的关于内部工具(能极大提升初学者开发插件的效率,我个人是这么认为的)使用的文章,然后再继续介绍关于 PSI 的内容。
相关文章:

IntelliJ IDE 插件开发 | (五)VFS 与编辑器
系列文章 IntelliJ IDE 插件开发 |(一)快速入门IntelliJ IDE 插件开发 |(二)UI 界面与数据持久化IntelliJ IDE 插件开发 |(三)消息通知与事件监听IntelliJ IDE 插件开发 |(四)来查收…...

金融OCR领域实习日志(一)
一、OCR基础 任务要求: 工作原理 OCR(Optical Character Recognition,光学字符识别)是指电子设备(例如扫描仪或数码相)检查纸上打印的字符,经过检测暗、亮的模式肯定其形状,而后用…...

CC++编译和链接介绍
介绍 C语言的编译和链接是将源代码转换为可执行文件的两个关键步骤。以下是详细的流程: 编译过程(Compilation) 预处理(Preprocessing): 编译器首先对源代码进行预处理,这个阶段处理#include包…...

Element-UI中的el-upload插件上传文件action和headers参数
官网给的例子action都是绝对地址,我现在需要上传到自己后台的地址,只有一个路由地址/task/upload 根据 config/index.js配置,那么action要写成/api/task/upload,另外也可以传入函数来返回地址:action"uploadUrl()"。 …...

在IntelliJ IDEA中通过Spring Boot集成达梦数据库:从入门到精通
目录 博客前言 一.创建springboot项目 新建项目 选择创建类型编辑 测试 二.集成达梦数据库 添加达梦数据库部分依赖 添加数据库驱动包 配置数据库连接信息 编写测试代码 验证连接是否成功 博客前言 随着数字化时代的到来,数据库在应用程序中的地位越来…...

docker相关
下载Ubuntu18.04文件64位(32位安装不了MySQL) https://old-releases.ubuntu.com/releases/18.04.4/?_ga2.44113060.1243545826.1617173008-2055924693.1608557140 Linux ubuntu16.04打开控制台:到桌面,可以按快捷键ctrlaltt 查…...

生产力工具|卸载并重装Anaconda3
一、Anaconda3卸载 (一)官方方案一(Uninstall-Anaconda3-不能删除配置文件) 官方推荐的方案是两种,一种是直接在Anaconda的安装路径下,双击: (可以在搜索栏或者使用everything里面搜…...

大模型学习与实践笔记(十二)
使用RAG方式,构建opencv专业资料构建专业知识库,并搭建专业问答助手,并将模型部署到openxlab 平台 代码仓库:https://github.com/AllYoung/LLM4opencv 1:创建代码仓库 在 GitHub 中创建存放应用代码的仓库ÿ…...

Vulnhub靶机:FunBox 5
一、介绍 运行环境:Virtualbox 攻击机:kali(10.0.2.15) 靶机:FunBox 5(10.0.2.30) 目标:获取靶机root权限和flag 靶机下载地址:https://www.vulnhub.com/entry/funb…...

性能优化(CPU优化技术)-NEON指令介绍
「发表于知乎专栏《移动端算法优化》」 本文主要介绍了 NEON 指令相关的知识,首先通过讲解 arm 指令集的分类,NEON寄存器的类型,树立基本概念。然后进一步梳理了 NEON 汇编以及 intrinsics 指令的格式。最后结合指令的分类,使用例…...

【极数系列】Flink环境搭建(02)
【极数系列】Flink环境搭建(02) 引言 1.linux 直接在linux上使用jdk11flink1.18.0版本部署 2.docker 使用容器部署比较方便,一键启动停止,方便参数调整 3.windows 搭建Flink 1.18.0版本需要使用Cygwin或wsl工具模拟unix环境…...

仓储管理系统——软件工程报告(需求分析)②
需求分析 一、系统概况 仓库管理系统是一种基于互联网对实际仓库的管理平台,旨在提供一个方便、快捷、安全的存取货物和查询商品信息平台。该系统通过在线用户登录查询,可以线上操作线下具体出/入库操作、查询仓库商品信息、提高仓库运作效率ÿ…...

立创EDA学习:PCB布局
参考内容 【PCB布线教程 | 嘉立创EDA专业版入门教程(11)】 https://www.bilibili.com/video/BV1mW4y1Z7kb/?share_sourcecopy_web&vd_sourcebe33b1553b08cc7b94afdd6c8a50dc5a 单路布线 遵循顺序 先近后远,先易后难 可以拖动让拐角缩小…...

tomcat与Apache---一起学习吧之服务器
Apache和Tomcat都是Web服务器,但它们有一些重要的区别。 Apache服务器是普通服务器,本身只支持HTML即普通网页。不过可以通过插件支持PHP,还可以与Tomcat连通(单向Apache连接Tomcat,就是说通过Apache可以访问Tomcat资…...

Vue3的优势
Vue3和Vue2之间存在以下主要区别: 1. 性能优化:Vue3在内部进行了重写和优化,采用了新的响应式系统(Proxy),相较于Vue2中的Object.defineProperty,更具性能优势。Vue3还对编译和渲染进行了优化&…...

鸿蒙开发案例002
1、目标需求 界面有增大字体按钮,每次点击增大字体按钮,“Hello ArkTS”都会变大 2、源代码 Entry Component struct Page {textValue: string Hello ArkTSState textSize: number 50myClick():void{this.textSize 4}build() {Row() {Column() {//…...

Git学习笔记(第9章):国内代码托管中心Gitee
目录 9.1 简介 9.1.1 Gitee概述 9.1.2 Gitee帐号注册和登录 9.2 VSCode登录Gitee账号 9.3 创建远程库 9.4 本地库推送到远程库(push) 9.5 导入GitHub项目 9.6 删除远程库 9.1 简介 9.1.1 Gitee概述 众所周知,GitHub服务器在国外,使用GitHub作为…...

使用k8s 配置 RollingUpdate 滚动更新实现应用的灰度发布
方案实现方式: RollingUpdate 滚动更新机制 当某个服务需要升级时,传统的做法是,先将要更新的服务下线,业务停止后再更新版本和配置,然后重新启动服务。 如果业务集群规模较大时,这个工作就变成了一个挑战…...

MATLAB知识点:mode :计算众数
讲解视频:可以在bilibili搜索《MATLAB教程新手入门篇——数学建模清风主讲》。 MATLAB教程新手入门篇(数学建模清风主讲,适合零基础同学观看)_哔哩哔哩_bilibili 节选自第3章 3.4.1节 mode :计算众数 众数是指一…...

【JavaWeb】MVC架构模式
文章目录 MVC是什么?一、M :Model 模型层二、V:View 视图层三、C:Controller 控制层四、非前后端分离MVC五、前后端分离MVC总结 MVC是什么? MVC(Model View Controller)是软件工程中的一种**软件…...

【Unity学习笔记】创建人物控制器
人物左右移动 1 导入模型,如果没有模型,则在 窗口-资产商店-free sample 找到人物模型 2 在 窗口-包管理中 导入自己的模型 3 在自己的资产文件夹中找到Prefabs Base HighQuality MaleFree1模型,导入到场景中 4 Assets中创建C#项目 写入如下…...

HCIP:不同VLAN下实现网络互相通信
配置pc1 配置pc2 配置pc3 将sw1划分到vlan3 将sw3划分到vlan3 在sw1上进行缺省 将sw1上(g0/0/1)的untagged改成 1 3 则在pc1上ping pc2可通 在sw1上进行缺省 在sw3上(e0/0/1)打标记 则在pc1上ping pc3可通(实现互通&am…...

07 队列
目录 1.队列 2.实现 3.OJ题 1. 队列 只允许在一段进行插入数据操作,在另一端进行数据删除操作的特殊线性表,队列具有先进先出FIFO(First In Firtst Out),插入操作的叫队尾,删除操作的叫队头 2. 实现 队列…...

产品面试题2
39.产品经理在与 开发团队合作时,以下哪个角色负责将产品需求转化为可执行的任务? a) 技术经理 b) 交互设计师 c) 项目经理 d) 开发工程师 答案:c 40.以下哪个方法适用于评估产品的用户满意度和体验? a) 用户访谈 b) 用户调研问卷…...

[NSSCTF]-Web:[SWPUCTF 2021 新生赛]easy_md5解析
先看网页 大致就是输入name和password的值,只要他俩的值不一样,然后经过md5函数之后一样就能出flag。 解法一(利用php的科学计数法): 在php中,假设a,b为数字,那科学计数法可以用ae…...

嵌入式解惑——串口通信中的流控制有什么作用?
在串口通信中,流控制(Flow Control)是一个非常重要的概念。它主要是用来协调发送端和接收端的数据传输速率,以防止接收端流量过大导致的数据丢失问题。 串口通信的特点是数据是以串行方式,一位一位的进行传输。如果…...

Kubernetes-Taint (污点)和 Toleration(容忍)
目录 一、Taint(污点) 1.污点的组成 2.污点的设置、查看和去除 3.污点实验: 二、Toleration(容忍) 1.容忍设置的方案 2.容忍实验: Taint 和 toleration 相互配合,可以用来避免 pod 被分配…...

python三数之和
给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k ,同时还满足 nums[i] nums[j] nums[k] 0 。请 你返回所有和为 0 且不重复的三元组。 注意:答案中不可以包含重复的三元组。 示例 1…...

uniapp 用css animation做的鲤鱼跃龙门小游戏
第一次做这种小游戏,刚开始任务下来我心里是没底的,因为我就一个‘拍黄片’的,我那会玩前端的动画啊,后面尝试写了半天,当即我就给我领导说,你把我工资加上去,我一个星期给你做出来,…...

JeecgBoot 3.6.1实现Modal对话框,以为审核数据为例
JeecgBoot 3.6.1实现Modal对话框 vue使用的是3.0版本 文章目录 JeecgBoot 3.6.1实现Modal对话框前言一、列表页面关键代码示例二、textAuditModal.vue代码示例三、test.api.ts总结 前言 在工作中,有一个需求,要求,在数据列表页,…...