initializeCodeMirror()

in src/executable-code/executable-fragment.js [639:874]


  initializeCodeMirror(options = {}) {
    const textarea = this.nodes[0].getElementsByTagName('textarea')[0];
    const readOnly = options.highlightOnly;
    const codemirrorOptions = {
      readOnly: readOnly,
      lineNumbers: false,
      mode: options.mode,
      theme: options.theme,
      matchBrackets: options.matchBrackets,
      scrollbarStyle: options.scrollbarStyle || 'overlay',
      continueComments: true,
      autoCloseBrackets: true,
      indentUnit: options.indent,
      viewportMargin: Infinity,
      foldGutter: true,
      gutters: [SELECTORS.ERROR_AND_WARNING_GUTTER, SELECTORS.FOLD_GUTTER],
    };

    // Workaround to allow copy code in read-only mode
    // Taken from https://github.com/codemirror/CodeMirror/issues/2568#issuecomment-308137063
    if (readOnly) {
      codemirrorOptions.cursorBlinkRate = -1;
    }

    this.codemirror = CodeMirror.fromTextArea(textarea, codemirrorOptions);

    // don't need to create additional editor options in readonly mode.
    if (readOnly) return;

    /**
     * Show highlight for extraKey Ctrl+Alt+H/Cmd+Option+H
     */
    let highlight = () => {
      const { compilerVersion, targetPlatform, hiddenDependencies } =
        this.state;
      this.removeStyles();
      WebDemoApi.getHighlight(
        this.getCode(),
        compilerVersion,
        targetPlatform,
        hiddenDependencies,
      ).then((data) => this.showDiagnostics(data));
    };

    const showImportSuggestions = (mirror) => {
      if (this.importsSuggestions.length === 0) return;
      let cur = mirror.getCursor();
      let token = mirror.getTokenAt(cur);
      let interval = {
        start: { line: cur.line, ch: token.start },
        end: { line: cur.line, ch: token.end },
      };
      let results = this.importsSuggestions
        .filter((it) => equal(it.interval, interval))
        .map((it) => it.imports)
        .flat();
      let withImports = this.canAddImport;
      if (results.length !== 0) {
        let options = {
          hint: function () {
            return {
              from: mirror.getDoc().getCursor(),
              to: mirror.getDoc().getCursor(),
              list: results.map((result) => {
                if (!withImports) {
                  result[IMPORT_NAME] = null;
                }
                return new CompletionView(result);
              }),
            };
          },
        };
        mirror.showHint(options);
      }
    };

    const hint = (mirror, callback) => {
      let cur = mirror.getCursor();
      let token = mirror.getTokenAt(cur);
      let code = this.state.folded
        ? this.prefix + mirror.getValue() + this.suffix
        : mirror.getValue();
      let currentCursor = this.state.folded
        ? { line: cur.line + this.prefix.split('\n').length - 1, ch: cur.ch }
        : cur;
      WebDemoApi.getAutoCompletion(
        code,
        currentCursor,
        this.state.compilerVersion,
        this.state.targetPlatform,
        this.state.hiddenDependencies,
        processingCompletionsList,
      );
      let withImports = this.canAddImport;

      function processingCompletionsList(results) {
        const anchorCharPosition = mirror.findWordAt({
          line: cur.line,
          ch: cur.ch,
        }).anchor.ch;
        const headCharPosition = mirror.findWordAt({
          line: cur.line,
          ch: cur.ch,
        }).head.ch;
        const currentSymbol = mirror.getRange(
          { line: cur.line, ch: anchorCharPosition },
          {
            line: cur.line,
            ch: headCharPosition,
          },
        );
        if (results.length === 0 && /^[a-zA-Z]+$/.test(currentSymbol)) {
          CodeMirror.showHint(mirror, CodeMirror.hint.auto, {
            completeSingle: false,
          });
        } else {
          callback({
            list: results.map((result) => {
              if (!withImports) result[IMPORT_NAME] = null;
              return new CompletionView(result);
            }),
            from: { line: cur.line, ch: token.start },
            to: { line: cur.line, ch: token.end },
          });
        }
      }
    };

    CodeMirror.registerHelper('hint', 'kotlin', hint);

    CodeMirror.hint.kotlin.async = true;

    CodeMirror.commands.autocomplete = (cm) => {
      cm.showHint(cm);
    };

    /**
     * Register own helper for autocomplete.
     * Getting completions from api.kotlinlang.org.
     * CodeMirror.hint.default => getting list from codemirror kotlin keywords.
     *
     * {@see WebDemoApi}      - getting data from WebDemo
     * {@see CompletionView} - implementation completion view
     */
    this.codemirror.setOption('hintOptions', { hint });

    if (window.navigator.appVersion.indexOf(MAC) !== -1) {
      this.codemirror.setOption('extraKeys', {
        'Cmd-Alt-L': 'indentAuto',
        'Shift-Tab': 'indentLess',
        'Ctrl-/': 'toggleComment',
        'Cmd-[': false,
        'Cmd-]': false,
        'Ctrl-Space': 'autocomplete',
        'Cmd-Alt-H': highlight,
        'Cmd-Alt-Enter': debounce(showImportSuggestions, DEBOUNCE_TIME),
      });
    } else {
      this.codemirror.setOption('extraKeys', {
        'Ctrl-Alt-L': 'indentAuto',
        'Shift-Tab': 'indentLess',
        'Ctrl-/': 'toggleComment',
        'Ctrl-[': false,
        'Ctrl-]': false,
        'Ctrl-Space': 'autocomplete',
        'Ctrl-Alt-H': highlight,
        'Ctrl-Alt-Enter': debounce(showImportSuggestions, DEBOUNCE_TIME),
      });
    }

    /**
     * When editor's changed:
     * 1) Remove all styles
     * 2) if onFlyHighLight flag => getting highlight
     */
    this.codemirror.on(
      'change',
      debounce((cm) => {
        const {
          onChange,
          onFlyHighLight,
          compilerVersion,
          targetPlatform,
          hiddenDependencies,
        } = this.state;
        if (onChange) onChange(cm.getValue());
        this.removeStyles();
        if (onFlyHighLight) {
          WebDemoApi.getHighlight(
            this.getCode(),
            compilerVersion,
            targetPlatform,
            hiddenDependencies,
          ).then((data) => this.showDiagnostics(data));
        }
      }, DEBOUNCE_TIME),
    );

    /**
     * If autoComplete => Getting completion on every key press on the editor.
     */
    this.codemirror.on(
      'keypress',
      debounce((cm, event) => {
        if (event.keyCode !== KEY_CODES.R && !event.ctrlKey) {
          if (this.state.autoComplete && !cm.state.completionActive) {
            CodeMirror.showHint(cm, CodeMirror.hint.kotlin, {
              completeSingle: false,
            });
          }
        }
      }, DEBOUNCE_TIME),
    );

    /**
     * Select marker's placeholder on mouse click
     */
    this.codemirror.on('mousedown', (codemirror, event) => {
      let position = codemirror.coordsChar({
        left: event.pageX,
        top: event.pageY,
      });
      if (position.line !== 0 || position.ch !== 0) {
        let markers = codemirror.findMarksAt(position);
        let todoMarker = markers.find(
          (marker) => marker.className === SELECTORS.MARK_PLACEHOLDER,
        );
        if (todoMarker != null) {
          let markerPosition = todoMarker.find();
          codemirror.setSelection(markerPosition.from, markerPosition.to);
          codemirror.focus();
          event.preventDefault();
        }
      }
    });
  }