<commit_before>import { times } from "lodash";
import { connect, MqttClient } from "mqtt";
import { Terminal } from "xterm";

type TerminalLike = Pick<Terminal, "write" | "onKey">;
type KeysWeNeed = "publish" | "on" | "once" | "publish" | "subscribe";

export class TerminalSession {
  buffer = "";
  private client: Pick<MqttClient, KeysWeNeed>;

  constructor(url: string, public username: string,
    password: string,
    private terminal: TerminalLike) {
    this.client = connect(url, { username, password });
  }

  get rx() { return `bot/${this.username}/terminal_output`; }
  get tx() { return `bot/${this.username}/terminal_input`; }

  connect = () => {
    return new Promise((resolve) => {
      this.client.once("connect", () => {
        this.client.subscribe(this.rx);
        this.terminal.onKey(this.terminalKeyboardHandler);
        this.client.on("message", this.terminalMessageHandler);
      });
      resolve();
    });
  }

  erase = (n: number) => {
    times(Math.max(0, n), () => {
      this.buffer = this.buffer.slice(0, -1);
      this.terminal.write("\b \b");
    });
  }

  clearBuffer = () => {
    this.erase(this.buffer.length - 1);
    this.buffer = "";
  };

  terminalMessageHandler = (m: string, payload: Buffer) => {
    if (m.includes("terminal_output")) {
      this.terminal.write(payload);
    }
  }

  terminalKeyboardHandler = ({ key: key }: { key: string }) => {
    this.buffer += key;
    switch (key) {
      case "\r":
        if (!["\r", "\r\r"].includes(this.buffer)) {
          this.client.publish(this.tx, this.buffer);
        }
        this.clearBuffer();
        break;
      case "\b":
      case String.fromCharCode(127):
        this.erase(1);
        break;
      default:
        this.terminal.write(key);
    }
  }
}<commit_msg>Fix terminal buffer issue wrt backspace key.<commit_after>import { times } from "lodash";
import { connect, MqttClient } from "mqtt";
import { Terminal } from "xterm";

type TerminalLike = Pick<Terminal, "write" | "onKey">;
type KeysWeNeed = "publish" | "on" | "once" | "publish" | "subscribe";

export class TerminalSession {
  buffer = "";
  private client: Pick<MqttClient, KeysWeNeed>;

  constructor(url: string, public username: string,
    password: string,
    private terminal: TerminalLike) {
    this.client = connect(url, { username, password });
  }

  get rx() { return `bot/${this.username}/terminal_output`; }
  get tx() { return `bot/${this.username}/terminal_input`; }

  connect = () => {
    return new Promise((resolve) => {
      this.client.once("connect", () => {
        this.client.subscribe(this.rx);
        this.terminal.onKey(this.terminalKeyboardHandler);
        this.client.on("message", this.terminalMessageHandler);
      });
      resolve();
    });
  }

  erase = (n: number) => {
    times(Math.max(0, n), () => {
      this.terminal.write("\b \b");
      this.buffer = this.buffer.slice(0, -1);
    });
  }

  clearBuffer = () => {
    this.erase(this.buffer.length);
    this.buffer = "";
  };

  terminalMessageHandler = (m: string, payload: Buffer) => {
    if (m.includes("terminal_output")) {
      this.terminal.write(payload);
    }
  }

  terminalKeyboardHandler = ({ key: key }: { key: string }) => {
    switch (key) {
      case "\r":
        if (!["\r", "\r\r"].includes(this.buffer)) {
          this.client.publish(this.tx, this.buffer + key);
        }
        this.clearBuffer();
        break;
      case "\b":
      case String.fromCharCode(127):
        this.erase(1);
        break;
      default:
        this.buffer += key;
        this.terminal.write(key);
    }
  }
}
{'fraction_non_alphanumeric': 0.1752691139939277, 'fraction_numerical': 0.004140215291195142, 'mean_word_length': 2.694189602446483, 'pattern_counts': {'":': 4, '<': 7, '<?xml version=': 0, '>': 23, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4642990', 'n_tokens_mistral': 1196, 'n_tokens_neox': 1154, 'n_words': 232}