在vue中实现在线代码编辑器(lua)

您所在的位置:网站首页 vue文本编辑组件 在vue中实现在线代码编辑器(lua)

在vue中实现在线代码编辑器(lua)

2023-08-27 07:13| 来源: 网络整理| 查看: 265

# 前言 由于项目需求,需要完成一个支持代码提示、代码校验、代码格式化的lua编辑器 1 技术选型

精力有限,只了解了几个主流的编辑器codemirror、ace、Monaco Editor

codemirror5

用户最多,生态最好,所以插件也相对完备,能想到的基本都有,同时也被很多线上应用在用,有什么问题百度搜下基本都能搜到。

github官网官方lua的demo ace

ajax团队弄的一个开源编辑器,生态、文档也还不错,相对codemirror来说,语法提示和语法校验支持的语言种类更多

官网github官方在线测试demo Monaco Editor

微软开源的一个web代码编辑器,可以看作vscode的web版,各项功能都比较强,理论上来说vscode支持的插件Monaco Editor也支持(因为vscode的代码编辑器也是用这个实现的),但是相关文档比较少

github在线测试demo 基于vue封装的组件 vue-codemirrorvue2-ace-editor(不推荐使用,已长期未维护)vue-monaco-editor(不推荐使用,已长期未维护) 对比

在这里插入图片描述 在这里插入图片描述

各个编辑器对JavaScript、SQL、Java这些热门语言的支持度都很高,其他相对冷门语言的支持度都差一点,可以看看以下文章来帮助大家选型(以上对比图片来源于以下文章):

Wikipedia:基于 JavaScript 的源代码编辑器的比较基于JavaScript的代码编辑器的比较和选型 知乎的这个问题:CodeMirror 和 ACE 相比各有什么优缺点?浅显的Monaco Editor 与codemirror 选型 2 技术验证 codemirror.js(vue-codemirror)

由于之前有了解过codemirror,所以第一个方案自然就尝试使用codemirror来实现lua编辑器

注意:

codemirror已发布v6版本,本文使用的是v5版本vue-codemirror已兼容codemirror@6版本,只有@4版本才支持codemirror@5 安装

推荐使用vue-codemirror

npm i codemirror@5 -S npm i [email protected] -S

推荐安装@types/codemirror以支持类型推断

npm i @types/codemirror -D 基础使用

注册全局组件

// require lib import Vue from 'vue' import VueCodemirror from 'vue-codemirror' // require styles import 'codemirror/lib/codemirror.css' // require more codemirror resource... // you can set default global options and events when use Vue.use(VueCodemirror, /* { options: { theme: 'base16-dark', ... }, events: ['scroll', ...] } */)

注册局部组件

// require component import { codemirror } from 'vue-codemirror' // require styles import 'codemirror/lib/codemirror.css' // require more codemirror resource... // component export default { components: { codemirror } }

使用组件

// 引入语言 import 'codemirror/mode/javascript/javascript.js' // 引入样式 import 'codemirror/theme/base16-dark.css' // 按需导入codemirror其他功能 import 'codemirror/xxx' export default { data () { return { code: 'const a = 10', cmOptions: { // codemirror options tabSize: 4, mode: 'text/javascript', theme: 'base16-dark', lineNumbers: true, line: true, // more codemirror options, 更多 codemirror 的高级配置... } } }, methods: { }, computed: { codemirror() { return this.$refs.myCm.codemirror } }, mounted() { } } 实现lua代码编辑器

使用lua模式时,mode需要设置为text/x-lua 具体功能需要引入哪些模块已标好注释

import { codemirror } from 'vue-codemirror' import 'codemirror/lib/codemirror.css' import 'codemirror/mode/lua/lua' import 'codemirror/theme/neat.css' import 'codemirror/theme/idea.css' // #region 搜索功能 // find:Ctrl-F (PC), Cmd-F (Mac) // findNext:Ctrl-G (PC), Cmd-G (Mac) // findPrev:Shift-Ctrl-G (PC), Shift-Cmd-G (Mac) // replace:Shift-Ctrl-F (PC), Cmd-Alt-F (Mac) // replaceAll:Shift-Ctrl-R (PC), Shift-Cmd-Alt-F (Mac) import 'codemirror/addon/dialog/dialog.css' import 'codemirror/addon/dialog/dialog' import 'codemirror/addon/search/searchcursor' import 'codemirror/addon/search/search' import 'codemirror/addon/search/jump-to-line' import 'codemirror/addon/search/matchesonscrollbar' import 'codemirror/addon/search/match-highlighter' // #endregion // #region 代码提示功能 // 具体语言可以从 codemirror/addon/hint/ 下引入多个 import 'codemirror/addon/hint/show-hint.css' import 'codemirror/addon/hint/show-hint.js' import 'codemirror/addon/hint/anyword-hint' // 简易的代码提示功能 // #endregion // #region 高亮行功能 import 'codemirror/addon/selection/active-line' import 'codemirror/addon/selection/selection-pointer' // #endregion // #region 覆盖scrollbar样式功能 import 'codemirror/addon/scroll/simplescrollbars.css' import 'codemirror/addon/scroll/simplescrollbars' // #endregion // #region 自动括号匹配功能 import 'codemirror/addon/edit/matchbrackets.js' // #endregion // 全屏功能 由于项目复杂,自带的全屏功能一般不好使 // import 'codemirror/addon/display/fullscreen.css' // import 'codemirror/addon/display/fullscreen.js' // 显示自动刷新 import 'codemirror/addon/display/autorefresh.js' // 多语言支持? // import 'codemirror/addon/mode/overlay' // import 'codemirror/addon/mode/multiplex' import 'codemirror/addon/lint/lint.js' import 'codemirror/addon/lint/lint.css' // #region 代码段折叠功能 import 'codemirror/addon/fold/foldcode.js' import 'codemirror/addon/fold/foldgutter.js' import 'codemirror/addon/fold/foldgutter.css' import 'codemirror/addon/fold/brace-fold.js' // 括号折叠 import 'codemirror/addon/fold/comment-fold.js' import 'codemirror/addon/fold/indent-fold.js' // 缩进折叠 import 'codemirror/addon/fold/comment-fold.js' // import 'codemirror/addon/fold/xml-fold.js' // import 'codemirror/addon/fold/markdown-fold.js' // #endregion // #region merge功能 // import 'codemirror/addon/merge/merge.css' // import 'codemirror/addon/merge/merge.js' // #endregion export default { name: 'LuaEditor', // #region 组件基础 components: { codemirror: codemirror }, props: { value: { type: String, required: true } }, // #endregion // #region 数据相关 data() { return { /** @type {import('@types/codemirror/index').EditorConfiguration}*/ codemirrorOptions: { // 语言及语法模式 mode: 'text/x-lua', // 主题 theme: 'neat', // 显示函数 line: true, // 显示行号 lineNumbers: true, // 软换行 lineWrapping: true, // tab宽度 tabSize: 4, // 允许拖入的文件类型 allowDropFileTypes: ['text/x-lua'], cursorScrollMargin: 5, extraKeys: {}, // 高亮行功能 styleActiveLine: true, // 调整scrollbar样式功能 // scrollbarStyle: 'overlay', // 自动括号匹配功能 matchBrackets: true, autofocus: true, autoRefresh: true, // #region 代码折叠 foldGutter: true, // foldOptions: { scanUp: true }, gutters: [ 'CodeMirror-linenumbers', 'CodeMirror-foldgutter', 'CodeMirror-lint-markers' ], // #endregion showHint: true, lint: true, hintOptions: { // 避免由于提示列表只有一个提示信息时,自动填充 completeSingle: false } } } }, computed: {}, watch: {}, // #endregion // #region 生命周期 created() {}, mounted() {}, // #endregion methods: { handleInputChange(value) { this.$emit('input', value) }, handleInputRead(cm) { // 显示代码提示框 cm.showHint() } } } .lua-editor { textarea { height: 100%; } } 实现效果

代码高亮: 在这里插入图片描述 简易的代码提示: 在这里插入图片描述 输入错误的代码: 在这里插入图片描述 由于不支持lua语言的代码段折叠、代码校验,所以无法测试

总结

优点:

支持lua语法高亮简易的语法提示(根据当前代码中的关键字)可以按需导入功能与模块

缺点:

不支持lua代码校验不支持lua代码格式化lua语法提示不满足需求没有全量导入功能,当导入大量模块和功能时,需要写很多import,不方便管理

. . .

ace.js(本文使用的方案) 安装 npm i ace-builds -S

推荐安装@types/ace以提供类型推断

npm i @types/ace -D 基础使用 全量引入(不推荐,仅开发阶段使用)

ace提供了一个全量导入模块:ace-builds/webpack-resolver

缺点:导致引入包过大,不需要的、使用不到的也被打包进来了,而且打包后的根目录下会有很多js文件优点:不需要再去手动查找/导入会用到的模块了,适合在开发阶段使用 import ace from 'ace-builds' import 'ace-builds/css/ace.css' import 'ace-builds/webpack-resolver' // 全量导入

打包后输出如下:

在这里插入图片描述 在这里插入图片描述

按需引入(推荐,打包时使用)

ace-build.js建议使用按需引入,全量引入时体积过于庞大

按需引入需要知道会用到哪些模块,提前import进来,适合已开发完成、维护的阶段需要注意的是,worker相关js文件,不能直接使用import导入,一定要使用ace.config.setModuleUrl + file-loader导入,否则会报错误在这里插入图片描述在这里插入图片描述 import ace from 'ace-builds' import 'ace-builds/css/ace.css' // #region lua语法高亮 import 'ace-builds/src-noconflict/mode-lua' import 'ace-builds/src-noconflict/snippets/lua' // #endregion // #region 代码提示 import 'ace-builds/src-noconflict/ext-language_tools' // #endregion // #region 代码校验 ace.config.setModuleUrl( 'ace/mode/base_worker', require('file-loader?esModule=false!ace-builds/src-noconflict/worker-base.js') ) ace.config.setModuleUrl( 'ace/mode/lua_worker', require('file-loader?esModule=false!ace-builds/src-noconflict/worker-lua.js') ) // #endregion // #region 主题 import 'ace-builds/src-noconflict/theme-chrome' // #endregion // #region 其他功能 import 'ace-builds/src-noconflict/ext-searchbox' import 'ace-builds/src-noconflict/ext-keybinding_menu' import 'ace-builds/src-noconflict/ext-settings_menu' // #endregion

打包后输出如下,少了很多js文件,体积比全量引入小了10+MB 在这里插入图片描述

在这里插入图片描述

实现Lua代码编辑器 import ace from 'ace-builds' import 'ace-builds/css/ace.css' import 'ace-builds/webpack-resolver' // 全量导入 export default { // #region 组件基础 components: {}, props: { value: { type: String, required: true }, options: Object }, // #endregion // #region 数据相关 data() { content: this.value || '', return {/** @type {import('ace-builds').Ace.EditorOptions}*/ editorOptions: { // keyboardHandler: '', mode: 'ace/mode/lua', theme: 'ace/theme/chrome', tabSize: 2, selectionStyle: 'text', // 拖动代码块 dragEnabled: true, useWorker: true, // 自动缩进 enableAutoIndent: true, // 显示行号 showLineNumbers: true, useSoftTabs: true, // 渐变隐藏折叠按钮 fadeFoldWidgets: true, // 输入边界 showPrintMargin: false, // 高亮当前行 highlightActiveLine: true, // 高亮选中词 highlightSelectedWord: true, // 滚动动画 autoScrollEditorIntoView: true, copyWithEmptySelection: true, // #region 启用自动完成和代码段 // enableBasicAutocompletion: true, enableLiveAutocompletion: true, enableSnippets: true // #endregion }} }, computed: { /** @return {import('ace-builds').Ace.Editor} */ _editor() { return this.editor } }, watch: { value(nval) { this.syncContent(nval) }, options(nval) { this.syncOptions(nval) } }, // #endregion // #region 生命周期 created() {}, mounted() { let editor = ace.edit(this.$el, this.editorOptions) this.editor = editor }, beforeDestroy() { this._editor.destroy() }, // #endregion methods: { /** * 同步内容 */ syncContent(value = this.value) { if (this.content != value) { this._editor?.setValue(value, 1) } }, /** * 同步配置 */ syncOptions(option = this.option) { let keys = Object.keys(option) for (let key of keys) { // 禁止修改固定的option if (key in this.editorOptions) continue this._editor?.setOption(key, option[key]) } } } } 实现效果 功能实际效果代码高亮在这里插入图片描述代码提示在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述代码校验在这里插入图片描述代码段折叠在这里插入图片描述在这里插入图片描述 总结

优点:

支持lua语法高亮支持lua代码校验支持lua代码块折叠基础语法提示,提供部分代码片段可以按需导入功能与模块

缺点:

不支持lua代码格式化引入的模块,打包后会在根目录下产生很多js文件 Monaco Editor

这个编辑器是在写文章的时候看到的,目前还没有尝试使用,后续有时间追加

可参考文档:

JS在线代码编辑器多种方案monaco-editor,vue-monaco-editorVue + monaco-editor

. . .

3 代码格式化

codemirror、ace都不支持lua的代码格式化,那只能在github找找使用javascript实现lua格式化的开源插件了,共找到如下开源库

luaparse

此开源库使用javascript语言实现将lua代码字符串分析为ast抽象语法树,但是无法直接使用,需要二次开发,有一定的开发成本

@appguru/luafmt(本文使用的方案)

此开源库基于luaparse库开发,luaparse将lua语言转换为ast树,@appguru/luafmt在此基础上对ast树进行分析,以实现代码格式化

安装&使用

安装

npm i @appguru/luafmt -S

使用

import { ast } from '@appguru/luafmt' import { parse } from 'luaparse' // 创建格式化器实例 let formatter = ast.formatter({ indent: ' ', // 缩进符号 newline: '\n', // 换行符号 extra_newlines: true, // 是否额外换行 // tabWidth: 2, // tab宽度 // useTabs: true, // semi: false, // 是否加分号 inline: { // 单行宽度设置 block: { max_exp_length: 60 }, table: { max_field_count: 0, max_field_length: 0 } } }) // 使用 let result = formatter('lua code') 实际效果

原始代码: 在这里插入图片描述 格式化后: 在这里插入图片描述

lua-format

此库应该是自行实现了ast语法树分析,所以体积会比较大

安装&使用

安装

npm i lua-format -S

使用

import luafmt from 'luamin' let result = luafmt.Beautify(code, { RenameVariables: false, // 重命名变量 RenameGlobals: false, // 重命名全局变量 SolveMath: false }) 实际效果

原始代码:

在这里插入图片描述

格式化后:

在这里插入图片描述

多次格式化后:

在这里插入图片描述 多行注释测试:

在这里插入图片描述 在这里插入图片描述

luamin

此开源库也是基于luaparse库开发,不过是内部集成的方式,仅支持lua代码压缩,没有其他功能

安装&使用

安装

npm i luamin -S

使用

import luamin from 'luamin' let result = luamin.minify(code) 实际效果

原始代码:

在这里插入图片描述

压缩后:

在这里插入图片描述

总结

推荐使用@appguru/luafmt,或者基于luaparse自行实现

-打包后体积(粗略数值)开源协议最近一次提交存在的问题@appguru/luafmt8.59kbMIT2020-111. 同行注释会跑到代码下面(强制换行) 2. 数值变量会变成指数形式lua-format32.1kb(gzip: 10.4kb)ISC2022-071.会输出:–discord.gg/boronide, code generated using luamin.js™ + 换行 * 4,需要手动去除 2.会将多行注释删除luamin64.1kb(gzip: 15.2kb)MIT2019-08只支持压缩lua代码 4 lua代码编辑器 源码 import ace from 'ace-builds' // import 'ace-builds/webpack-resolver' import 'ace-builds/css/ace.css' // #region lua语法高亮 import 'ace-builds/src-noconflict/mode-lua' import 'ace-builds/src-noconflict/snippets/lua' // #endregion // #region 代码提示 import 'ace-builds/src-noconflict/ext-language_tools' // #endregion // #region 代码校验 // import 'ace-builds/src-noconflict/worker-base.js' // import 'ace-builds/src-noconflict/worker-lua.js' ace.config.setModuleUrl( 'ace/mode/base_worker', require('file-loader?esModule=false!ace-builds/src-noconflict/worker-base.js') ) ace.config.setModuleUrl( 'ace/mode/lua_worker', require('file-loader?esModule=false!ace-builds/src-noconflict/worker-lua.js') ) // #endregion // #region 主题 import 'ace-builds/src-noconflict/theme-chrome' // #endregion // #region 其他功能 import 'ace-builds/src-noconflict/ext-searchbox' import 'ace-builds/src-noconflict/ext-keybinding_menu' import 'ace-builds/src-noconflict/ext-settings_menu' // #endregion import { ast } from '@appguru/luafmt' // import { ast } from './appguru' import { parse } from 'luaparse' const events = [ 'bulr', 'change', 'changeSelectionStyle', 'changeSession', 'copy', 'focus', 'paste', 'mousemove', 'mouseup', 'mousewheel', 'click' ] /** * LuaEditor * @author lcm * @createTime * @description */ export default { name: 'LuaEditor', // #region 组件基础 components: {}, props: { value: { type: String, required: true }, options: Object, theme: String, mode: String, readonly: Boolean }, // #endregion // #region 数据相关 data() { return { content: this.value || '', cursorPos: null, /** @type {import('ace-builds').Ace.EditorOptions}*/ editorOptions: { // keyboardHandler: '', mode: 'ace/mode/lua', theme: 'ace/theme/chrome', tabSize: 2, selectionStyle: 'text', // 拖动代码块 dragEnabled: true, useWorker: true, // 自动缩进 enableAutoIndent: true, // 显示行号 showLineNumbers: true, useSoftTabs: true, // 渐变隐藏折叠按钮 fadeFoldWidgets: true, // 输入边界 showPrintMargin: false, // 高亮当前行 highlightActiveLine: true, // 高亮选中词 highlightSelectedWord: true, // 滚动动画 autoScrollEditorIntoView: true, copyWithEmptySelection: true, // #region 启用自动完成和代码段 // enableBasicAutocompletion: true, enableLiveAutocompletion: true, enableSnippets: true // #endregion } } }, computed: { /** @return {import('ace-builds').Ace.Editor} */ _editor() { return this.editor } }, watch: { value(nval) { this.syncContent(nval) }, theme(nval) { this._editor?.setTheme('ace/theme/' + nval) }, mode(nval) { this._editor?.getSession().setMode('ace/mode/' + nval) }, readonly(nval) { this._editor?.setReadOnly(nval) }, options(nval) { this.syncOptions(nval) } }, // #endregion // #region 生命周期 created() {}, mounted() { if (this.theme) { this.editorOptions.theme = this.theme } // if (this.lang) { // this.editorOptions.mode = this.lang // } //@appguru/luafmt this.formatter = ast.formatter({ indent: ' ', newline: '\n', extra_newlines: true, // tabWidth: 2, // useTabs: true, // semi: false, inline: { block: { max_exp_length: 60 }, table: { max_field_count: 0, max_field_length: 0 } } }) let editor = ace.edit(this.$el, this.editorOptions) editor.setValue(this.value, 1) // editor.setOption('auto', 1) editor.commands.addCommands([ { name: 'showSettingsMenu', bindKey: { win: 'Ctrl-q', mac: 'Ctrl-q' }, exec(editor) { ace.config.loadModule('ace/ext/settings_menu', function (module) { module.init(editor) editor.showSettingsMenu() }) }, readOnly: true } ]) // 快捷键帮助面板 editor.commands.addCommand({ name: 'showKeyboardShortcuts', bindKey: { win: 'Ctrl-Alt-h', mac: 'Command-Alt-h' }, exec(editor) { ace.config.loadModule('ace/ext/keybinding_menu', function (module) { module.init(editor) editor.showKeyboardShortcuts() }) } }) //格式化 editor.commands.addCommand({ name: 'formattingCode', bindKey: { win: 'Shift-Alt-F', mac: 'Shift-Alt-F' }, exec: (editor) => { this.formattingCode() } }) editor.selection.on('changeCursor', (e) => { this.cursorPos = editor.getCursorPosition() this.$emit('changeCursor', e) }) editor.on('change', (ev) => { this.cursorPos = editor.getCursorPosition() let value = editor.getValue() this.content = value console.log('cursorPos', this.cursorPos) this.$emit('input', value) }) for (let name of events) { editor.on(name, (ev) => { this.$emit(name, ev) }) } this.editor = editor }, beforeDestroy() { this._editor.destroy() }, // #endregion methods: { /** * 格式化代码 */ formattingCode() { try { const srcAST = parse(this.content) ast.fixRanges(srcAST) ast.insertComments(srcAST) let result = this.formatter(srcAST) this.content = result this.editor.setValue(this.content) // 获取当前光标位置 // let cursorPos = this._editor.getCursorPosition() // this._editor.setValue(result, 0) // 保持光标位置不变 // this._editor.navigateTo(cursorPos.row, cursorPos.column) return true } catch (error) { console.log('格式化失败 ', error) return false } }, /** * 同步内容 */ syncContent(value = this.value) { if (this.content != value) { this._editor?.setValue(value, 1) } }, /** * 同步配置 */ syncOptions(option = this.option) { let keys = Object.keys(option) for (let key of keys) { // 禁止修改固定的option if (key in this.editorOptions) continue this._editor?.setOption(key, option[key]) } } } } .lua-editor { textarea { height: 100%; } }


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3