在vue中使⽤CodeMirror创建XML编辑器经验总结(⼀)
  使⽤codemirror做完项⽬⼀段时间了,来进⾏⼀个功能总结,希望能帮助到⼩伙伴们。编辑器包括基础功能代码提⽰、格式化、搜索替换、变量跳转,代码字体放⼤缩⼩,注释等,⾃定义功能包括在编辑器中查看图⽚,插⼊⾃定义代码块,代码实时⾼亮等。由于篇幅太长,本篇介绍代码提⽰,格式化,搜索替换功能。其它的功能依次见下篇⽂章~
  1)基础的引⼊ 
html代码
<textarea ref="code"></textarea>
javascript代码
1 const CodeMirror = require("codemirror/lib/codemirror"); // 后续要使⽤CodeMIrror,故使⽤require⽅式引⼊
2 import "codemirror/lib/codemirror.css";
3 import "codemirror/mode/xml/xml"; // xml编辑器模式
4 import "codemirror/theme/monokai.css"; // 主题
5this.editor = CodeMirror.fromTextArea(this.$de, {
6    mode: "application/xml",
7    lineNumbers: true, // 显⽰⾏号
8    styleActiveLine: true,  // ⾼亮当前⾏
9      theme: "monokai",  // 主题
10 }
  2)代码提⽰功能
实现代码提⽰功能⾸先要定义需要提⽰的代码代码⽂件,然后需要设置给编辑器
提⽰的代码:
1  export const tags = {
2      "!attrs": { // 若tag标签⾥边没有设置attr属性,则所有的标签⾃带id和class属性,此为⾃定义项,⾮必须
3          id: null,
4          class: ["A", "B", "C"]
5      },
6      Button: {
7          attrs: {
8              x: null,
9              y: null,
10            w: null,
11            h: null,
12            align: ["center", "left", "right"],
13            alignV: ["center", "top", "bottom"],
14            Visibility: null
15          }
16      }
17  }
触发条件定义
1 CodeMirrormandspleteAfter = function (cm, pred) {
2if (!pred || pred()) setTimeout(function () {
3if (!cm.statepletionActive)
4            cm.showHint({ completeSingle: false });
5    }, 100);
6return CodeMirror.Pass;
7 };
8 CodeMirrormandspleteIfAfterLt = function (cm) {
9return CodeMirrormandspleteAfter(cm, function () {
10var cur = cm.getCursor();
Range(CodeMirror.Pos(cur.line, cur.ch - 1), cur) == "<";
12    });
13 };
14 CodeMirrormandspleteIfInTag = function (cm) {
15return CodeMirrormandspleteAfter(cm, function () {
16var tok = cm.Cursor());
17var reg = /[""]/;
18if (pe == "string" && (!st(tok.string.charAt(tok.string.length - 1)) || tok.string.length == 1)) return false;
19var inner = CodeMirror.Mode(), tok.state).state;
20return inner.tagName;
21    });
22 };
javascript代码
1this.editor = CodeMirror.fromTextArea(this.$de, {
2    extraKeys: {
3          "'<'": "completeAfter",
4          "'/'": "completeIfAfterLt",
5          "' '": "completeIfInTag",
6          "'='": "completeIfInTag",
editor at large7    }
8 }
9this.editor.setOption("hintOptions", { schemaInfo: this.tags })
  3)格式化
格式化定义
1 CodeMirror.defineExtension("autoFormatRange", function (from, to) {
2var cm = this;
3var outer = cm.getMode(),
4        text = cm.getRange(from, to).split("\n");
5var state = pyState(outer, cm.getTokenAt(from).state);
6var tabSize = cm.getOption("tabSize");
7
8var out = "",
9        lines = 0,
10        atSol = from.ch == 0;
11
12function newline() {
13        out += "\n";
14        atSol = true;
15        ++lines;
16    }
17
18for (var i = 0; i < text.length; ++i) {
19var stream = new CodeMirror.StringStream(text[i], tabSize);
20while (!l()) {
21var inner = CodeMirror.innerMode(outer, state);
22var style = ken(stream, state),
23                cur = stream.current();
24            stream.start = stream.pos;
25if (!atSol || /\S/.test(cur)) {
26                out += cur;
27                atSol = false;
28            }
29if (
30                !atSol &&
31                wlineAfterToken &&
32                wlineAfterToken(
33                    style,
34                    cur,
35                    stream.string.slice(stream.pos) || text[i + 1] || "",
36                    inner.state
37                )
38            )
39                newline();
40        }
41if (!stream.pos && outer.blankLine) outer.blankLine(state);
42if (!atSol) newline();
43    }
44
45    cm.operation(function () {
46        cm.replaceRange(out, from, to);
47for (var cur = from.line + 1, end = from.line + lines; cur <= end; ++cur) {
48            cm.indentLine(cur, "smart")
49        }
50    });
51 });
javascript代码
1this.editor = CodeMirror.fromTextArea(this.$de, {
2    extraKeys: {
3        "Shift-Tab": function () { // 选中部分格式化
4              that.formatSelection()
5          },
6          "Shift-Alt": function () {  // 全⽂格式化,快捷键可⾃定义,根据具体业务此处格式化的同时要处理其它逻辑,故把此处代码提出来写
7                that.format()
8          }
9      }
10  }
11// 选中部分格式化 indentAuto为codemirror⾃带api
12  formatSelection() {
13        CodeMirrormands.indentAuto(this.editor);
14  },
15// 格式化
16  format() {
17this.editor.autoFormatRange({ line: 0, ch: 0 }, { line: this.editor.lineCount() });
18  }
  4)搜索替换
搜索替换框
功能实现,cm-search插件中的代码全部保留,以下都是分析源代码之后新添加的代码
  为了计算匹配个数,我们需要在最开始的地⽅引⼊StringStream对象
1 const StringStream = CodeMirror.StringStream
  在每次需要搜索替换时获取state并调⽤startSearch⽅法
1var state = getSearchState(cm);
2function SearchState() {
3this.posFrom = this.posTo = this.lastQuery = this.query = null;
4this.overlay = null;
5 }
6function getSearchState(cm) {
7return cm.state.search || (cm.state.search = new SearchState());
8  }
1this.startSearch = function (cm, state, query) {
2        state.query = parseQuery(query);
3if (self.caseSensitiveOption.checked == undefined) { // caseSensitiveOption.checked=true 开启区分⼤⼩写
4            self.caseSensitiveOption.checked = false
5        }
6if (self.wholeWordOption.checked == undefined) { // caseSensitiveOption.checked=true 开启全字匹配
7            self.wholeWordOption.checked = false
8        }
9if (ExpOption.checked == undefined) { // regExpOption.checked=true 开启正则
10            ExpOption.checked = false
11        }
12        cm.removeOverlay(state.overlay, self.caseSensitiveOption.checked);
13        state.overlay = searchOverlay(
14            state.query, self.caseSensitiveOption.checked, self.wholeWordOption.checked, ExpOption.checked);
15        cm.addOverlay(state.overlay, {
16            opaque: true
17        });
18    }
  在startSearch中引⽤了searchOverlay和parseQuery⽅法,⽤于⾼亮和匹配个数计算
function searchOverlay(query, caseSensitive, wholeWord, regExp) {
if (regExp || wholeWord) {
if (wholeWord)  query = '\\b' + query + '\\b';
query = RegExp(query);
}
if (typeof query == "string")
query = new place(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"), caseSensitive ? "g" : "gi");
else if (!query.global)
query = new RegExp(query.source, caseSensitive ? "g" : "gi");
// 获取匹配个数以及当前匹配index,totalMatch为总匹配个数,curMatchIndex为当前匹配索引
totalMatch = 0
var current = cm.listSelections()[0]
cm.eachLine((line) => {
var stream = new , cm.options.tabSize)
var match = (stream.string);
var lineNumber = cm.getLineNumber(line)
while (match) {
totalMatch += 1
if (lineNumber == current.anchor.line && match.index == current.anchor.ch) {
curMatchIndex = totalMatch
}
stream = new , cm.options.tabSize)
match = (stream.string); // 从字符串出现的位置的下⼀位置开始继续查
}
})
if (totalMatch) {
aceTotal.innerHTML = totalMatch + "中的" + curMatchIndex
} else {
aceTotal.innerHTML = "⽆结果"
}
// 给匹配到的添加class值cm-searching,实现代码⾼亮
return {
token: function (stream) {
query.lastIndex = stream.pos;
var match = (stream.string);
if (match && match.index == stream.pos) {
stream.pos += match[0].length || 1;
return "searching";
} else if (match) {
stream.pos = match.index;
} else {
stream.skipToEnd();
}
}
};
}
function parseString(string) {
place(/\\(.)/g, function (_, ch) {
if (ch == "n") return "\n";
if (ch == "r") return "\r";
return ch;
});
};
function parseQuery(query) {
if () {
return query;
}
var isRE = query.indexOf('/') === 0 && query.lastIndexOf('/') > 0;
if (!!isRE) {
try {
var matches = query.match(/^\/(.*)\/([a-z]*)$/);
query = new RegExp(matches[1], matches[2].indexOf("i") == -1 ? "" : "i");        } catch (e) { } // Not a regular expression after all, do a string search
} else {
query = parseString(query);
}
if (typeof query == "string" ? query == "" : st("")) query = /x^/;
return query;
}
  希望⼩伙伴们多多指正哈~