☻Blog("Laziji")

System.out.print("辣子鸡的博客");

安装

1
2
npm install monaco-editor@0.20.0
npm install monaco-editor-webpack-plugin@1.9.0

vue.config

1
2
3
4
5
6
7
8
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
module.exports = {
configureWebpack: {
plugins: [
new MonacoWebpackPlugin()
]
}
};

文件结构

1
2
3
4
5
6
monaco-editor
|- util
|- |- javascript-completion.js
|- |- sql-completion.js
|- |- log-language.js
|- MonacoEditor.vue

monaco-editor/MonacoEditor.vue

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
<template>
<div ref="editor" class="main"></div>
</template>

<script>
import * as monaco from "monaco-editor";
import createSqlCompleter from "./util/sql-completion";
import createJavascriptCompleter from "./util/javascript-completion";
import registerLanguage from "./util/log-language";
const global = {};

const getHints = (model) => {
let id = model.id.substring(6);
return (global[id] && global[id].hints) || [];
};
monaco.languages.registerCompletionItemProvider(
"sql",
createSqlCompleter(getHints)
);
monaco.languages.registerCompletionItemProvider(
"javascript",
createJavascriptCompleter(getHints)
);
registerLanguage(monaco);
/**
* monaco options
* https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.istandaloneeditorconstructionoptions.html
*/
export default {
props: {
options: {
type: Object,
default() {
return {};
},
},
value: {
type: String,
required: true,
},
language: {
type: String,
},
hints: {
type: Array,
default() {
return [];
},
},
},
name: "MonacoEditor",
data() {
return {
editorInstance: null,
defaultOptions: {
theme: "vs",
fontSize: 14,
},
};
},
watch: {
value() {
if (this.value !== this.editorInstance.getValue()) {
this.editorInstance.setValue(this.value);
}
},
},
mounted() {
this.initEditor();
global[this.editorInstance._id] = this;
window.addEventListener("resize", this.layout);
},
destroyed() {
this.editorInstance.dispose();
global[this.editorInstance._id] = null;
window.removeEventListener("resize", this.layout);
},
methods: {
layout() {
this.editorInstance.layout();
},
undo() {
this.editorInstance.trigger("anyString", "undo");
this.onValueChange();
},
redo() {
this.editorInstance.trigger("anyString", "redo");
this.onValueChange();
},
getOptions() {
let props = { value: this.value };
this.language !== undefined && (props.language = this.language);
let options = Object.assign({}, this.defaultOptions, this.options, props);
return options;
},
onValueChange() {
this.$emit("input", this.editorInstance.getValue());
this.$emit("change", this.editorInstance.getValue());
},
initEditor() {
this.MonacoEnvironment = {
getWorkerUrl: function () {
return "./editor.worker.bundle.js";
},
};

this.editorInstance = monaco.editor.create(
this.$refs.editor,
this.getOptions()
);
this.editorInstance.onContextMenu((e) => {
this.$emit("contextmenu", e);
});
this.editorInstance.onDidChangeModelContent(() => {
this.onValueChange();
});
this.editorInstance.addCommand(
monaco.KeyMod.CtrlCmd | monaco.KeyCode.KEY_S,
() => {
this.$emit("save", this.editorInstance.getValue());
}
);
},
},
};
</script>

<style scoped>
.main /deep/ .view-lines * {
font-family: Consolas, "Courier New", monospace !important;
}
</style>

monaco-editor/util/javascript-completion.js

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
import * as monaco from 'monaco-editor'
// js 有内置提示
function createCompleter(getExtraHints) {
const createSuggestions = function (model, textUntilPosition) {
let text = model.getValue();
textUntilPosition = textUntilPosition.replace(/[\*\[\]@\$\(\)]/g, "").replace(/(\s+|\.)/g, " ");
let arr = textUntilPosition.split(/[\s;]/);
let activeStr = arr[arr.length - 1];
let len = activeStr.length;
let rexp = new RegExp("([^\\w]|^)" + activeStr + "\\w*", "gim");
let match = text.match(rexp);
let mergeHints = Array.from(new Set([...getExtraHints(model)]))
.sort()
.filter(ele => {
let rexp = new RegExp(ele.substr(0, len), "gim");
return (match && match.length === 1 && ele === activeStr) ||
ele.length === 1 ? false : activeStr.match(rexp);
});
return mergeHints.map(ele => ({
label: ele,
kind: monaco.languages.CompletionItemKind.Text,
documentation: ele,
insertText: ele
}));
};
return {
provideCompletionItems(model, position) {
let textUntilPosition = model.getValueInRange({
startLineNumber: position.lineNumber,
startColumn: 1,
endLineNumber: position.lineNumber,
endColumn: position.column
});
return { suggestions: createSuggestions(model, textUntilPosition) };
}
}
}
export default createCompleter;

monaco-editor/util/sql-completion.js

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
import * as monaco from 'monaco-editor'
const hints = [
"SELECT",
"INSERT",
"DELETE",
"UPDATE",
"CREATE TABLE",
"DROP TABLE",
"ALTER TABLE",
"CREATE VIEW",
"DROP VIEW",
"CREATE INDEX",
"DROP INDEX",
"CREATE PROCEDURE",
"DROP PROCEDURE",
"CREATE TRIGGER",
"DROP TRIGGER",
"CREATE SCHEMA",
"DROP SCHEMA",
"CREATE DOMAIN",
"ALTER DOMAIN",
"DROP DOMAIN",
"GRANT",
"DENY",
"REVOKE",
"COMMIT",
"ROLLBACK",
"SET TRANSACTION",
"DECLARE",
"EXPLAN",
"OPEN",
"FETCH",
"CLOSE",
"PREPARE",
"EXECUTE",
"DESCRIBE",
"FORM",
"ORDER BY"]
function createCompleter(getExtraHints) {
const createSuggestions = function (model, textUntilPosition) {
let text = model.getValue();
textUntilPosition = textUntilPosition.replace(/[\*\[\]@\$\(\)]/g, "").replace(/(\s+|\.)/g, " ");
let arr = textUntilPosition.split(/[\s;]/);
let activeStr = arr[arr.length - 1];
let len = activeStr.length;
let rexp = new RegExp("([^\\w]|^)" + activeStr + "\\w*", "gim");
let match = text.match(rexp);
let textHints = !match ? [] :
match.map(ele => {
let rexp = new RegExp(activeStr, "gim");
let search = ele.search(rexp);
return ele.substr(search);
});
let mergeHints = Array.from(new Set([...hints, ...textHints, ...getExtraHints(model)]))
.sort()
.filter(ele => {
let rexp = new RegExp(ele.substr(0, len), "gim");
return (match && match.length === 1 && ele === activeStr) ||
ele.length === 1 ? false : activeStr.match(rexp);
});
return mergeHints.map(ele => ({
label: ele,
kind: hints.indexOf(ele) > -1 ?
monaco.languages.CompletionItemKind.Keyword :
monaco.languages.CompletionItemKind.Text,
documentation: ele,
insertText: ele
}));
};
return {
provideCompletionItems(model, position) {
let textUntilPosition = model.getValueInRange({
startLineNumber: position.lineNumber,
startColumn: 1,
endLineNumber: position.lineNumber,
endColumn: position.column
});
return { suggestions: createSuggestions(model, textUntilPosition) };
}
}
}
export default createCompleter;

monaco-editor/util/log-language.js

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
function registerLanguage(monaco) {
monaco.languages.register({
id: "log"
});
monaco.languages.setMonarchTokensProvider("log", {
tokenizer: {
root: [
[/(^[=a-zA-Z].*|\d\s.*)/, "log-normal"],
[/\sERROR\s.*/, "log-error"],
[/\sWARN\s.*/, "log-warn"],
[/\sINFO\s.*/, "log-info"],
[
/^([0-9]{4}||[0-9]{2})-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}(.[0-9]{3})?/,
"log-date",
],
[
/^[0-9]{2}\/[0-9]{2}\/[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}(.[0-9]{3})?/,
"log-date",
],
[/(^\*\*Waiting queue:.*)/, "log-info"],
[/(^\*\*result tips:.*)/, "log-info"],
],
},
});
monaco.editor.defineTheme("log", {
base: "vs",
inherit: true,
rules: [{
token: "log-info",
foreground: "4b71ca"
},
{
token: "log-error",
foreground: "ff0000",
fontStyle: "bold"
},
{
token: "log-warn",
foreground: "FFA500"
},
{
token: "log-date",
foreground: "008800"
},
{
token: "log-normal",
foreground: "808080"
},
],
colors: {
"editor.lineHighlightBackground": "#ffffff",
"editorGutter.background": "#f7f7f7",
},
});

}

export default registerLanguage;

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
<monaco-editor
v-model="value"
language="sql"
:options="{}"
:hints="['table_name']"
style="width:400px;height:300px"
/>
</template>
<script>
import MonacoEditor from "@/components/monaco-editor/MonacoEditor";
export default {
components: {
MonacoEditor,
},
data() {
return {
value: "xxx",
};
},
};
</script>

实现

对值进行分类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function getObjectType(object) {
if (object === undefined) {
return "undefined";
}
if (object === null) {
return "null";
}
if (typeof object !== "object") {
return "unit";
}
if (object instanceof Array) {
return "array";
}
return "object";
}

arrayobject的值进行递归合并

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
function deepMergeObject(...objects) {
let subs = [];
for (let obj of objects) {
if (obj === undefined) {
continue;
}
if (subs.length > 0 && getObjectType(obj) !== getObjectType(subs[0])) {
subs = [obj];
} else {
subs.push(obj);
}
}
if (subs.length === 0) {
return undefined;
}
let type = getObjectType(subs[0]);
if (subs.length === 1 || (type !== "object" && type !== "array")) {
return subs[subs.length - 1];
}
let keySet = new Set();
for (let obj of objects) {
for (let k in obj) {
keySet.add(k);
}
}
let newObj = type === "object" ? {} : [];
for (let k of keySet) {
let child = [];
for (let obj of subs) {
child.push(obj[k]);
}
newObj[k] = deepMergeObject(...child);
}
return newObj;
}

说明

数据分为undefinednullunitobjectarray,
deepMergeObject(a,b,c,d)调用过程中, 不同类型后者覆盖前者,
忽略undefined, objectarray进行keyindex遍历对子值进行递归合并

0%