使用正则表达式实现一个类似于navicat中sql编辑器功能,大致实现以下目标:
- 指定关键字高亮(eg. 红色)
- 数字高亮(eg. 蓝色)
- 引号内容高亮(eg.浅绿色)
实现效果如下:
下面直接上代码,具体解释见代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>SQL编辑器</title>
<style>
#edit{
height:100px;
width:500px;
border:1px solid red;
padding: 5px;
background: black;
color: white;
white-space: pre-wrap;
word-wrap:break-word;
}
.hight-light-red{
color: red
}
.hight-light-yellow{
color: yellow
}
.hight-light-lightgreen{
color: lightgreen
}
</style>
</head>
<body>
<div id="edit" contenteditable>132adasd#csfsfspan</div>
<script>
//SQL关键字举例
const SQL_KEYWORDS = [
'show', 'case', 'when', 'use', 'alter', 'add', 'change', 'select', 'from', 'as', 'left', 'right', 'inner', 'join', 'using', 'where', 'like', 'between', 'and', 'or', 'in', 'not', 'null', 'exists', 'group', 'order',
'by', 'asc', 'desc', 'limit', 'having', 'union', 'all', 'distinct', 'create', 'truncate', 'drop', 'insert', 'delete', 'update', 'set', 'table', 'count', 'sum', 'max', 'min', 'hvg'
];
//正则替换高亮文本
function hightLight(el, keywords) {
const reg = RegExp(`(<)|(>)|(\\b(${keywords.join('|')})\\b)|(\\b(\\d+)\\b)|('[^']*'?|"[^"]*"?)`, 'gi');
el.innerHTML = el.innerText.replace(reg, (match, k1, k2, k3, k4, k5) => {
if (k1) {
//替换<
return `<`;
} else if (k2) {
//替换>
return `>`;
} else if (k3) {
//高亮关键字
return `<span class='hight-light-red'>${match}</span>`;
} else if (k5) {
//高亮数字(作为变量名时不高亮)
return `<span class='hight-light-yellow'>${match}</span>`;
} else {
//高亮引号内容
return `<span class='hight-light-lightgreen'>${match}</span>`;
}
});
}
//获取文本长度
function getNodeTextLength(node) {
return node.nodeName === '#text' ? node.length : node.innerText.length;
}
//获取光标相对于整个文本的位置
function getCursorIndex(el, node, index) {
if(node.previousSibling===null) {
return node.parentNode===el ? index : getCursorIndex(el, node.parentNode, index);
}
return node===el ? index : getCursorIndex(el, node.previousSibling, index + getNodeTextLength(node.previousSibling));
}
//根据光标位置(相对于整个文本的位置)获取具体的位置(光标所在元素的位置)
function getPosition(el, index) {
if(el.nodeName === '#text') {
return {el, index};
}
let childNodes = el.childNodes;
for(let i = 0; i<childNodes.length; i++) {
let node = childNodes[i];
let length = getNodeTextLength(node);
if(index <= length) {
return getPosition(node, index);
}
index -= length;
}
return {el, index};
}
//设置光标位置
function setSelectionPosition(start, end) {
const selection = window.getSelection();
const range = document.createRange();
range.setStart(start.el, start.index);
range.setEnd(end.el, end.index);
selection.removeAllRanges();
selection.addRange(range);
}
//监听输入框文本变化
let edit = document.getElementById('edit');
edit.oninput=function(e) {
let el = e.target;
let range = window.getSelection().getRangeAt(0);
let {startContainer, endContainer, startOffset, endOffset} = range;
let startIndex = getCursorIndex(el, startContainer, startOffset);
let endIndex = getCursorIndex(el, endContainer, endOffset);
hightLight(el, SQL_KEYWORDS);
e.inputType==='insertParagraph' && (startIndex+=1, endIndex+=1);
let start = getPosition(el, startIndex), end = getPosition(el, endIndex);
setSelectionPosition(start, end);
};
</script>
</body>
</html>