????

Your IP : 216.73.216.32


Current Path : C:/opt/pgsql/pgAdmin 4/web/pgadmin/static/js/components/ReactCodeMirror/
Upload File :
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) });
  }
}