ReactJS + Pyodide Terminal not processing python

57 views Asked by At

I have a component to emulate a python terminal, but whenever I type something in, it doesn't process it :(. The dependencies are xtermjs, pyodide, react, and xterm-addon-fit. The code is below (I know it is not small, but I can't scale it down):

import React, { useEffect, useRef } from 'react';
import { FitAddon } from 'xterm-addon-fit';
import { Terminal as XTerm } from 'xterm';
import { version as pyodide_version, loadPyodide } from 'pyodide';
import 'xterm/css/xterm.css'
const Terminal = () => {
  const termRef = useRef(null);
  const fitAddon = useRef(new FitAddon());
  const termHistory = useRef(['']);
  const currentHistoryLine = useRef(0);
  const currentLine = useRef('');
  const curCode = useRef('');
  const multiLine = useRef(false);
  const ready = useRef(false);
  const typeListening = useRef(true);
  const pyodide = (async function(){await loadPyodide({indexURL : `https://cdn.jsdelivr.net/pyodide/v${pyodide_version}/full/`})})().then(function(python){window.pyodide=python;return window.pyodide})
  const pressed = {}
  // Event listener for keydown event
  document.addEventListener('keydown',e=>pressed[e.key]=true);

  // Event listener for keyup event
  document.addEventListener('keyup',e=>pressed[e.key]=false);

  useEffect(() => {
    const term = new XTerm({
      allowProposedApi: true
    });
    term.loadAddon(fitAddon.current);
    term.open(termRef.current);
    fitAddon.current.fit();
    window.onresize=()=>fitAddon.current.fit();
    term.onKey(async (event) => {
      if (!typeListening.current) return;
      console.log(event)
      if (event.key === '\x7f') {
        if (term.buffer.active.cursorX > 4) {
          if (currentHistoryLine.current !== 0) {
            termHistory.current[0] = currentLine.current;
          }
          currentLine.current = currentLine.current.slice(0, -1);
          termHistory.current[0] = currentLine.current;
          term.write('\b \b');
        }
        return 0;
      }
      if (event.key === '\r') {
        if (termHistory.current[0] !== currentLine.current) {
          termHistory.current[0] = currentLine.current;
        }
        curCode.current += currentLine.current;
        currentLine.current = '';
        currentHistoryLine.current = 0;
        termHistory.current.unshift('');
        if (!isDown('Shift') || willBreakLine()) {
          multiLine.current = true;
          curCode.current += '\n';
          term.write('\n\t... ');
        } else {
          if (multiLine.current) {
            if (curCode.current[curCode.current.length - 1] !== '\n') {
              return 0;
            }
            multiLine.current = false;
          }
          term.write('\n\r');
          if (curCode.current.length === 0) {
            term.write('>>> ');
            return 0;
          }
          typeListening.current = false;
          await pyodide.runPythonAsync(curCode.current)
            .then((result) => {
              if (typeof result === 'string') {
                write(`'${result.replace(/\n/g, '\\n')}'`);
              } else {
                write(result);
              }
            })
            .catch((error) => {
              write(error);
            })
            .then(() => {
              typeListening.current = true;
              curCode.current = '';
              term.write('\r\n>>> ');
            });
        }
        return 0;
      }
      if (event.key === '\x1b[A') {
        if (currentHistoryLine.current > 0) {
          currentHistoryLine.current--;
          term.write('\x1b[5G\x1b[K');
          term.write(termHistory.current[currentHistoryLine.current]);
          currentLine.current = termHistory.current[currentHistoryLine.current];
        }
        return 0;
      }
      if (event.key === '\t') {
        term.write('  ');
        currentLine.current += '  ';
        termHistory.current[0] = currentLine.current;
        return 0;
      }
      term.write(event.key);
      currentLine.current += event.key;
      termHistory.current[0] = currentLine.current;
    });

    const onResize = () => {
      fitAddon.current.fit();
    };

    term.onResize(onResize);

    return () => {
      term.dispose();
    };
  });

  const write = (...args) => {
    termRef.current.terminal.write(format(...args).replace(/\n/g, '\r\n'));
  };

  const writeln = (...args) => {
    termRef.current.terminal.writeln(format(...args).replace(/\n/g, '\r\n'));
  };

  const isDown = (key) => {
    return !!pressed[key];
  };

  const willBreakLine = () => {
    const lines = [];
    let inQuotes = false;
    let inComment = false;
    let inLineComment = false;
    for (let i = 0; i < curCode.current.length; i++) {
      const char = curCode.current[i];
      if (char === '"' && !inComment && !inLineComment) {
        inQuotes = !inQuotes;
      }
      if (char === '/' && i < curCode.current.length - 1) {
        if (curCode.current[i + 1] === '/') {
          if (!inQuotes && !inComment) {
            inLineComment = true;
          }
        } else if (curCode.current[i + 1] === '*') {
          if (!inQuotes && !inComment && !inLineComment) {
            inComment = true;
          }
        }
      }
      if (char === '\n' && !inQuotes && !inComment && !inLineComment) {
        lines.push(i);
      }
      if (char === '*' && i < curCode.current.length - 1) {
        if (curCode.current[i + 1] === '/') {
          if (!inQuotes && !inLineComment) {
            inComment = false;
            i++;
          }
        }
      }
      if (char === '\r' && i < curCode.current.length - 1 && curCode.current[i + 1] === '\n') {
        i++;
      }
    }
    return lines.length === 0 || lines[lines.length - 1] !== curCode.current.length - 1;
  };

  const format = (str, ...args) => {
    let formatted = str?.toString() || '';
    if (args.length) {
      formatted = formatted.replace(/(%?)(%([jds]))/g, (match, escaped, ptn, specifier) => {
        const arg = args.shift();
        switch (specifier) {
          case 's':
            return escaped ? match : `${arg}`;
          case 'd':
            return escaped ? match : Number(arg);
          case 'j':
            return escaped ? match : JSON.stringify(arg);
          default:
            return match;
        }
      });
    }
    if (args.length) {
      formatted += ` ${args.join(' ')}`;
    }
    formatted = formatted.replace(/%{2,2}/g, '%');
    return `${formatted}`;
  };

  const termStyle = {
    width: '100%',
    height: '100%',
  };
  return (
    <div ref={termRef} style={termStyle}></div>
  );
};

I have tried changing it, using event.domEvent.key, and everything I could think of. I am also kind of a newbie, so it took me a while writing this, but it might just be a simple error.

0

There are 0 answers