未完成です。休むともうさっぱり。睡魔にも勝てず・・・

きっとまた、あさっての方向を向いている。

<!DOCTYPE html>
<html lang="ja">
<head>
  <title>Terminal Emulator</title>
  <meta charset="utf-8">
  <style>
body {
  margin : 0;
  width      : 100%;
}

hgroup {
  color      : white;
  background : black;
  padding    : 0 6px 2px 6px;
  margin     : 0;
  height     : 16px;
}

hgroup > h1 {
  font-size  : small;
  margin     : 0;
  float      : left;
  height     : 16px;
}

hgroup > h2 {
  font-size  : x-small;
  margin     : 0;
  text-align : right;
  padding-top: 2px;
}

#DISPLAY {
  clear      : both;
  width      : 100%;
  margin     : 0;

}

#DISPLAY > p,
#DISPLAY > pre {
  margin     : 0;
  width      : 100%;
}

#DISPLAY > p > input.command_line {
  border     : 0 none;
  background : transparent;
  width      : 100%;
  margin     : 0;
  font-size  : medium;
}

  </style>
</head>

<body>
  <hgroup>
    <h1><span id="TITLE">XXX</span> Terminal Emulator</h1>
    <h2><span id="SUB_TITLE">babu_babu_baboo<span></h2>
  </hgroup>
  <div id="DISPLAY">&nbsp;</div>

<script>




// 指定したノードに、データを変換して、タイプ別のノードを生成して追加する
function createHTMLConverter (target, type, converter) {
  if (1 > arguments.length)
    throw new Error ('ノードが指定されていない');
  
  var doc = target.ownerDocument;
  
  return function (data) {
    var node;
    
    data = ('function' === typeof converter) ? converter (data): data;
    
    switch (type) {
    case 1 : case 'pre' :
      node = doc.createElement ('pre');
      node.appendChild (doc.createTextNode (data.replace (/\n|\r|\n\r/g, '<br>')));
      break;
    
    case 2 : case 'innerHTML' :
      node = doc.createDocumentFragment ();
      node.innerHTML = data;
      break;
      
    case 0 : case 'textarea' :
    default :
      node = doc.createElement ('textarea');
      node.value = data;
    }
    if (node)
      target.appendChild (node);
  };
}


//____________________________________________________________


function Input_Wrapper (obj) {
  if (1 > arguments.length) throw new Error ('引数がない');
  
  return new function () {
    this.clear             = function () { return obj.clear (); };
    this.disabled          = function () { return obj.disabled (); };
    this.focus             = function () { obj.focus (); };
    this.standby           = function () { return obj.standby (); };
    this.setCurrentCommand = function (val) { return obj.setCurrentCommand (val); };
    this.getCurrentCommand = function () { return obj.getCurrentCommand (); };
    this.isTarget          = function (node) { return obj.isTarget (node); };
    this.init              = function () { this.standby (); };
  };
}

//____________________________________________________________

function Output_Wrapper (obj) {
  if (1 > arguments.length) throw new Error ('引数がない');
  
  return new function () {
    this.clear = function () { obj.clear (); };
    this.print = function (data) { obj.print (data); };
    this.init  = function () { obj.init (); };
  };
}
//____________________________________________________________



var HTMLView = new function () {
  var doc = document;
  var target = doc.getElementById ('DISPLAY');
  var INPUT = null;
  var prompt = '$>';
  
  // Input
  this.input = new function () {

    this.init      = function () { this.standby (); };
    this.clear     = function () { INPUT.value = ''; };
    this.disabled  = function () { if (INPUT) INPUT.disabled = true; };
    this.focus     = function () { if (INPUT) INPUT.focus (); };
    this.isTarget  = function (node) { return (INPUT && (node == INPUT)); };
    this.setCurrentCommand = function (val) { INPUT.value = prompt + (val || '') };
    this.getCurrentCommand = function () { return INPUT.value.substring (prompt.length); };

    this.standby   = function () {
      var p = doc.createElement ('p');
      INPUT = doc.createElement ('input');
      INPUT.type = 'text';
      INPUT.className = 'command_line';
      INPUT.value = prompt;
      p.appendChild (INPUT);
      target.appendChild (p);
      setTimeout (function (n) {
          var len = n.value.length;
          n.focus ();
          n.setSelectionRange (len, len); //error?
        }, 100, INPUT);
    };
  };

  
  // Output
  this.output = new function () {
    var printer = createHTMLConverter (target, 'pre');
  
    this.clear =
      (function () {
        while (target.hasChildNodes ())
          target.removeChild (target.firstChild);
       });
    
    
    this.print =
      (function (data) {
        printer (data);
      });
    

    this.init = this.clear;
  };
};


//____________________________________________________________



(function () {

  /*____________________________________________________________
   * ターミナルエミュレーター
   */
  function TerminalEmulator (processor, input, output) {
    if (2 > arguments.length)
      throw new Error ('引数が足りない');

    if ('undefined' === typeof output) // output が省略されたら、alert で代用
      output = alert_;
    
    var buf = []; // コマンドバッファー
    var idx = 0;  // バッファーのインデックス

    var emu = new function () {
      this.init = // 各オブジェクトに対し、初期化を実行する
        (function () {
          initCall (processor);
          initCall (input);
          initCall (output);
          input.standby ();
        });
      

      this.nextCommand = // コマンドラインバッファから次を選択
        (function () {
          moveIndex (+1);
          input.setCurrentCommand (buf[idx]);
        });


      this.previousCommand = // コマンドラインバッファから前を選択
        (function () {
          moveIndex (-1);
          input.setCurrentCommand (buf[idx]);
        });


      this.send = // processor に、コマンドを送信
        (function (cmd) {
           var result;
           cmd = ('undefined' !== typeof cmd) ? cmd: input.getCurrentCommand ();
           buf.push (cmd);
           idx = buf.length;
           input.disabled (); // input 側を無効化
           result = processor (cmd);
           output.print (result); // 結果を出力
           input.standby (); // input 側に入力可能にする
           return result;
        });


      this.isTarget = // その要素が、指令できるのか判断
        (function (node) {
          return input.isTarget (node);
        });
      
      this.commandLineFocus =
        (function () {
           input.focus ();
         });
    };
    
    emu.init ();
    return emu;

    //_______________

    function moveIndex (n) {
      idx += n;
      if (idx < 0)
        idx = 0;
      else if (buf.length <= idx)
        idx = buf.length - 1;
    }
  }

  function alert_ (data) { alert (data.join ('\n')); }
  function initCall (func) { if ('function' === typeof func.init) func.init (); } // xxx.init が関数であれば実行


  /*____________________________________________________________
   * キーでオブジェクトを制御
   */

  function createKeyController (object) {
    return function (event) {
      var e = event.target;
      
      switch (event.type) {
      case 'keyup' :
        if ((! e.disabled) && (object.isTarget (e))) {
          switch (event.keyCode) {
          case 13 : object.send (); break;
          case 38 : object.previousCommand (); break;
          case 40 : object.nextCommand (); break;
          }
        }
        break;

      case 'focus' :
        object.commandLineFocus ();
        break;
      }
    };
  }

  //____________________________________________________________
  
  this.TerminalEmulator = TerminalEmulator;
  this.createKeyController = createKeyController;

})();
//____________________________________________________________


var processor = function echo (command) { return 'echo:' + command; };
var inDevice = Input_Wrapper (HTMLView.input);
var outDevice = Output_Wrapper (HTMLView.output);
var emulator = TerminalEmulator (processor, inDevice, outDevice);
var handler = createKeyController (emulator);

document.addEventListener ('keyup', handler, false);
window.addEventListener ('focus', handler, false);


</script>