编辑器实现自定义高亮关键字、提示输入操作

效果展示

image.png

前置

npm i codemirror@5.65.5 //锁定版本

组件

<!--
 * @Descripttion: 代码编辑器
 * @version: 1.0
 * @Author: bing
 * @Date: 2024年3月1日
 * @LastEditors: 
 * @LastEditTime: 
-->

<template>
    <div class="bing-code-editor" :style="{'height':_height}">
        <textarea ref="textarea" v-model="contentValue"></textarea>
    </div>
</template>

<script>
    import { markRaw } from "vue"

    //框架
    import CodeMirror from 'codemirror'
    import 'codemirror/lib/codemirror.css'

    //主题
    import 'codemirror/theme/idea.css'
    import 'codemirror/theme/darcula.css'

    //功能
    import 'codemirror/addon/selection/active-line'

    //语言
    import 'codemirror/mode/javascript/javascript'
    import 'codemirror/mode/sql/sql'


    import 'codemirror/addon/hint/show-hint.js'
    import 'codemirror/addon/hint/show-hint.css'

    import 'codemirror/addon/hint/sql-hint.js'

    export default {
        props: {
            modelValue: {
                type: String,
                default: ""
            },
            mode: {
                type: String,
                default: "javascript"
            },
            height: {
                type: [String,Number],
                default: 300,
            },
            options: {
                type: Object,
                default: () => {}
            },
            theme: {
                type: String,
                default: "idea"
            },
            readOnly: {
                type: Boolean,
                default: false
            },
        },
        data() {
            return {
                contentValue: this.formatStrInJson(this.modelValue),
                coder: null,
                keywords: [
                    ['吃了', "keyword"],
                    ['兵兵', "property"],
                    ['徐', "number"],
                    ['牛肉', "comment"]
                ],
                opt: {
                    theme: this.theme,  //主题
                    styleActiveLine: true,  //高亮当前行
                    lineNumbers: true,  //行号
                    lineWrapping: true, //自动换行
                    tabSize: 4, //Tab缩进
                    indentUnit: 4,  //缩进单位
                    indentWithTabs : true,  //自动缩进
                    mode : this.mode,   //语言
                    readOnly: this.readOnly,    //只读
                    lint: true, // 格式化
                    foldGutter: true, // 启用折叠效果
                    hintOptions: { // 代码提示
                        completeSingle: false, // 当匹配只有一项的时候是否自动补全
                        hint: this.bingShowHint // 自定义提示
                    },
                    gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter', 'CodeMirror-lint-markers'], // 配置折叠参数
                    ...this.options
                },
                isBlank: false, // 是否为空格
                start: null // 光标开始位置
            }
        },
        computed: {
            _height() {
                return Number(this.height)?Number(this.height)+'px':this.height
            },
        },
        watch: {
            modelValue(val) {
                this.contentValue = val
                if (val !== this.coder.getValue()) {
                    const format = this.formatStrInJson(val)
                    this.coder.setValue(format)
                }
            }
        },
        mounted() {
            this.init()
            //获取挂载的所有modes
            //console.log(CodeMirror.modes)
        },
        methods: {
            init(){
                const _this = this
                // 颜色变换[高亮自定义关键词]
                CodeMirror.defineMode('javascript', function() {
                    return {
                        token: (stream, state) => {
                            const cmCustomCheckStreamFn = (streamWrapper) => {
                                for (let i = 0; i < _this.keywords.length; i++) {
                                    if (streamWrapper.match(_this.keywords[i][0])) { return _this.keywords[i][1] }
                                }
                                return ''
                            }
                            const ret = cmCustomCheckStreamFn(stream)
                            if(ret.length > 0) return ret
                            stream.next()
                            return null
                        }
                    }
                })

                this.coder = markRaw(CodeMirror.fromTextArea(this.$refs.textarea, this.opt))
                this.coder.on('change', (coder, data) => {
                    // console.log(this.inputFilter(data.text))
                    this.contentValue = coder.getValue()
                    this.$emit('update:modelValue', this.contentValue)
                })
                // 输入或者粘贴时触发
                this.coder.on('inputRead', (coder) => {
                    const cursor = coder.getDoc().getCursor()
                    const token = coder.getTokenAt(cursor)
                    console.log(token.string)
                    if((token.string).trim() == '') {
                        this.start = token.start + 1
                    }
                    if(this.start === null) {
                        this.start = token.start
                    }
                    const currentData = this.inputFilter(token.string)
                    if(currentData) {
                        coder.showHint()
                    }
                })
            },
            // 输入过滤
            inputFilter(data) {
                var reg = /^[\u4e00-\u9fa5]+$/;
                if(!reg.test(data)) {
                    return false
                }else {
                    return data
                }
            },
            // 自定义弹窗内容
            bingShowHint(cmInstance) {
                let cursor = cmInstance.getCursor()
                let token = cmInstance.getTokenAt(cursor)
                return {
                    list: [ 
                        {
                            text: '吃了',
                            displayText: '吃了',
                            displayInfo: '吃了',
                        },
                        {
                            text: '徐',
                            displayText: '徐',
                            displayInfo: '徐',
                        },
                        {
                            text: '兵兵',
                            displayText: '兵兵',
                            displayInfo: '兵兵',
                        }
                    ],
                    from: {
                        // ch: token.start, line: cursor.line
                        ch: this.start, line: cursor.line
                    },
                    to: {
                        ch: token.end, line: cursor.line
                    }
                }
            },
            formatStrInJson(strValue) {
                // return JSON.stringify(JSON.parse(strValue), null, 4)
                return strValue
            }
        }
    }
</script>

<style scoped>
    .bing-code-editor {font-size: 14px;border: 1px solid #ddd;line-height: 150%;}
    .bing-code-editor:deep(.CodeMirror)  {height: 100%;}
</style>

组件使用


<script setup>
import scCodeEditor from '@/components/bingEditor/index.vue'
import { ref } from 'vue'

const demoValue = ref('')
</script>

<template>
  <div class="editor">
    <sc-code-editor v-model="demoValue" mode="javascript" height="100vh" theme="darcula"></sc-code-editor>
  </div>
</template>

<style scoped>
.editor {
  width: 100%;
  height: 100%;
}
</style>

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容