AceEditor开发的坑

本文最后更新于:几秒前

禁止首行编辑

使用到ace实现一个首行禁止编辑的功能,我们知道编辑操作可以这些:插入、删除、粘贴。只要阻止第一行的相关操作就可以了

但是,中文输入法输入会导致一些奇奇怪怪的问题。所以我们另外对光标进行处理,只要光标点击第一行我们就给他跳转到第二行

show me the code…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//光标跳转
this.ace.getSession().selection.on('changeCursor', (e)=>{
if(this.ace.getSelectionRange().start.row == 0 || this.ace.getSelectionRange().end.row == 0){
this.ace.gotoLine(2);
}
});
//不能对第一行进行粘贴、删除操作
this.ace.commands.on("exec", (e)=>{
const hasFirst = this.ace.getSelectionRange().start.row == 0 ;
const command = e.command.description;
if ((hasFirst && ( command == "Paste" || command == "Backspace"))) {
e.preventDefault()
e.stopPropagation()
}
});

以上方法能解决大部分问题,但是移动文本能成功修改(2020-11-21)

下面是stackoverflow上面的一个相对来说比较完美解决办法(传送门

重点代码是对原有编辑器方法的拦截修改

我进行了一些修改和注释:

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
/**
* Ace Editor 锁定或只读行封装
* @author CHANX 2020-11-21
* @param {ace} editor ace编辑器实例
* @param {number[]} [readOnlyLines = []] 需要锁定的行号
*/

function setReadOnly(editor, readOnlyLines) {
const session = editor.session;
const Range = editor;
const readOnlyRanges = [];
for (let i = 0; i < readOnlyLines.length; i += 1) {
const newRange = [readOnlyLines[i] - 1, 0, readOnlyLines[i], 0];
// 此处原有代码range是当前行头到下一行头,我修改为当前行头到当前行1000位置
// 防止下一行头无法被编辑
readOnlyRanges.push({
start: {
row: readOnlyLines[i] - 1,
column: 0,
},
end: {
row: readOnlyLines[i] - 1,
column: 1000,
},
});
}
function before(obj, method, wrapper) {
const orig = obj[method];
obj[method] = function (...arg) {
const args = Array.prototype.slice.call(arg);
return wrapper.call(this, () => orig.apply(obj, args), args);
};
return obj[method];
}

/**
* 当前选中范围和传入范围是否冲突
* @param {*} range
*/
function intersects(range) {
return editor.getSelectionRange().intersects(range);
}
function preventReadonly(next, args) {
for (let i = 0; i < readOnlyRanges.length; i += 1) { if (intersects(readOnlyRanges[i])) return; }
next();
}
/**
* 当前选中范围和不可编辑行范围是否冲突
* @param {*} newRange
*/
function intersectsRange(newRange) {
for (let i = 0; i < readOnlyRanges.length; i += 1) { if (newRange.intersects(readOnlyRanges[i])) return true; }
return false;
}
/**
* 是否在不可编辑行的末尾
* @param {*} position
*/
function onEnd(position) {
const row = position.row;
const column = position.column;
for (let i = 0; i < readOnlyRanges.length; i += 1) { if (readOnlyRanges[i].end.row === row && readOnlyRanges[i].end.column === column) return true; }
return false;
}
/**
* 是否在不可编辑行的范围外
* @param {*} position
*/
function outSideRange(position) {
const row = position.row;
const column = position.column;
for (let i = 0; i < readOnlyRanges.length; i += 1) {
if (readOnlyRanges[i].start.row < row && readOnlyRanges[i].end.row > row) { return false; }
if (readOnlyRanges[i].start.row === row && readOnlyRanges[i].start.column < column) {
if (readOnlyRanges[i].end.row !== row || readOnlyRanges[i].end.column > column) { return false; }
} else if (readOnlyRanges[i].end.row === row && readOnlyRanges[i].end.column > column) {
return false;
}
}
return true;
}
editor.keyBinding.addKeyboardHandler({
handleKeyboard(data, hash, keyString, keyCode, event) {
// 阻止不可编辑行行尾的回车
if (Math.abs(keyCode) === 13 && onEnd(editor.getCursorPosition())) {
return false;
}
if (hash === -1 || (keyCode <= 40 && keyCode >= 37)) return false;
for (let i = 0; i < readOnlyRanges.length; i += 1) {
if (intersects(readOnlyRanges[i])) {
return { command: "null", passEvent: false };
}
}
},
});
before(editor, "onPaste", preventReadonly);
before(editor, "onCut", preventReadonly);

const old$tryReplace = editor.$tryReplace;
editor.$tryReplace = function (range, replacement) {
return intersectsRange(range) ? null : old$tryReplace.apply(this, arguments);
};

/**
* 重写insert方法: 若postion在不可编辑范围内则不可插入
*/
const oldInsert = session.insert;
session.insert = function (position, text) {
return oldInsert.apply(this, [position, outSideRange(position) ? text : ""]);
};
/**
* 重写remove方法: 若range在不可编辑范围内则不可以删除
*/
const oldRemove = session.remove;
session.remove = function (range) {
return intersectsRange(range) ? false : oldRemove.apply(this, arguments);
};

/**
* 重写moveText方法: 若toPosition在不可编辑范围内则可不以移动
*/
const oldMoveText = session.moveText;
session.moveText = function (fromRange, toPosition, copy) {
if (intersectsRange(fromRange) || !outSideRange(toPosition)) return fromRange;
return oldMoveText.apply(this, arguments);
};
}

export default setReadOnly;

初始内容可被撤销/重置撤销栈

每次ace初始化完毕并赋值初始内容后,此时ctrl+z会发生内容消失的情况。因为赋值初始内容其实是一次输入(空 => 初始内容),此时撤销栈会压栈。

解决办法就是,在初始内容赋值完成后立即重置撤销栈。

1
2
3
4
//初始内容
editor.setValue("And now how can I reset the\nundo stack,-1");
//重置撤销栈UndoManager是保持所有历史的
editor.getSession().setUndoManager(new ace.UndoManager())

ace添加自定义主题

1. 编辑样式代码

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
.ace-vs-dark .ace_gutter {
background: #1e1e1e;
color: #858585;
overflow: hidden;
}
.ace-vs-dark .ace_print-margin {
width: 1px;
background: #e8e8e8;
}
.ace-vs-dark {
background-color: #1e1e1e;
color: #dcdcdc;
}
.ace-vs-dark .ace_cursor {
color: #dcdcdc;
}
.ace-vs-dark .ace_invisible {
color: #ffffff40;
}
.ace-vs-dark .ace_constant.ace_buildin {
color: #569cd6;
}
.ace-vs-dark .ace_constant.ace_language {
color: #b4cea8;
}
.ace-vs-dark .ace_constant.ace_library {
color: #b5cea8;
}
.ace-vs-dark .ace_invalid {
background-color: transparent;
color: #ff3333;
}
.ace-vs-dark .ace_fold {
}
.ace-vs-dark .ace_support.ace_function {
color: #dcdcdc;
}
.ace-vs-dark .ace_support.ace_constant {
color: #569cd6;
}
.ace-vs-dark .ace_support.ace_type,
.ace-vs-dark .ace_support.ace_class .ace-vs-dark .ace_support.ace_other {
color: #4ec9b0;
}
.ace-vs-dark .ace_variable.ace_parameter {
color: #dcdcdc;
}
.ace-vs-dark .ace_keyword.ace_operator {
color: #dcdcdc;
}
.ace-vs-dark .ace_comment {
color: #608b4e;
}
.ace-vs-dark .ace_comment.ace_doc {
color: #608b4e;
}
.ace-vs-dark .ace_comment.ace_doc.ace_tag {
color: #608b4e;
}
.ace-vs-dark .ace_constant.ace_numeric {
color: #b5cea8;
}
.ace-vs-dark .ace_variable {
color: #dcdcdc;
}
.ace-vs-dark .ace_xml-pe {
/**/
color: rgb(104, 104, 91);
}
.ace-vs-dark .ace_entity.ace_name.ace_function {
color: #dcdcdc;
}
.ace-vs-dark .ace_heading {
color: #569cd6;
}
.ace-vs-dark .ace_list {
color: #dcdcdc;
}
.ace-vs-dark .ace_marker-layer .ace_selection {
/**/
background: rgb(181, 213, 255);
}
.ace-vs-dark .ace_marker-layer .ace_step {
/**/
background: rgb(252, 255, 0);
}
.ace-vs-dark .ace_marker-layer .ace_stack {
/**/
background: rgb(164, 229, 101);
}
.ace-vs-dark .ace_marker-layer .ace_bracket {
/**/
margin: -1px 0 0 -1px;
border: 1px solid rgb(192, 192, 192);
}
.ace-vs-dark .ace_marker-layer .ace_active-line {
/**/
background: rgba(0, 0, 0, 0.07);
}
.ace-vs-dark .ace_gutter-active-line {
background-color: #0f0f0f;
}
.ace-vs-dark .ace_marker-layer .ace_selected-word {
/**/
background: rgb(250, 250, 255);
border: 1px solid rgb(200, 200, 250);
}
.ace-vs-dark .ace_storage,
.ace-vs-dark .ace_keyword,
.ace-vs-dark .ace_meta.ace_tag {
color: #569cd6;
}
.ace-vs-dark .ace_string.ace_regex {
/**/
color: rgb(255, 0, 0);
}
.ace-vs-dark .ace_string {
color: #d69d85;
}
.ace-vs-dark .ace_entity.ace_other.ace_attribute-name {
color: #92caf4;
}

2. 编辑模块代码

将css样式文本复制粘贴进去即可

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
/* eslint-disable */
ace.define("ace/theme/vs-dark",["require","exports","module","ace/lib/dom"], function(require, exports, module) {

exports.isDark = true;
exports.cssClass = "ace-vs-dark";
exports.cssText = ".ace-vs-dark .ace_gutter {\
background: #1e1e1e;\
color: #858585;\
overflow: hidden;\
}\
.ace-vs-dark .ace_print-margin {\
width: 1px;\
background: #e8e8e8;\
}\
.ace-vs-dark {\
background-color: #1e1e1e;\
color: #dcdcdc;\
}\
.ace-vs-dark .ace_cursor {\
color: #dcdcdc;\
}\
.ace-vs-dark .ace_invisible {\
color: #ffffff40;\
}\
.ace-vs-dark .ace_constant.ace_buildin {\
color: #569cd6;\
}\
.ace-vs-dark .ace_constant.ace_language {\
color: #b4cea8;\
}\
.ace-vs-dark .ace_constant.ace_library {\
color: #b5cea8;\
}\
.ace-vs-dark .ace_invalid {\
background-color: transparent;\
color: #ff3333;\
}\
.ace-vs-dark .ace_fold {\
}\
.ace-vs-dark .ace_support.ace_function {\
color: #dcdcdc;\
}\
.ace-vs-dark .ace_support.ace_constant {\
color: #569cd6;\
}\
.ace-vs-dark .ace_support.ace_type,\
.ace-vs-dark .ace_support.ace_class .ace-vs-dark .ace_support.ace_other {\
color: #4ec9b0;\
}\
.ace-vs-dark .ace_variable.ace_parameter {\
color: #dcdcdc;\
}\
.ace-vs-dark .ace_keyword.ace_operator {\
color: #dcdcdc;\
}\
.ace-vs-dark .ace_comment {\
color: #608b4e;\
}\
.ace-vs-dark .ace_comment.ace_doc {\
color: #608b4e;\
}\
.ace-vs-dark .ace_comment.ace_doc.ace_tag {\
color: #608b4e;\
}\
.ace-vs-dark .ace_constant.ace_numeric {\
color: #b5cea8;\
}\
.ace-vs-dark .ace_variable {\
color: #dcdcdc;\
}\
.ace-vs-dark .ace_xml-pe {\
/**/\
color: rgb(104, 104, 91);\
}\
.ace-vs-dark .ace_entity.ace_name.ace_function {\
color: #dcdcdc;\
}\
.ace-vs-dark .ace_heading {\
color: #569cd6;\
}\
.ace-vs-dark .ace_list {\
color: #dcdcdc;\
}\
.ace-vs-dark .ace_marker-layer .ace_selection {\
/**/\
background: rgb(181, 213, 255);\
}\
.ace-vs-dark .ace_marker-layer .ace_step {\
/**/\
background: rgb(252, 255, 0);\
}\
.ace-vs-dark .ace_marker-layer .ace_stack {\
/**/\
background: rgb(164, 229, 101);\
}\
.ace-vs-dark .ace_marker-layer .ace_bracket {\
/**/\
margin: -1px 0 0 -1px;\
border: 1px solid rgb(192, 192, 192);\
}\
.ace-vs-dark .ace_marker-layer .ace_active-line {\
/**/\
background: rgba(0, 0, 0, 0.07);\
}\
.ace-vs-dark .ace_gutter-active-line {\
background-color: #0f0f0f;\
}\
.ace-vs-dark .ace_marker-layer .ace_selected-word {\
/**/\
background: rgb(250, 250, 255);\
border: 1px solid rgb(200, 200, 250);\
}\
.ace-vs-dark .ace_storage,\
.ace-vs-dark .ace_keyword,\
.ace-vs-dark .ace_meta.ace_tag {\
color: #569cd6;\
}\
.ace-vs-dark .ace_string.ace_regex {\
/**/\
color: rgb(255, 0, 0);\
}\
.ace-vs-dark .ace_string {\
color: #d69d85;\
}\
.ace-vs-dark .ace_entity.ace_other.ace_attribute-name {\
color: #92caf4;\
}\n";


});

3. 引入自定义主题

1
2
3
4
5
6
7
8
9
10
11
ace.config.setModuleUrl(
"ace/theme/vs-dark",
// eslint-disable-next-line import/no-webpack-loader-syntax
require("file-loader?esModule=false!./vs-dark.js")
);


ace.edit(context, {
fontSize: 15,
theme: "ace/theme/vs-dark",
}

AceEditor开发的坑
https://www.chanx.tech/2020/5a731ae5d023/
作者
ischanx
发布于
2020年9月20日
更新于
2023年9月8日
许可协议