電子冒険:エピソード17:ターミナル入力


私たちのターミナルアプリは良くなっている.次のステップは、実行するコマンドと対話するための方法を追加することです.これらは三つの主要な方法です.
  • いくつかのテキストを入力します(デフォルトでは文字ではなく)
  • 入力が完了したコマンドを指示します(従来の端末ではControl - D )
  • コマンドを停止させる(伝統的な端末でのControl - C )
  • プリロードでruncommand。js


    我々は再びそれを変えている.アプリから来るイベントの多くがありますinput , endInput , kill ), そして、我々はアプリから送信するイベントの多くonout , onerr , ondone ):
    let runCommand = ({command, onout, onerr, ondone}) => {
      const proc = child_process.spawn(
        command,
        [],
        {
          shell: true,
          stdio: ["pipe", "pipe", "pipe"],
        },
      )
      proc.stdout.on("data", (data) => onout(data.toString()))
      proc.stderr.on("data", (data) => onerr(data.toString()))
      proc.on("close", (code) => ondone(code))
      return {
        kill: () => proc.kill(),
        input: (data) => proc.stdin.write(data),
        endInput: () => proc.stdin.end(),
      }
    }
    
    変化したstdin からignore to pipe それが今アクティブなので、今我々は我々のプロセスに話をするために使用するアプリケーションの3つのメソッドを持つオブジェクトを返します.

    アプリのすべてのロジックを移動します。スベルト


    最初のコマンドを扱うすべてのロジックはApp.svelte and HistoryEntry.svelte はディスプレイのみのクラスです.
    これは反転する必要がありますApp.svelte , では、名前を変えましょうHistoryEntry.svelte to Command.svelte 代わりにすべてのロジックを移動します.
    <script>
      import Command from "./Command.svelte"
      import CommandInput from "./CommandInput.svelte"
    
      let history = []
    
      async function onsubmit(command) {
        let entry = {command}
        history.push(entry)
        history = history
      }
    </script>
    
    <h1>Svelte Terminal App</h1>
    
    <div id="terminal">
      <div id="history">
        {#each history as entry}
          <Command command={entry.command} />
        {/each}
      </div>
    
      <CommandInput {onsubmit} />
    </div>
    
    <style>
    :global(body) {
      background-color: #444;
      color: #fff;
      font-family: monospace;
    }
    </style>
    

    コマンド入力の入力ボックススタイル。スベルト


    それは小さいものです、しかし、現在我々が同時に複数の入力ボックスを持っているので、私はそれをより明瞭にするために少し色を変えました.
      input {
        background-color: #666;
      }
    

    コマンド.スベルトテンプレート


    やりたいことがたくさんあります.
  • テキスト入力用の入力フィールドを追加する
  • 入力の終わりのためにいくつかのボタンを加えて、コマンドを殺すために
  • それは冗長な今のようにスピンのアイコンを削除-コマンドを実行すると、入力フィールドを持って、コマンドを実行しません
  • 相互作用の代わりにstdoutを最初にし、stderrではなく、stdin、stdout、stderrを絡んでいます.
  • <div class='history-entry'>
      <div class='input-line'>
        <span class='prompt'>$</span>
        <span class='command'>{command}</span>
      </div>
      {#each interactions as interaction}
        <div class={interaction.type}>{interaction.data}</div>
      {/each}
      {#if running}
        <form on:submit|preventDefault={submit}>
          <input type="text" bind:value={input} />
          <button type="button" on:click={endInput}>End Input</button>
          <button type="button" on:click={kill}>Kill</button>
        </form>
      {/if}
      {#if error}
        <Icon data={exclamationTriangle} />
      {/if}
    </div>
    

    コマンド.スベートスクリプト


    既存のロジックApp.svelte だけでなく、新しいロジックの束ここに行く.
    コードは十分クリアする必要があります.interactions はオブジェクトの配列です.type and data プロパティ.type どちらかstdin , stdout , or stderr . data が送受信された実際のテキストです.
    <script>
      import Icon from "svelte-awesome"
      import { exclamationTriangle } from "svelte-awesome/icons"
    
      export let command
    
      let running = true
      let interactions = []
      let error = false
      let input = ""
    
      function onout(data) {
        interactions.push({data, type: "stdout"})
        interactions = interactions
      }
      function onerr(data) {
        interactions.push({data, type: "stderr"})
        interactions = interactions
      }
      function ondone(code) {
        running = false
        error = (code !== 0)
      }
      function endInput() {
        proc.endInput()
      }
      function kill() {
        proc.kill()
      }
      function submit() {
        let data = input+"\n"
        interactions.push({data, type: "stdin"})
        interactions = interactions
        proc.input(data)
        input = ""
      }
      let proc = window.api.runCommand({command,onout,onerr,ondone})
    </script>
    

    コマンド.スベルトスタイリング


    スタイリングは、入力の背景色を少し変更しただけで、端末の残りの部分から入力を区別することができました.
    <style>
      .history-entry {
        padding-bottom: 0.5rem;
      }
    
      .stdin {
        color: #ffa;
        white-space: pre;
      }
    
      .stdout {
        color: #afa;
        white-space: pre;
      }
    
      .stderr {
        color: #faa;
        white-space: pre;
      }
    
      .input-line {
        display: flex;
        gap: 0.5rem;
      }
    
      .command {
        color: #ffa;
        flex: 1;
      }
    
      form {
        flex: 1;
        display: flex;
      }
    
      input {
        flex: 1;
        font-family: inherit;
        background-color: #666;
        color: inherit;
        border: none;
      }
    </style>
    

    結果


    結果は以下の通りです.

    端末には、いくつかの制限があります.
  • コマンドを実行すると、新しいunfocus入力ボックスが作成されますので、手動でフォーカスする必要がありますコマンドが終了すると、手動で新しいコマンドの入力にフォーカスする必要があります
  • Control - DとControl - Cのようなキーボードショートカット
  • cd コマンドが動作しません
  • バイナリデータ、あまりにも多くのデータ、または行ベースのテキストではないデータを生成するコマンドは、非常に不十分に動作します
  • しかし、それはまだかなりうまくいっています.
    次のエピソードについては、我々の端末アプリから休憩を取るし、何か別のコードを試してください.
    いつものように.all the code for the episode is here .