最近工作中需要优化下之前的网页端代码编辑器。我作为后端开发,虽然做这个不专业,但也只能硬着头皮上啊,大量查阅资料,还是优化了一些内容的,在这里分享一下。如果有写的不对的地方,也请大家体谅下,毕竟纯手打很累的哈哈。

参考手册:

vue-codemirror github 地址: https://github.com/surmon-china/vue-codemirror
codemirror 中文文档:https://olindk.gitbooks.io/codemirror/content/configuration.html
codemirror 英文文档:https://codemirror.net/doc/manual.html#config

如何引入 vue-codemirror 就不详细写了,网上资料很多。在 vue-codemirror gitbub 中也有简单教程,此文章只列举一些我认为挺有用的功能。

options配置

codemirror 中最重要的就是配置,这里先贴出来我用的配置。

cmOptions: {
	theme: 'monokai',
	mode: '',
	readOnly: false,
	tabSize: 4, // 制表符
	indentUnit: 2, // 缩进位数
	lineNumbers: true,
	ineWiseCopyCut: true,
	viewportMargin: 1000,
	autofocus: true,
	autocorrect: true,
	spellcheck: true,
	specialChars: /[\u0000-\u001f\u007f-\u009f\u00ad\u061c\u200b-\u200f\u2028\u2029\ufeff\ufff9-\ufffc]/g,
	specialCharPlaceholder: function (ch) {
	  let token = document.createElement("span");
	  let content = "\u002e";
	  token.className = "cm-invalidchar";
	  if (typeof content == "string") {
		token.appendChild(document.createTextNode(content));
	  }
	  token.title = "\\u" + ch.charCodeAt(0).toString(16);
	  token.setAttribute("aria-label", token.title);
	  return token
	},
	extraKeys: {
	  Tab: (cm) => {
		if (cm.somethingSelected()) {      // 存在文本选择
		  cm.indentSelection('add');    // 正向缩进文本
		} else {                    // 无文本选择
		  cm.replaceSelection(Array(cm.getOption("indentUnit") + 1).join(" "), "end", "+input");  // 光标处插入 indentUnit 个空格
		}
	  },
	},
	lint: false,
	// 代码折叠
	gutters: [
	  "CodeMirror-lint-markers",
	  "CodeMirror-linenumbers",
	  "CodeMirror-foldgutter"
	],
	lineWrapping: false,
	foldGutter: true, // 启用行槽中的代码折叠
	autoCloseBrackets: true, // 自动闭合符号
	autoCloseTags: true,
	matchTags: { bothTags: true },
	matchBrackets: true, // 在光标点击紧挨{、]括号左、右侧时,自动突出显示匹配的括号 }、]
	styleSelectedText: true,
	styleActiveLine: true,
	autoRefresh: true,
	highlightSelectionMatches: {
	  minChars: 2,
	  trim: true,
	  style: "matchhighlight",
	  showToken: false
	},
	hintOptions: {
	  completeSingle: false
	}
}

1)代码自动提示和补全

代码提示和补全功能,需要引入 codemirror/addon/hint 目录中的如下文件

import 'codemirror/addon/hint/show-hint.css'
import 'codemirror/addon/hint/show-hint.js'
import 'codemirror/addon/hint/javascript-hint'
import 'codemirror/addon/hint/xml-hint'
import 'codemirror/addon/hint/sql-hint'
import 'codemirror/addon/hint/anyword-hint'

代码提示需要一个事件,我这里用的是 inputRead 事件(也可以用别的事件,效果会略微不同),当监听到输入时,判断是否需要提示,所以要在编辑器初始化完成时,启动这个监听,这里只监听了英文字母的输入。编辑器初始化好后会触发 @ready 事件,在 onCmReady 方法中监听 inputRead 事件。然后只要有英文字母输入,就会监听到,自动调用 cm 的 showHint 方法进行提示。

<codemirror
  ref="myCm"
  v-model="content"
  :options="cmOptions"
  @ready="onCmReady"
  @blur="onCmBlur"
  @mousedown.native="onMouseDown">
</codemirror>

onCmReady(cm) {
  // 这里的 cm 对象就是 codemirror 对象,等于 this.$refs.myCm.codemirror 
  cm.on("inputRead", (cm, obj) => {
    if (obj.text && obj.text.length > 0) {
    let c = obj.text[0].charAt(obj.text[0].length - 1)
      if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
        cm.showHint({ completeSingle:false })
      }
    }
  })
}

提示框的层级问题

基本上增加了如上内容后,在编写代码时就有提示功能了,但是我遇到个问题就是提示框不出来,但是在源码中加日志后发现提示文本是有打印且正确的,后来发现是层级问题。

找到提示框的样式如下,默认的 z-index 是10,太小了,我改到超过2000后才能显示出来。

.CodeMirror-hints {
  position: absolute;
  z-index: 10;
  overflow: hidden;
  list-style: none;

  margin: 0;
  padding: 2px;

  -webkit-box-shadow: 2px 3px 5px rgba(0,0,0,.2);
  -moz-box-shadow: 2px 3px 5px rgba(0,0,0,.2);
  box-shadow: 2px 3px 5px rgba(0,0,0,.2);
  border-radius: 3px;
  border: 1px solid silver;

  background: white;
  font-size: 90%;
  font-family: monospace;

  max-height: 20em;
  overflow-y: auto;
}

最终的效果:

 2)代码折叠

代码折叠同样是个非常重要的功能,需要引入如下文件。

最后那个 indent-fold 不要漏了,我就是因为漏了这文件,发现 python 代码无法折叠,debug了很久源码才找到原因,浪费了很多时间!当然,如果用不到的话可以不引入。

import 'codemirror/addon/fold/foldgutter.css'
import 'codemirror/addon/fold/foldcode'
import 'codemirror/addon/fold/foldgutter'
import 'codemirror/addon/fold/brace-fold'
import 'codemirror/addon/fold/comment-fold'
import 'codemirror/addon/fold/markdown-fold'
import 'codemirror/addon/fold/xml-fold'
import 'codemirror/addon/fold/indent-fold'

几个重要的参数如下:

// 代码折叠
gutters: [
  "CodeMirror-lint-markers",
  "CodeMirror-linenumbers",
  "CodeMirror-foldgutter"
],
foldGutter: true, // 启用行槽中的代码折叠

做完这些配置后,代码就应该可以折叠了,无需额外写 js。

3)以小红点方式显示空格

两个重要的配置:

specialChars :正则表达式,用于描述哪些特殊字符应该被替换为special placeholder。通常用于无法打印的特殊字符。默认为/[\u0000-\u001f\u007f\u00ad\u200b-\u200f\u2028\u2029\ufeff]/。

specialCharPlaceholder :当传入一个与specialChars匹配的字符时,返回一个用于替换的DOM节点。默认为一个红色的点(·)

这个功能也挺有意思的,其实开启的话很简单,只要增加 specialChars 参数,正则中加上空格就行了,但是呢,默认的样式很丑,是个很大很深的红点,如下图所示:

 所以呢必须要改,那么查看 codemirror.js 源码,下面贴出关键的部分:

function defaultSpecialCharPlaceholder(ch) {
  var token = elt("span", "\u2022", "cm-invalidchar");
  token.title = "\\u" + ch.charCodeAt(0).toString(16);
  token.setAttribute("aria-label", token.title);
  return token
}

function elt(tag, content, className, style) {
  var e = document.createElement(tag);
  if (className) { e.className = className; }
  if (style) { e.style.cssText = style; }
  if (typeof content == "string") { e.appendChild(document.createTextNode(content)); }
  else if (content) { for (var i = 0; i < content.length; ++i) { e.appendChild(content[i]); } }
  return e
}

根据源码中的 defaultSpecialCharPlaceholder 和 elt 两个方法,我自定义了specialCharPlaceholder 的配置,用于替代默认的空格样式。这里去掉了一些我用不到的,比如非字符串的校验等。然后将大的点替换成了小的点,编码为 \u002e ,自定义的配置如下:

specialChars: /[\u0000-\u001f\u007f-\u009f\u00ad\u061c\u200b-\u200f\u2028\u2029\ufeff\ufff9-\ufffc]/g,
specialCharPlaceholder: function (ch) {
  let token = document.createElement("span");
  let content = "\u002e";
  token.className = "cm-invalidchar";
  if (typeof content == "string") {
	token.appendChild(document.createTextNode(content));
  }
  token.title = "\\u" + ch.charCodeAt(0).toString(16);
  token.setAttribute("aria-label", token.title);
  return token
}

这里specialChars我默认没改的,因为我想要的效果是默认不显示红点,当我想显示红点时再开启。所以我增加了一个开关,当设置为true时, 在 specialChars 的正则中增加 \s (\s表示空格),这样空格就能显示为我想要的小红点了。

并且对 cm-invalidchar 的样式也改了下,颜色修改为淡点的红色,颜色代码为:#909399

最终的样式如下:

4)制表符替换为4个空格

options中增加 extraKeys 配置,修改默认的tab行为。至于替换为几个空格数,是由参数 indentUint 控制的。

代码如下,这段是很久以前刚弄这个编辑器时,从某篇文章中借鉴的。

extraKeys: {
  Tab: (cm) => {
	if (cm.somethingSelected()) {      // 存在文本选择
	  cm.indentSelection('add');    // 正向缩进文本
	} else {                    // 无文本选择
	  cm.replaceSelection(Array(cm.getOption("indentUnit") + 1).join(" "), "end", "+input");  // 光标处插入 indentUnit 个空格
	}
  }
}

5)选中单词后,其他相同单词也高亮

需要引入如下文件并增加 option 配置:

import 'codemirror/addon/search/match-highlighter'

highlightSelectionMatches: {
  minChars: 2,
  trim: true,
  style: "matchhighlight",
  showToken: false
},

highlightSelectionMatches中 的 showToken 最好配置为 false,否则在在输入的时候,当前还没输入完的单词也会高亮,很难受,如下图所示:

最终效果如下:

这里高亮的样式我是改过的,为了适应这个主题,我重新了 cm-matchhighlight 的样式,修改了它的背景颜色:

.cm-matchhighlight {
  background: #848484 !important
}

6)自动补全括号,且光标在括号左右侧时,自动突出匹配的括号

引入如下文件并增加 option 参数:

import 'codemirror/addon/edit/matchbrackets'
import 'codemirror/addon/edit/closebrackets'

autoCloseBrackets: true, // 自动闭合符号
matchBrackets: true, // 在光标点击紧挨{、]括号左、右侧时,自动突出显示匹配的括号 }、]

7)其他有用配置

theme:主题样式,我用的monakai,这个就看个人喜好了
mode:这个很重要,不同的语言要使用对应的mode,这样才能有关键字的高亮!mode列表在中英文手册中可以查
lineWrapping:这个要配置为 false,如果配置为true,样式会比较奇怪,原因未知,或许跟我用的monakai主题有关?有知道的人可以告诉我下。
readOnly:决定是否只读
viewportMargin:一次加载多少行,防止文件过大页面卡死
lineNumbers:显示行数

当然,还有很多有用的功能,不过我暂时够用了,其他也就懒得继续研究了。
Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐