????
Current Path : C:/opt/pgsql/pgAdmin 4/web/pgadmin/static/js/components/ReactCodeMirror/ |
Current File : C:/opt/pgsql/pgAdmin 4/web/pgadmin/static/js/components/ReactCodeMirror/CustomEditorView.js |
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'; import { activeLineEffect, activeLineField } from './extensions/activeLineMarker'; import { clearBreakpoints, hasBreakpoint, toggleBreakpoint } from './extensions/breakpointGutter'; function getAutocompLoading({ bottom, left }, dom) { const cmRect = dom.getBoundingClientRect(); const div = document.createElement('div'); div.classList.add('cm-tooltip', 'pg-autocomp-loader'); div.innerText = 'Loading...'; div.style.position = 'absolute'; div.style.top = (bottom - cmRect.top) + 'px'; div.style.left = (left - cmRect.left) + 'px'; dom?.appendChild(div); return div; } export default class CustomEditorView extends EditorView { constructor(...args) { super(...args); this._cleanDoc = this.state.doc; } getValue(tillCursor=false) { if(tillCursor) { return this.state.sliceDoc(0, this.state.selection.main.head); } 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) { // create a new doc with new value to make it clean this._cleanDoc = EditorState.create({ doc: newValue }).doc; } this.dispatch({ changes: { from: 0, to: this.getValue().length, insert: newValue } }); } getSelection() { return this.state.sliceDoc(this.state.selection.main.from, this.state.selection.main.to) ?? ''; } replaceSelection(newValue) { this.dispatch(this.state.changeByRange(range => ({ changes: { from: range.from, to: range.to, insert: newValue }, range: EditorSelection.range(range.from, range.to) }))); } getCursor() { let offset = this.state.selection.main.head; 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; let pos = 0; if(lineNo > this.state.doc.lines) { pos = this.state.doc.length; } else { const line = this.state.doc.line(lineNo); pos = line.from + ch; if(ch == -1 || pos > line.to) { pos = line.to; } } this.dispatch({ selection: { anchor: pos, head: pos }, scrollIntoView: true}); } 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; } getActiveLine() { const activeLineChunk = this.state.field(activeLineField).chunkPos; if(activeLineChunk.length > 0) { return this.state.doc.lineAt(activeLineChunk[0]).number; } return undefined; } hasBreakpoint(lineNo) { const line = this.state.doc.line(lineNo); return hasBreakpoint(this, line.from); } toggleBreakpoint(lineNo, silent, val) { const line = this.state.doc.line(lineNo); toggleBreakpoint(this, line.from, silent, val); } clearBreakpoints() { clearBreakpoints(this); } markClean() { this._cleanDoc = this.state.doc; } isDirty() { return !this._cleanDoc.eq(this.state.doc); } fireDOMEvent(event) { this.contentDOM.dispatchEvent(event); } execCommand(cmd) { switch (cmd) { case 'undo': undo(this); break; case 'indentMore': indentMore(this); break; case 'indentLess': indentLess(this); break; case 'toggleComment': toggleComment(this); break; default: break; } } registerAutocomplete(completionFunc) { this.dispatch({ effects: StateEffect.appendConfig.of( autocompletion({ override: [(context) => { this.loadingDiv?.remove(); this.loadingDiv = getAutocompLoading(this.coordsAtPos(context.pos), this.dom); context.addEventListener('abort', () => { this.loadingDiv?.remove(); }); return Promise.resolve(completionFunc(context, () => { this.loadingDiv?.remove(); })); }] } )) }); } 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 }) }); } setActiveLine(line) { let scrollEffect = line >= 0 ? [EditorView.scrollIntoView(this.state.doc.line(line).from, {y: 'center'})] : []; this.dispatch({ effects: [activeLineEffect.of({ from: line, to: line })].concat(scrollEffect) }); } }