Add a function getQueryAt to get the SQL query at given cursor position from a CodeMirror input

This commit is contained in:
Aditya Toshniwal 2024-04-13 15:47:18 +05:30
parent e612140473
commit ee0687ecd3

View File

@ -2,6 +2,7 @@ import {
EditorView
} from '@codemirror/view';
import { StateEffect, EditorState, EditorSelection } from '@codemirror/state';
import { syntaxTree } from '@codemirror/language';
import { autocompletion } from '@codemirror/autocomplete';
import {undo, indentMore, indentLess, toggleComment} from '@codemirror/commands';
import { errorMarkerEffect } from './extensions/errorMarker';
@ -33,7 +34,145 @@ export default class CustomEditorView extends EditorView {
}
return this.state.doc.toString();
}
/* Function to extract query based on position passed */
getQueryAt(currPos) {
try {
if(typeof currPos == 'undefined') {
currPos = this.state.selection.main.head;
}
const tree = syntaxTree(this.state);
let origLine = this.state.doc.lineAt(currPos);
let startPos = currPos;
// Move the startPos a known node type or a space.
// We don't want to be in an unknown teritory
for(;startPos<origLine.to; startPos++) {
let node = tree.resolve(startPos);
if(node.type.name != 'Script') {
break;
}
const currChar = this.state.sliceDoc(startPos, startPos+1);
if(currChar == ' ' || currChar == '\t') {
break;
}
}
let maxEndPos = this.state.doc.length;
let statementStartPos = -1;
let validTextFound = false;
// we'll go in reverse direction to get the start position.
while(startPos >= 0) {
const currLine = this.state.doc.lineAt(startPos);
// If empty line then start with prev line
// If empty line in between then that's it
if(currLine.text.trim() == '') {
if(origLine.number != currLine.number) {
startPos = currLine.to + 1;
break;
}
startPos = currLine.from - 1;
continue;
}
// Script type doesn't give any info, better skip it.
const currChar = this.state.sliceDoc(startPos, startPos+1);
let node = tree.resolve(startPos);
if(node.type.name == 'Script' || (currChar == '\n')) {
startPos -= 1;
continue;
}
// Skip the comments
if(node.type.name == 'LineComment' || node.type.name == 'BlockComment') {
startPos = node.from - 1;
// comments are valid text
validTextFound = true;
continue;
}
// sometimes, node type is child of statement.
while(node.type.name != 'Statement' && node.parent) {
node = node.parent;
}
// We already had found valid text
if(validTextFound) {
// continue till it reaches start so we can check for empty lines, etc.
if(statementStartPos > 0 && statementStartPos < startPos) {
startPos -= 1;
continue;
}
// don't go beyond this
startPos = node.to;
break;
}
// statement found for the first time
if(node.type.name == 'Statement') {
statementStartPos = node.from;
maxEndPos = node.to;
// if the statement is on the same line, jump to stmt start
if(node.from >= currLine.from) {
startPos = node.from;
}
}
validTextFound = true;
startPos -= 1;
}
// move forward from start position
let endPos = startPos+1;
maxEndPos = maxEndPos == -1 ? this.state.doc.length : maxEndPos;
while(endPos < maxEndPos) {
const currLine = this.state.doc.lineAt(endPos);
// If empty line in between then that's it
if(currLine.text.trim() == '') {
break;
}
let node = tree.resolve(endPos);
// Skip the comments
if(node.type.name == 'LineComment' || node.type.name == 'BlockComment') {
endPos = node.to + 1;
continue;
}
// Skip any other types
if(node.type.name != 'Statement') {
endPos += 1;
continue;
}
// can't go beyond a statement
if(node.type.name == 'Statement') {
maxEndPos = node.to;
}
if(currLine.to < maxEndPos) {
endPos = currLine.to + 1;
} else {
endPos +=1;
}
}
// make sure start and end are valid values;
if(startPos < 0) startPos = 0;
if(endPos > this.state.doc.length) endPos = this.state.doc.length;
return this.state.sliceDoc(startPos, endPos).trim();
} catch (error) {
console.error(error);
return this.getValue();
}
}
setValue(newValue, markClean=false) {
newValue = newValue || '';
if(markClean) {
@ -46,7 +185,7 @@ export default class CustomEditorView extends EditorView {
changes: { from: 0, to: this.getValue().length, insert: newValue }
});
}
getSelection() {
return this.state.sliceDoc(this.state.selection.main.from, this.state.selection.main.to) ?? '';
}
@ -63,7 +202,7 @@ export default class CustomEditorView extends EditorView {
let line = this.state.doc.lineAt(offset);
return {line: line.number, ch: offset - line.from};
}
setCursor(lineNo, ch) {
// line is 1-based;
// ch is 0-based;
@ -79,15 +218,15 @@ export default class CustomEditorView extends EditorView {
}
this.dispatch({ selection: { anchor: pos, head: pos } });
}
getCurrentLineNo() {
return this.state.doc.lineAt(this.state.selection.main.head).number;
}
lineCount() {
return this.state.doc.lines;
}
getLine(lineNo) {
// line is 1-based;
return this.state.doc.line(lineNo).text;
@ -141,7 +280,7 @@ export default class CustomEditorView extends EditorView {
break;
}
}
registerAutocomplete(completionFunc) {
this.dispatch({
effects: StateEffect.appendConfig.of(
@ -160,13 +299,13 @@ export default class CustomEditorView extends EditorView {
))
});
}
setErrorMark(fromCursor, toCursor) {
const from = this.state.doc.line(fromCursor.line).from + fromCursor.pos;
const to = this.state.doc.line(toCursor.line).from + toCursor.pos;
this.dispatch({ effects: errorMarkerEffect.of({ from, to }) });
}
removeErrorMark() {
this.dispatch({ effects: errorMarkerEffect.of({ clear: true }) });
}
@ -175,4 +314,3 @@ export default class CustomEditorView extends EditorView {
this.dispatch({ effects: activeLineEffect.of({ from: line, to: line }) });
}
}