NoteZ_技术博客 NoteZ_技术博客
🏠 首页
  • 📚 Web技术
  • 📋 Npm笔记
  • 📑 Markdown
  • 📄 Git笔记
  • 📝 Nginx文档
  • 📓 Linux文档
  • 📖 技术文档
  • 📜 其他文档
  • 🧊 NodeJs
  • 🎡 Express
  • 🔥 Rust
  • 🎉 Koa2
  • 🍃 MongoDB
  • 🐬 MySql
  • 🥦 Oracle
  • 🍁 Python
  • 🍄 JavaScript
  • 🌰 CSS
  • 🧄 HTML
  • 🥑 Canvas
  • 🌽 Nuxt
  • 🍆 React
  • 🥜 Vue
  • 🧅 TypeScript
  • 🌶️ AI
  • 📘 分类
  • 📗 标签
  • 📙 归档
⚜️ 在线编辑 (opens new window)
  • 📁 站点收藏
  • 📦 前端组件库
  • 📊 数据可视化
  • 🌈 开源插件
  • 🎗️ 关于我
  • 🔗 友情链接
GitHub (opens new window)

NoteZ_技术博客

前端界的小学生
🏠 首页
  • 📚 Web技术
  • 📋 Npm笔记
  • 📑 Markdown
  • 📄 Git笔记
  • 📝 Nginx文档
  • 📓 Linux文档
  • 📖 技术文档
  • 📜 其他文档
  • 🧊 NodeJs
  • 🎡 Express
  • 🔥 Rust
  • 🎉 Koa2
  • 🍃 MongoDB
  • 🐬 MySql
  • 🥦 Oracle
  • 🍁 Python
  • 🍄 JavaScript
  • 🌰 CSS
  • 🧄 HTML
  • 🥑 Canvas
  • 🌽 Nuxt
  • 🍆 React
  • 🥜 Vue
  • 🧅 TypeScript
  • 🌶️ AI
  • 📘 分类
  • 📗 标签
  • 📙 归档
⚜️ 在线编辑 (opens new window)
  • 📁 站点收藏
  • 📦 前端组件库
  • 📊 数据可视化
  • 🌈 开源插件
  • 🎗️ 关于我
  • 🔗 友情链接
GitHub (opens new window)
  • JavaScript笔记

  • CSS笔记

  • HTML笔记

  • Canvas笔记

  • Nuxt笔记

  • React笔记

  • Vue笔记

    • markdown(md-editor-v3)简单使用
    • Prop 验证 与 非 Prop 的 Attribute
    • transition-group列表过渡
    • transition过渡&动画
    • vetur 中 eslint 的问题
    • vite 打包时内存溢出处理方案
    • Vite2 + Vue3添加svg的使用并通过svg实现自定义Icon组件
    • Vite项目构建优化之移除console.log和其他生产日志
    • vsCode 之 Monaco Editor 在 vue-cli3.0 中的使用方法
    • vue cli3 中的使用Luckysheet和Luckyexcel
    • vue 与 Koa 上传示例代码
    • vue 中 axios 如何取消请求,取消前面一个或多个请求
    • Vue 中 node-sass 安装失败简单解决方案
    • vue 中使用 pinia 状态管理教程
    • vue 中使用 web worker
    • vue 中封装 Axios 和 管理 API 接口
    • vue 中的 hash 与 history 两种路由模式
    • Vue 中的鼠标事件汇总
    • Vue 实现 Base64 图片上传
    • vue 强制刷新组件
    • Vue 组件中 v-mode 的本质以及实现方式
    • vue 组件中的.native 原生事件(@click.native)
    • Vue 组件中的.native原生事件(@click.native)
    • Vue 页面路由(router)权限控制和登陆验证
    • Vue 项目中如何在打包时清除 console 和 debugger
    • Vue 项目打包去除 .map 文件方法
    • Vue 预渲染之 prerender-spa-plugin 的配置使用
    • vue-cli 2.x和3.x配置移动端适配px自动转为rem
    • vue-cli3 如何实现pc端自适应
    • vue-cli3.0 开启 Gzip 压缩方法
    • Vue-cli3.0 监听路由变化的几种方式
    • Vue-cli3.0 结合 axios 解决生产环境中的跨域问题
    • vue-cli3.0移动端项目将px转换成rem
    • vue-cli3.x项目兼容ie解决方案
    • vue-router传递参数的几种方式
    • Vue2-ace-editor 基本使用方法
    • vue2.x 插槽slot使用总结
    • Vue2.x 组件通信方式
    • vue3 + vite 自适应(rem 适配)方法
    • vue3+vite:本地代理,配置proxy
    • Vue3.0 基础知识点总结
    • Vue3.0 挂载方法,添加全局属性
    • vue3.0之全局变量app.config.globalProperties的使用
    • vue3.2+elelmenui-plus+vite 按需导入ui组件库
    • vue3.x 中使用 ol (openlayer)地图
    • Vue3.x 组件通信方式
    • Vue3全局变量的定义和使用
    • Vuex使用记录
    • vue中使用html2canvas将html页面转为图片并且下载该图片
    • vue中父子组件之间的通信
    • Vue中的防抖函数封装和使用
    • Vue中配合clipboard.js实现点击按钮复制内容到剪切板
    • vue使用monaco editor时报错 Unexpected usage at EditorSimpleWorker.loadForeignModule
    • Vue列表渲染之数组、对象更新检测
    • Vue实现下拉滚动加载刷新功能
    • Vue开发中安装库经常报错
    • Vue技术框架
    • Vue生命周期
    • Vue监听store中数据变化的两种方式
    • Vue非父子组件之间通信的几种方式
    • Vue项目 解决 vuex 在页面刷新后数据丢失的问题
    • vue项目nginx部署子目录_vue 多项目部署_二级目录 copy
    • 使用 monaco-editor 汉化 右键菜单汉化
    • 使用 vite 脚手架创建 vue3 项目
    • 使用animate库
    • 使用vue实现鼠标框选文件的功能
    • 使用vue的transition标签结合css样式完成动画
    • 关于Vue项目优化方案总结
    • 关于解决vue-cli2.xx、vue-cli3.xx项目在ie中白屏的方案总结
    • 利用create-nuxt-app脚手架创建NuxtJS应用
    • 国际化插件 vue-i18n 使用方法
    • 在 vite-vue3.x 中的使用 vscode monaco-editor 方法
    • 在 Vue 中 如何使用 jsencrypt 实现 Rsa 进行加密
    • 在 vue 中使用 jsPlumb 总结
    • 在 vue 中使用 v-viewer 预览图方法
    • 在 Vue 中读取本地文本文件(兼容各种浏览器)
    • 在 vue 或 vuex 中如何使用 axios 进行请求操作
    • 在 vue 项目中 mock 的基本使用方法
    • 在 vue-cli3 项目更改 favicon 图标
    • 在 vue-cli3.0 中使用 Sass 全局变量
    • 在vue3+vite中使用markdown(v-md-editor)编辑器
    • 在vue中如何使用watch深度监听对象中值的变化
    • 基于 vue 列表拖拽排序
    • 基于 vue 图片裁剪(vue-cropper)
    • 基于 vue 的 CodeMirror 代码编辑器
      • 安装依赖
      • 代码案例
      • 高级功能
      • 自定义代码提示
        • 自定义hint方法
        • 自定义hint展示内容
        • 异步返回hint结果
        • CodeMirror命令API
      • 特殊用法和踩过的坑
        • 自动高度
        • 只读模式
        • tab转空格
      • 参考Demo
    • 如何在 vue 中使用 swiper 插件创建轮播图
    • 如何在 vue 中实现代码高亮?
    • 如何在 Vue-cli3.0 中使用 prerender-spa-plugin 进行预渲染
    • 如何用 vue 实现一个虚拟列表
    • 清除项目中无用的console.log代码方法
    • 解决vue-cli3.0中提示syntax 'classProperties' isn't currently enabled的错误
    • 记录vite打包vue项目内存溢出问题及解决方法
    • Vite中使用 svg-sprite-loader加载svg文件
    • vite引入svg图片
    • vue 使用CompressionPlugin压缩解决打包后文件过大的问题
    • vue2.x递归组件 树形组件
    • vue3.x递归组件 树形组件
    • 使用Vite快速构建Vue3+ts+pinia脚手架
    • 启动vue项目时报错digital envelope routines__unsupported
    • Pinia 快速入门(使用教程)
    • 关于 Vite Vue 辨别当前所处于什么(生产、开发)环境
  • TypeScript笔记

  • AI相关笔记

  • 开发文档
  • Vue笔记
NoteZ
2020-01-18
目录

基于 vue 的 CodeMirror 代码编辑器

Codemirror是一个不错的Web代码编辑库,可以方便简单的集成。没有自动提示功能的代码编辑器是没有灵魂的,Codemirror的自动提示功能是使用show-hint库进行的,我们可以调用showHint方法或者autoComplete方法来显示提示框。

  • 官网地址:https://codemirror.net/ (opens new window)
  • 官方GitHub:https://github.com/codemirror/codemirror.next (opens new window)
  • Vue官方GitHub: https://github.com/surmon-china/vue-codemirror (opens new window)

# 安装依赖

npm i codemirror --save
1

# 代码案例

<template>
  <div class="coder-panel">
    <textarea ref="textarea"></textarea>
  </div>
</template>

<script>
// 引入全局实例
import _CodeMirror from "codemirror";

// 核心样式
// import 'codemirror/theme/ambiance.css'
import "codemirror/lib/codemirror.css";
import "codemirror/addon/hint/show-hint.css";

// 引入主题后还需要在 options 中指定主题才会生效
import "codemirror/theme/3024-day.css";
// import "codemirror/theme/cobalt.css";

// 需要引入具体的语法高亮库才会有对应的语法高亮效果
// codemirror 官方其实支持通过 /addon/mode/loadmode.js 和 /mode/meta.js 来实现动态加载对应语法高亮库
// 但 vue 貌似没有无法在实例初始化后再动态加载对应 JS ,所以此处才把对应的 JS 提前引入

import "codemirror/addon/edit/matchbrackets";
import "codemirror/addon/selection/active-line";

import "codemirror/addon/hint/show-hint";
import "codemirror/addon/hint/anyword-hint.js";
import "codemirror/mode/cypher/cypher.js";

// require('codemirror/mode/sql/sql')
// require('codemirror/addon/hint/sql-hint')

import cypherKeywords from "@/utils/CypherKeywords.js";

// 尝试获取全局实例
const CodeMirror = window.CodeMirror || _CodeMirror;
 
let cypherKeywords = [
    'ACCESS',
    'ACTIVE',
    'ADMIN',
    'ADMINISTRATOR',
    'ALL',
    'ALLSHORTESTPATHS',
    'ALTER',
    'AND',
    'ANY',
    'AS',
    'ASC',
    'ASCENDING',
    'ASSERT',
    'ASSIGN',
    'BOOSTED',
    'BRIEF',
    'BTREE',
    'BUILT',
    'BY',
    'CALL',
    'CASE',
    'CATALOG',
    'CHANGE',
    'COMMIT',
    'CONSTRAINT',
    'CONSTRAINTS',
    'CONTAINS',
    'COPY',
    'COUNT',
    'CREATE',
    'CSV',
    'CURRENT',
    'CYPHER',
    'DATABASE',
    'DATABASES',
    'DBMS',
    'DEFAULT',
    'DEFINED',
    'DELETE',
    'DENY',
    'DESC',
    'DESCENDING',
    'DETACH',
    'DISTINCT',
    'DROP',
    'EACH',
    'ELEMENT',
    'ELEMENTS',
    'ELSE',
    'END',
    'ENDS',
    'EXECUTABLE',
    'EXECUTE',
    'EXIST',
    'EXISTENCE',
    'EXISTS',
    'EXPLAIN',
    'EXTRACT',
    'FALSE',
    'FIELDTERMINATOR',
    'FILTER',
    'FOR',
    'FOREACH',
    'FROM',
    'FULLTEXT',
    'FUNCTION',
    'FUNCTIONS',
    'GRANT',
    'GRAPH',
    'GRAPHS',
    'HEADERS',
    'HOME',
    'IF',
    'IN',
    'INDEX',
    'INDEXES',
    'IS',
    'JOIN',
    'KEY',
    'LABEL',
    'LABELS',
    'LIMIT',
    'LOAD',
    'LOOKUP',
    'MANAGEMENT',
    'MATCH',
    'MERGE',
    'NAME',
    'NAMES',
    'NEW',
    'NODE',
    'NODES',
    'NONE',
    'NOT',
    'NULL',
    'OF',
    'ON',
    'OPTIONAL',
    'OPTIONS',
    'OR',
    'ORDER',
    'OUTPUT',
    'PASSWORD',
    'PERIODIC',
    'POPULATED',
    'PRIVILEGES',
    'PROCEDURE',
    'PROCEDURES',
    'PROFILE',
    'PROPERTY',
    'READ',
    'REDUCE',
    'REL',
    'RELATIONSHIP',
    'RELATIONSHIPS',
    'REMOVE',
    'RENAME',
    'REPLACE',
    'REQUIRED',
    'RETURN',
    'REVOKE',
    'ROLE',
    'ROLES',
    'SCAN',
    'SET',
    'SHORTESTPATH',
    'SHOW',
    'SINGLE',
    'SKIP',
    'START',
    'STARTS',
    'STATUS',
    'STOP',
    'SUSPENDED',
    'THEN',
    'TO',
    'TRAVERSE',
    'TRUE',
    'TYPE',
    'TYPES',
    'UNION',
    'UNIQUE',
    'UNWIND',
    'USER',
    'USERS',
    'USING',
    'VERBOSE',
    'WHEN',
    'WHERE',
    'WITH',
    'WRITE',
    'XOR',
    'YIELD',
  ];
export default {
  name: "in-coder",
  data() {
    return {
      // 内部真实的内容
      code: "",
      // 默认的语法类型
      mode: "cypher",
      // 编辑器实例
      coder: null,
      // 默认配置
      options: {
        // 缩进格式
        mode: "cypher",
        // tabSize: 2,
        // 主题,对应主题库 JS 需要提前引入 cobalt
        theme: "3024-day",
        // 显示行号
        lineNumbers: false,
        line: false,
        matchBrackets: true,
        // 高亮行功能
        // styleActiveLine: false,
        // 调整scrollbar样式功能
        // scrollbarStyle: "overlay",
        // smartIndent: true,
        // autofocus: false,
        hintOptions: {
          // 自定义提示选项
          hint: this.handleShowHint,
          completeSingle: false,
        },
      },
      // 支持切换的语法高亮类型,对应 JS 已经提前引入
      // 使用的是 MIME-TYPE ,不过作为前缀的 text/ 在后面指定时写死了
    };
  },
  props: {
    // 外部传入的内容,用于实现双向绑定
    value: String,
    // 外部传入的语法类型
    language: {
      type: String,
      default: null,
    },
  },
  mounted() {
    // 初始化
    this._initialize();
  },
  methods: {
    handleShowHint(cmInstance, hintOptions) {
      // console.log(cmInstance);
      let hintList = cypherKeywords;
      let cur = this.coder.getCursor(),
        token = this.coder.getTokenAt(cur);
      let start = token.start,
        end = cur.ch;
      let str = token.string;

      let list = hintList.filter(function (item) {
        return item.toLowerCase().indexOf(str.toLowerCase()) === 0;
      });

      if (list.length) {
        return {
          list: list,
          from: CodeMirror.Pos(cur.line, start),
          to: CodeMirror.Pos(cur.line, end),
        };
      }
    },
    // 初始化
    _initialize() {
      // 初始化编辑器实例,传入需要被实例化的文本域对象和默认配置
      this.coder = CodeMirror.fromTextArea(this.$refs.textarea, this.options);
      // 编辑器赋值
      this.coder.setValue(this.value || this.code);

      // 支持双向绑定
      this.coder.on("change", (coder) => {
        this.code = coder.getValue();

        if (this.$emit) {
          this.$emit("input", this.code);
        }
      });
      this.coder.on("inputRead", () => {
        this.coder.closeHint();
        this.coder.showHint();
      });
      // this.coder.on("cursorActivity", () => {
      //   this.coder.closeHint();
      //   this.coder.showHint();
      // });
      this.coder.setSize("auto", "auto");
      // editor.setValue("");    //给代码框赋值
      // editor.getValue();    //获取代码框的值
      // editor.setOption("readOnly", true);	//类似这种
      // this.coder.setOption("mode", `text/${this.mode}`);

      // 尝试从父容器获取语法类型
      if (this.language) {
        // 获取具体的语法类型对象
        let modeObj = this._getLanguage(this.language);

        // 判断父容器传入的语法是否被支持
        if (modeObj) {
          this.mode = modeObj.label;
        }
      }
    },
    // 获取当前语法类型
    _getLanguage(language) {
      // 在支持的语法类型列表中寻找传入的语法类型
      return this.modes.find((mode) => {
        // 所有的值都忽略大小写,方便比较
        let currentLanguage = language.toLowerCase();
        let currentLabel = mode.label.toLowerCase();
        let currentValue = mode.value.toLowerCase();

        // 由于真实值可能不规范,例如 java 的真实值是 x-java ,所以讲 value 和 label 同时和传入语法进行比较
        return currentLabel === currentLanguage || currentValue === currentLanguage;
      });
    },
    // 更改模式
    changeMode(val) {
      // 修改编辑器的语法配置
      this.coder.setOption("mode", `text/${val}`);

      // 获取修改后的语法
      let label = this._getLanguage(val).label.toLowerCase();

      // 允许父容器通过以下函数监听当前的语法值
      this.$emit("language-change", label);
    },
  },
};
</script>

<style lang="scss">
.coder-panel {
  flex-grow: 1;
  display: flex;
  position: relative;

  .CodeMirror {
    flex-grow: 1;
    z-index: 1;
    // height: 26px;
    background: transparent !important;
    // .CodeMirror-code {
    //   line-height: 19px;
    // }
    font-family: Inconsolata, Monaco, "Courier New", Terminal, monospace !important;
    font-size: 18px !important;
    line-height: 23px !important;
    padding-top: 6px;
  }
  .CodeMirror-cursor {
    border-left: 11px solid rgba(155, 157, 162, 0.37) !important;
  }
  // .CodeMirror-scroll {
  //   margin-bottom: 10px;
  //   padding-bottom: 0px;
  // }
  .code-mode-select {
    position: absolute;
    z-index: 2;
    right: 10px;
    top: 10px;
    max-width: 130px;
  }
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368

示例图片

img_1640423991980.png

# 高级功能

<template>
  <div>
    <codemirror
      ref="cm"
      v-model="code"
      :options="cmOptions"
      @input="inputChange"
    ></codemirror>
  </div>
</template>

<script>
  // 全局引入vue-codemirror
  import {codemirror} from 'vue-codemirror';
  // 引入css文件
  import 'codemirror/lib/codemirror.css'
  // 引入主题 可以从 codemirror/theme/ 下引入多个
  import 'codemirror/theme/idea.css'
  // 引入语言模式 可以从 codemirror/mode/ 下引入多个
  import 'codemirror/mode/sql/sql.js';

  // 搜索功能
  // 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'


  // 代码提示功能 具体语言可以从 codemirror/addon/hint/ 下引入多个
  import 'codemirror/addon/hint/show-hint.css';
  import 'codemirror/addon/hint/show-hint';
  import 'codemirror/addon/hint/sql-hint';

  // 高亮行功能
  import 'codemirror/addon/selection/active-line'
  import 'codemirror/addon/selection/selection-pointer'

  // 调整scrollbar样式功能
  import 'codemirror/addon/scroll/simplescrollbars.css'
  import 'codemirror/addon/scroll/simplescrollbars'

  // 自动括号匹配功能
  import 'codemirror/addon/edit/matchbrackets'

  // 全屏功能 由于项目复杂,自带的全屏功能一般不好使
  import 'codemirror/addon/display/fullscreen.css'
  import 'codemirror/addon/display/fullscreen'

  // 显示自动刷新
  import 'codemirror/addon/display/autorefresh'

  // 多语言支持?
  import 'codemirror/addon/mode/overlay'
  import 'codemirror/addon/mode/multiplex'


  // 代码段折叠功能
  import 'codemirror/addon/fold/foldcode'
  import 'codemirror/addon/fold/foldgutter'
  import 'codemirror/addon/fold/foldgutter.css'

  import 'codemirror/addon/fold/brace-fold'
  import 'codemirror/addon/fold/comment-fold'
  import 'codemirror/addon/fold/xml-fold.js';
  import 'codemirror/addon/fold/indent-fold.js';
  import 'codemirror/addon/fold/markdown-fold.js';
  import 'codemirror/addon/fold/comment-fold.js';

  // merge功能
  import 'codemirror/addon/merge/merge.css'
  import 'codemirror/addon/merge/merge'
  // google DiffMatchPatch
  import DiffMatchPatch from 'diff-match-patch'
  // DiffMatchPatch config with global
  window.diff_match_patch = DiffMatchPatch;
  window.DIFF_DELETE = -1;
  window.DIFF_INSERT = 1;
  window.DIFF_EQUAL = 0;


  export default {
    name: 'Show',
    components: {codemirror},
    data() {
      return {
        code: 'select a from table1 where b = 1',
        cmOptions: {
          // 语言及语法模式
          mode: 'text/x-sql',
          // 主题
          theme: 'idea',
          // 显示函数
          line: true,
          lineNumbers: true,
          // 软换行
          lineWrapping: true,
          // tab宽度
          tabSize: 4,
          // 代码提示功能
          hintOptions: {
            // 避免由于提示列表只有一个提示信息时,自动填充
            completeSingle: false,
            // 不同的语言支持从配置中读取自定义配置 sql语言允许配置表和字段信息,用于代码提示
            tables: {
              "table1": ["c1", "c2"],
            },
          },
          // 高亮行功能
          styleActiveLine: true,
          // 调整scrollbar样式功能
          scrollbarStyle: 'overlay',
          // 自动括号匹配功能
          matchBrackets: true
        }
      }
    },
    methods: {
      inputChange(content) {
        this.$nextTick(() => {
          console.log("code:" + this.code);
          console.log("content:" + content)
        });
      },
    },
    mounted() {
      // 代码提示功能 当用户有输入时,显示提示信息
      this.$refs.cm.codemirror.on('inputRead', cm => {
        cm.showHint();
      })
    }
  }
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140

# 自定义代码提示

# 自定义hint方法

在methods中自定义代码实现方法:

/**

使用自定义hint,网上没有详细的讲解,这里着重讲一下。

1. 第一个入参cmInstance指的是codeMirror实例,第二个是配置中的的hintOptions值。
2. 从cmInstance中getCursor指的是获取光标实例,光标实例里有行数、列数。
3. 可以从cmInstance的getLine方法里传入一个行数,从而获取行中的字符串。
4. token对象是cmInstance对光标所在字符串进行提取处理,从对应语言的类库中判断光标所在字符串的类型,方便hint提示。token中包含start、end、string、type等属性,start和end指的是光标所在字符串在这一行的起始位置和结束位置,string是提取的字符串,type表示该字符串是什么类型(keyword/operator/string等等不定)
5. 下面方法中返回的结果体意思是:下拉列表中展示hello和world两行提示,from和to表示当用户选择了提示内容后,这些提示内容要替换编辑区域的哪个字符串。方法中的代码含义是替换token全部字符串。

*/
handleShowHint(cmInstance, hintOptions) {
    let cursor = cmInstance.getCursor();
    let cursorLine = cmInstance.getLine(cursor.line);
    let end = cursor.ch;
    let start = end;

    let token = cmInstance.getTokenAt(cursor)
    console.log(cmInstance, cursor, cursorLine, end, token)
    // console.log(hintOptions.tables)
    // return hintOptions.tables;
    return {
        list: ["hello","world"],
        from: {ch: token.start, line: cursor.line},
        to: {ch: token.end, line: cursor.line}
    };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

接下来修改配置文件中的hintOptions属性,增加hint属性,并指向实现方法:

{ // 省略其他配置项...
    hintOptions: {
        completeSingle: false,
        hint: this.handleShowHint
    }
}
1
2
3
4
5
6

# 自定义hint展示内容

自定义代码提示内容后,如果想让弹出的内容与实际插入内容不一样,则需要将返回结果进行调整。这里有一个示例,插入内容是英文,展示内容是中文。在methods中新增方法:

handleShowHint2(cmInstance, hintOptions) {
  let cursor = cmInstance.getCursor();
  let cursorLine = cmInstance.getLine(cursor.line);
  let end = cursor.ch;
  let start = end;

  let token = cmInstance.getTokenAt(cursor)
  console.log(cmInstance, cursor, cursorLine, end, token)
  return {
    list: [{
      text: "hello",
      displayText: "你好呀",
      displayInfo: "提示信息1",
      render: this.hintRender
          }, {
      text: "world",
      displayText: "世界",
      displayInfo: "提示信息2",
      render: this.hintRender
    }],
    from: {
      ch: token.start, line: cursor.line
    },
    to: {
      ch: token.end, line: cursor.line
    }
  }
},
hintRender(element, self, data) {
  let div = document.createElement("div");
  div.setAttribute("class", "autocomplete-div");

  let divText = document.createElement("div");
  divText.setAttribute("class", "autocomplete-name");
  divText.innerText = data.displayText;

  let divInfo = document.createElement("div");
  divInfo.setAttribute("class", "autocomplete-hint");
  divInfo.innerText = data.displayInfo;

  div.appendChild(divText);
  div.appendChild(divInfo);
  element.appendChild(div);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

增加样式调整:

<style>
  .autocomplete-div {
    display: inline-block;
    width: 100%;
  }
  .autocomplete-name {
    display: inline-block;
  }
  .autocomplete-hint {
    display: inline-block;
    float: right;
    color: #0088ff;
    margin-left: 1em;
  }
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 异步返回hint结果

cm提供了一种异步hint的功能,如果我们的数据来自后端,那这个功能就用的上了。具体使用方式如下:

设置hint配置:

{ // 省略其他配置项...
    hintOptions: {
        completeSingle: false,
        hint: this.handleShowHint,
        async: true
    }
}
1
2
3
4
5
6
7

实现自定义hint:

handleShowHint3(cmInstance, hintOptions) {
  let cursor = cmInstance.getCursor();
  let cursorLine = cmInstance.getLine(cursor.line);
  let end = cursor.ch;
  let start = end;

  let token = cmInstance.getTokenAt(cursor)
  console.log(cmInstance, cursor, cursorLine, end, token)
  // console.log(hintOptions.tables)
  // return hintOptions.tables;
  // 返回一个promise即可
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({
        list: ["hello", "world"],
        from: {ch: token.start, line: cursor.line},
        to: {ch: token.end, line: cursor.line}
      })
    }, 2000);
  })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# CodeMirror命令API

具体命令见:https://codemirror.net/doc/manual.html#commands (opens new window)

实际调用方式:

methods:{
    find(){
        this.$refs.cm.comdemirror.execCommand("find") 
    }
}
1
2
3
4
5

# 特殊用法和踩过的坑

# 自动高度

codemirror默认的高度是300px,如果想要调整默认高度,可以在mounted方法中增加下面一段代码,这段代码的含义是调整cm高度为(当前浏览器高度-200)px,并且在窗口发生变化时,重新再做出调整。

this.$refs.cm.codemirror.setSize("auto", (document.documentElement.clientHeight - 200) + "px")
this.$nextTick(() => {
    window.addEventListener('resize', () => {
        //监听浏览器窗口大小改变
        //浏览器变化执行动作
        this.$refs.cm.codemirror.setSize("auto", (document.documentElement.clientHeight - 200) + "px")
    });
})
1
2
3
4
5
6
7
8

# 只读模式

在官方文档里提示调整options中的readOnly参数便可以设置为只读,但实际上如果设置值为true后,用户还能在浏览器中看到光标闪烁,如果希望页面上不能编辑,则将该值设置为'nocursor'即可。

但如果设置了'nocursor',那么任何人将无法选中代码,也无法右键复制。如果还想支持选择和复制,那么需要用到以下代码:

this.$refs.cm.codemirror.setOption("readOnly",true)
// 不设的话,默认是530
this.$refs.cm.codemirror.setOption("cursorBlinkRate",-1)
1
2
3

# tab转空格

如果在新的一行直接使用tab键,大概率会输入一个制表符,但如果从上一行敲回车进入下一行,却默认是空格。这样的逻辑让使用者深恶痛绝,如何让tab键也变成空格呢?在配置json中增加下面配置,既可实现两者逻辑统一。

{
  indentUnit:4,
  extraKeys: {
      Tab: (cm) => {
          // 存在文本选择
          if (cm.somethingSelected()) {
              // 正向缩进文本
              cm.indentSelection('add');
          } else {
              // 无文本选择
              //cm.indentLine(cm.getCursor().line, "add");  // 整行缩进 不符合预期
              // 光标处插入 indentUnit 个空格
              //console.log(cm.getOption("tabSize"),cm.getOption("indentUnit"))
              cm.replaceSelection(Array(cm.getOption("indentUnit") + 1).join(" "), "end", "+input");
          }
      },
      "Shift-Tab": (cm) => {
          // 反向缩进
          if (cm.somethingSelected()) {
              // 反向缩进
              cm.indentSelection('subtract');
          } else {
              // cm.indentLine(cm.getCursor().line, "subtract");  // 直接缩进整行
              const cursor = cm.getCursor();
              // 光标回退 indexUnit 字符
              cm.setCursor({line: cursor.line, ch: cursor.ch - cm.getOption("indentUnit")});
          }
          return;
      },
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# 参考Demo

  • GitHub地址:https://github.com/MonguDykrai/cypher-codemirror-vuejs (opens new window) img_1640425272981.png

  • GitHub地址:https://github.com/mingju0421/codeMirror_dome (opens new window) img_1640425514950.png

  • cypher-editor (opens new window)

#Vue
上次更新: 2024/01/30, 00:35:17
基于 vue 图片裁剪(vue-cropper)
如何在 vue 中使用 swiper 插件创建轮播图

← 基于 vue 图片裁剪(vue-cropper) 如何在 vue 中使用 swiper 插件创建轮播图→

最近更新
01
Gitea数据备份与还原
03-10
02
Linux 中使用 rsync 同步文件目录教程
03-10
03
Linux 使用 rsync 互相传输同步文件的简单步骤
03-08
更多文章>
Theme by Vdoing | Copyright © 2019-2025 NoteZ,All rights reserved | 冀ICP备2021027292号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式