exited normally

--back of the flyer--

MyHL historyコマンドを追加

MyHLは、haribote HL-8cを改造したバージョンです。

historyコマンドを追加

historyコマンドを実装しました。

haribote HL-4aで実装したコマンド履歴は、上下矢印キーで履歴をたどる機能のみをサポートしていました。

上下矢印キーで履歴をたどれるのは便利ですが、古いものにたどりつくには何度もキーを押さないといけません。

MyHLの開発を進める中で、もっと速く履歴からコードを呼び出したいと感じることが増えたため実装しました。

historyコマンド

  • historyコマンド: 以前に実行したコマンドやコードを古い順に、引数に指定した行数だけ表示する

結果の左側に表示されている数値は、古いものから順に割り当てたid番号です。

確認したid番号や相対位置を使って履歴からコマンドやコードを検索し実行できます。

$ haribote
[1]> history 10
 9  print 1+2*3
10  print (1+2)*3
11  a = 0; print ++a; print a
12  a = 0; print a++; print a
13  int a[30] = { 1, 1 }
14  for (i = 2; i < 30; i++) { a[i] = a[i - 2] + a[i - 1]; }
15  for (i = 0; i < 10; i++) { print a[i]; }
16  for (i = 0; i < 20; i++) { print a[i]; }
17  exit
18  history 10

!コマンドを使って履歴からコードを実行する

  • ! (Bang) コマンド: id番号または相対位置を表す負数を指定して、その位置にあるコマンドやコードを実行する

!に続けて、数値または感嘆符を入力することで、実行するコマンドやコードを指定します。

!n

!nは、id番号(n番)の位置にあるコマンドやコードを実行します。

[2]> !13
int a[30] = { 1, 1 }
[3]> !14
for (i = 2; i < 30; i++) { a[i] = a[i - 2] + a[i - 1]; }
[4]> !15
for (i = 0; i < 10; i++) { print a[i]; }
1
1
2
3
5
8
13
21
34
55

!-n

!-nは、直近のものから数えてnつ前に実行したコマンドやコードを実行します。

[5]> prints "Riff Raff"
Riff Raff
[6]> prints "Jailbreak"
Jailbreak
[7]> prints "Beating Around The Bush"
Beating Around The Bush
[8]> history 5
21  for (i = 0; i < 10; i++) { print a[i]; }
22  prints "Riff Raff"
23  prints "Jailbreak"
24  prints "Beating Around The Bush"
25  history 5
[9]> !-3
prints "Jailbreak"
Jailbreak

!!

!!は、直前に実行したコマンドやコードを再実行するための記法です。

結果は!-1と同じになります。

[10]> !!
prints "Jailbreak"
Jailbreak

:eサフィックス

:eサフィックス!n!-n!!を修飾すると、履歴から検索したコマンドやコードが、入力が確定していない状態でコマンドラインにセットされます。

[11]> !9
print 1+2*3
7
[12]> !!:e
[13]> print 1+2*3

コマンドラインを編集して入力を確定すると、コマンドやコードが実行されます。

[13]> print (1+2)*3
9

!n!-n!!:eは直接履歴に追加されず、実行したコマンドやコードが追加されます。

特に以前のコマンドやコードを編集して実行したいときに、実装済みのコマンドライン編集とあわせて使うと便利な機能です。

実装メモ

自分が使いやすいようにbashの履歴展開に似せて作っていますが、hariboteの「!」は単なるREPLのコマンドです。

else if (text[0] == '!' && (text[1] == '!' || text[1] == '-' || isNumber(text[1]))) {
  int reedit = endsWithSuffix('e', text), i;
  char *end;
  if (reedit)
    text[inputLen - 2] = 0;
  if (text[1] == '!')
    i = -1;
  else if (i = strtol(&text[1], &end, 10), *end != 0) {
    printf("!: %s: numeric argument required\n", &text[1]);
    continue;
  }
  Command *cmd = text[1] == '!' || text[1] == '-'
    ? getPrevCmd(i - 1, text) // -1は"!"コマンド
    : getPrevCmdById(i, text);
  if (reedit) {
    ignorePrevHistory();
    eraseLine();
    setCursorX(cmd->len);
    printf("[%d]> %s", ++nLines, cmd->str);
    next = 0;
  }
  else {
    updatePrevHistory(cmd->str, cmd->len);
    resetCmdHistoryPos();
    setCursorX(0);
    printf("%s\n", cmd->str);
    reexecute = 1;
  }
}
else if (strncmp(text, "history ", 8) == 0) {
  int num = strtol(&text[8], NULL, 10);
  listPrevCmds(num, text);
}

historyコマンドや!コマンドで履歴を検索する処理は、上下矢印キーでコマンド履歴をたどる処理で使っているshowHistory()のラッパーを使って書いています。

int rewindHistory(int num)
{
  assert(num < 0);

  num = -num;
  int id = cmdHist.count - num + 1;
  if (id <= 0)
    return 0;

  int end = num;
  for (int i = 0; i < end; ++i)
    showHistory(Prev, NULL);
  return id;
}

showHistory()は、第二引数にNULLを渡すと、早期リターンしてコマンドやコードを表示しないように変更しました。履歴をたどる処理は haribote HL-4aと同じです。

historyコマンドの結果の左側に表示されている数値は、cmdHist.headに登録されているコマンドやコードの位置を1として、順に割り当てたid番号です。

id番号は内部的に保持せず、historyコマンドが実行される都度、int id = cmdHist.count - num + 1;で計算しています。

※ 変数numは、直近のものから数えて履歴をいくつたどるかを表す数値です。

haribote HL-4aに係る変更

HISTORY_SIZEを変更

-#define HISTORY_SIZE 100
+#define HISTORY_SIZE 500

不要なcontinue文を削除

@@ -1664,14 +1664,12 @@ int main(int argc, const char **argv)
       printf("[%d]> ", nLines);
       showHistory(Prev, text);
       next = 0;
-      continue;
     }
     else if (strcmp(text, "nexthist") == 0) {
       eraseLine();
       printf("[%d]> ", nLines);
       showHistory(Next, text);
       next = 0;
-      continue;
     }
 #endif
     else if (strncmp(text, "run ", 4) == 0) {
@@ -1684,7 +1682,6 @@ int main(int argc, const char **argv)
       eraseAll();
       printf("[%d]> ", nLines);
       next = 0;
-      continue;
     }
 #endif
     else {

prevhistとnexthistをリネーム

@@ -1537,8 +1676,8 @@ char *readLine(char *str, int size, FILE *stream)
       switch (ch) {
       case 67: if (cursorX < i) { write(0, "\e[C", 3); ++cursorX; } continue; // RightArrow
       case 68: if (cursorX > 0) { write(0, "\e[D", 3); --cursorX; } continue; // LeftArrow
-      case 65: strncpy(str, "prevhist", 9); break; // UpArrow
-      case 66: strncpy(str, "nexthist", 9); break; // DownArrow
+      case 65: strncpy(str, "__PREV_HIST", 12); break; // UpArrow
+      case 66: strncpy(str, "__NEXT_HIST", 12); break; // DownArrow
       }
       setCanonicalMode();
       return str;
@@ -1659,13 +1659,13 @@ int main(int argc, const char **argv)
     if (strcmp(text, "exit") == 0)
       goto exit;
 #if defined(__APPLE__) || defined(__linux__)
-    else if (strcmp(text, "prevhist") == 0) {
+    else if (strcmp(text, "__PREV_HIST") == 0) {
       eraseLine();
       printf("[%d]> ", nLines);
       showHistory(Prev, text);
       next = 0;
     }
-    else if (strcmp(text, "nexthist") == 0) {
+    else if (strcmp(text, "__NEXT_HIST") == 0) {
       eraseLine();
       printf("[%d]> ", nLines);
       showHistory(Next, text);