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)是软件工程中的一种**软件…...

3.3.1_1 检错编码(奇偶校验码)
从这节课开始,我们会探讨数据链路层的差错控制功能,差错控制功能的主要目标是要发现并且解决一个帧内部的位错误,我们需要使用特殊的编码技术去发现帧内部的位错误,当我们发现位错误之后,通常来说有两种解决方案。第一…...

微信小程序 - 手机震动
一、界面 <button type"primary" bindtap"shortVibrate">短震动</button> <button type"primary" bindtap"longVibrate">长震动</button> 二、js逻辑代码 注:文档 https://developers.weixin.qq…...
Qt Http Server模块功能及架构
Qt Http Server 是 Qt 6.0 中引入的一个新模块,它提供了一个轻量级的 HTTP 服务器实现,主要用于构建基于 HTTP 的应用程序和服务。 功能介绍: 主要功能 HTTP服务器功能: 支持 HTTP/1.1 协议 简单的请求/响应处理模型 支持 GET…...

学习STC51单片机32(芯片为STC89C52RCRC)OLED显示屏2
每日一言 今天的每一份坚持,都是在为未来积攒底气。 案例:OLED显示一个A 这边观察到一个点,怎么雪花了就是都是乱七八糟的占满了屏幕。。 解释 : 如果代码里信号切换太快(比如 SDA 刚变,SCL 立刻变&#…...

dify打造数据可视化图表
一、概述 在日常工作和学习中,我们经常需要和数据打交道。无论是分析报告、项目展示,还是简单的数据洞察,一个清晰直观的图表,往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server,由蚂蚁集团 AntV 团队…...

有限自动机到正规文法转换器v1.0
1 项目简介 这是一个功能强大的有限自动机(Finite Automaton, FA)到正规文法(Regular Grammar)转换器,它配备了一个直观且完整的图形用户界面,使用户能够轻松地进行操作和观察。该程序基于编译原理中的经典…...

Golang——6、指针和结构体
指针和结构体 1、指针1.1、指针地址和指针类型1.2、指针取值1.3、new和make 2、结构体2.1、type关键字的使用2.2、结构体的定义和初始化2.3、结构体方法和接收者2.4、给任意类型添加方法2.5、结构体的匿名字段2.6、嵌套结构体2.7、嵌套匿名结构体2.8、结构体的继承 3、结构体与…...

逻辑回归暴力训练预测金融欺诈
简述 「使用逻辑回归暴力预测金融欺诈,并不断增加特征维度持续测试」的做法,体现了一种逐步建模与迭代验证的实验思路,在金融欺诈检测中非常有价值,本文作为一篇回顾性记录了早年间公司给某行做反欺诈预测用到的技术和思路。百度…...

day36-多路IO复用
一、基本概念 (服务器多客户端模型) 定义:单线程或单进程同时监测若干个文件描述符是否可以执行IO操作的能力 作用:应用程序通常需要处理来自多条事件流中的事件,比如我现在用的电脑,需要同时处理键盘鼠标…...

算法打卡第18天
从中序与后序遍历序列构造二叉树 (力扣106题) 给定两个整数数组 inorder 和 postorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。 示例 1: 输入:inorder [9,3,15,20,7…...