@tidbcloud/codemirror-extension-sql-parser
Version:
codemiror extension to parser editor content to SQL statements
106 lines (103 loc) • 3.26 kB
JavaScript
import { ensureSyntaxTree } from '@codemirror/language';
import { StateEffect, StateField } from '@codemirror/state';
import { EditorView } from '@codemirror/view';
// state effect
const statementsEffect = StateEffect.define();
// state field
const statementsField = StateField.define({
create() {
return [];
},
update(value, tr) {
for (let effect of tr.effects) {
if (effect.is(statementsEffect)) {
return effect.value;
}
}
return value;
}
});
// regex to match use statement
// examples:
// - use test;
// - use test ;
// - USE `test`;
const useStatementRegex = /^use\s+[`]?([a-zA-Z0-9_-]+)[`]?\s*;$/i;
const ddlStatementRegex = /^(create|drop|alter|truncate|rename|comment|grant|revoke)/i;
// event listener
const statementsParser = () => {
let first = true;
return EditorView.updateListener.of((v) => {
if (first) {
first = false;
}
else {
if (!v.docChanged && !v.viewportChanged)
return;
}
const { state } = v;
const statements = [];
let database = '';
// syntaxTree() only parse the content in the visible area
// syntaxTree(state)
ensureSyntaxTree(state, v.view.viewport.to, 1 * 1000)
?.cursor()
.iterate((node) => {
if (node.name === 'Script') {
// root node
return true;
}
if (node.name === 'Statement') {
let type = 'other';
const content = state.sliceDoc(node.from, node.to);
const match = content.match(useStatementRegex);
if (match) {
database = match[1];
type = 'use';
}
else if (content.match(ddlStatementRegex)) {
type = 'ddl';
}
const lineFrom = state.doc.lineAt(node.from);
const lineTo = state.doc.lineAt(node.to);
statements.push({
from: node.from,
to: node.to,
lineFrom: lineFrom.number,
lineTo: lineTo.number,
content,
database,
type
});
}
return true;
});
v.view.dispatch({ effects: statementsEffect.of(statements) });
});
};
//-------------------
function getSqlStatements(state) {
return state.field(statementsField);
}
function getNearbyStatement(state, pos) {
const allStatements = getSqlStatements(state);
let target;
// find the nearest statement before the pos
for (let i = allStatements.length - 1; i >= 0; i--) {
const s = allStatements[i];
if (s.to <= pos) {
target = s;
break;
}
}
// if no statement found, choose the first statement
if (!target && allStatements.length > 0) {
target = allStatements[0];
}
return target;
}
//-------------------
function sqlParser() {
return [statementsField, statementsParser()];
}
export { getNearbyStatement, getSqlStatements, sqlParser, useStatementRegex };