使用正则表达式实现一个类似于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-deepskyblue{
color: deepskyblue
}
.hight-light-red{
color: red
}
.hight-light-lightgreen{
color: lightgreen
}
</style>
</head>
<body>
<div id="edit" contenteditable>select a.*
from a
left join b on a.b_id = b.id
where a.name like '%杰%' and a.age > 35 and b.type_code = '001'
order by a.name desc</div>
<script>
let hljs = (function(){
//正则替换高亮文本
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-deepskyblue'>${match}</span>`;
} else if (k5) {
//高亮数字(作为变量名时不高亮)
return `<span class='hight-light-lightgreen'>${match}</span>`;
} else {
//高亮引号内容
return `<span class='hight-light-red'>${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 getSelectionPosition(el, inputType) {
const selection = window.getSelection();
let startIndex = 0, endIndex = 0;
if(selection.rangeCount > 0) {
const range = selection.getRangeAt(0);
let {startContainer, endContainer, startOffset, endOffset} = range;
startIndex = getCursorIndex(el, startContainer, startOffset);
endIndex = getCursorIndex(el, endContainer, endOffset);
}
inputType==='insertParagraph' && (startIndex+=1, endIndex+=1);
return {startIndex, endIndex};
}
//设置光标位置
function setSelectionPosition(el, startIndex, endIndex) {
const start = getPosition(el, startIndex), end = getPosition(el, endIndex);
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);
}
return {
autoHightLight: function(el, keywords) {
//监听输入框文本变化
el.oninput = function(e) {
const {target, inputType} = e, {startIndex, endIndex} = getSelectionPosition(target, inputType);
hightLight(target, keywords);
setSelectionPosition(target, startIndex, endIndex);
};
//初始触发input事件
el.dispatchEvent(new InputEvent("input"));
}
};
})();
</script>
<script>
const edit = document.getElementById('edit');
//SQL关键字举例
const SQL_KEYWORDS = [
'show', 'case', 'when', 'use', 'alter', 'add', 'change', 'select', 'from', 'as', 'left', 'right', 'inner', 'join', 'on', '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'
];
hljs.autoHightLight(edit, SQL_KEYWORDS);
</script>
</body>
</html>
实现效果如下: