S-JIS[2022-09-20] 変更履歴

JLine3 History

JLine3のHistoryのメモ。


概要

Historyは、JLine3でユーザーが入力した文字列の履歴を扱うクラス(インターフェース)。

import org.jline.reader.History;

HistoryはLineReaderで保持される。
デフォルトではDefaultHistoryというクラスが使われる。
LineReaderのreadLineメソッドで読み込まれた文字列がHistoryに登録される。


デフォルトでは、カーソルキーの上で過去に入力した文字列が表示される。下を押すとその履歴より後の文字列が表示される。
Ctrl+Rで、文字列の先頭を入力してそれにマッチする履歴だけを表示できる。

これらのキーバインドはLineReaderImplで定義されている。
(デフォルトはemacsバインドになっていて、viバインドにも変更できる模様)

	bind(emacs, DOWN_LINE_OR_HISTORY,	ctrl('N'));
	bind(emacs, UP_LINE_OR_HISTORY,  	ctrl('P'));

	bind(emacs, HISTORY_INCREMENTAL_SEARCH_BACKWARD,	ctrl('R'));
	bind(emacs, HISTORY_INCREMENTAL_SEARCH_FORWARD, 	ctrl('S'));

Historyは、入力された履歴をファイルへ読み書き(保存)する機能を備えている。
履歴をファイルに保存する例


履歴を取得する例

履歴の取得に関係するHistoryのメソッドは何種類かある。

分類 メソッド 説明
全体 int size() 保持している履歴の個数
boolean isEmpty() 履歴が空かどうか
String toString() 全履歴を文字列にして返す。
取得 int first() 先頭の履歴のインデックス
int last() 最後の履歴のインデックス
String get(int index) 指定されたインデックスの履歴(文字列)を取得する。
ListIterator<Entry> iterator() 履歴のIterator
Iterator<Entry> reverseIterator() 履歴のIterator(逆順)
ナビゲーション
(たぶん内部的に使用するもの)
int index() 現在のインデックス
String current() 現在の位置の履歴(文字列)を取得する。
boolean moveTo(int index) 現在のインデックスを指定された位置にする。
boolean moveToFirst() 現在のインデックスを履歴の先頭にする。
boolean moveToLast() 現在のインデックスを履歴の最後にする。
void moveToEnd()  
boolean previous() 現在のインデックスをひとつ前に移動する。
boolean next() 現在のインデックスをひとつ後に移動する。
void resetIndex() 現在のインデックスを末尾に戻す。
    History history = lineReader.getHistory();
    System.out.println(history.get(history.last()));

HistoryはIterableを実装しているので、iteratorメソッドやfor文が使用できる。

    for (History.Entry entry : history) {
        System.out.println(entry.index());
        System.out.println(ZonedDateTime.ofInstant(entry.time(), ZoneId.of("Asia/Tokyo")));
        System.out.println(entry.line()); // 履歴の文字列
    }

履歴を追加する例

履歴はHistoryのaddメソッドで追加する。

    History history = lineReader.getHistory();
    history.add("abc");

内部的には、現在時刻も一緒に登録するようになっている。


履歴をファイルに保存する例

ユーザーが入力した文字列の履歴をファイルに読み書きするHistoryのメソッドは何種類かある。

メソッド 説明
void load() 履歴をファイルから読み込む。
read(null, false)相当。
void save() 履歴をファイルに保存する。
append(null, true)相当。
void write(Path file, boolean incremental) 履歴をファイルに保存する。
既存の履歴ファイルを削除してからappendを呼び出すのと同等。
void append(Path file, boolean incremental) 履歴をファイルに追加する。
fileがnullの場合、LineReader.HISTORY_FILEにセットされているパスを使用する。それもnullの場合は何もしない。
fileの親ディレクトリーが存在しない場合は作成される。
incrementalをtrueにすると、前回保存時より後に登録された履歴だけをファイルに保存する。
void read(Path file, boolean checkDuplicates) 履歴をファイルから読み込む。
ファイルのパスに関してはappendと同様。
checkDuplicatesをtrueにすると、同じ履歴は最初の1回しか登録しない。
(ファイルから読むときに重複を排除するだけで、ファイル内の重複を削除するわけではない)
void purge() 履歴ファイルを削除する。
ファイルのパスに関しては(fileがnullの場合の)appendと同様。
import java.nio.file.Path;
    var path = Path.of(System.getProperty("java.io.tmpdir"), "jline-example-history.txt");
    lineReader.setVariable(LineReader.HISTORY_FILE, path);
    lineReader.setOpt(Option.HISTORY_TIMESTAMPED);

//  History history = lineReader.getHistory();
//  history.attach(lineReader); // attachの中でloadされる(LineReaderImpl.readLine()内でattachされるので、明示的にattachする必要は無い)
//  history.load();

    for (;;) {
        String line = lineReader.readLine("jline> ");
        if (line.equals("exit")) {
            break;
        }
        if (line.equals("history")) {
            System.out.println("history=" + history);
        }

        terminal.writer().println(line);
    }

//  history.save();

LineReader.HISTORY_FILEで履歴ファイルのパスを設定しておけば、履歴は(LineReaderImpl.readLine()内で)自動的に読み込まれる (loadされる)。
また、デフォルトでは(Option.HISTORY_INCREMENTALがtrueなので)readLine()で1行入力する毎に履歴ファイルに保存される(saveされる)。


ちなみに、履歴ファイルは1行にひとつの履歴が入ったテキストファイル(UTF-8)。
(複数行の文字列でも、改行コードが「\r」や「\n」にエスケープされて必ず1行になる)

$TMP/jline-example-history.txtの例

1663663742270:aaa
1663663744867:bbb
1663663746476:abc

各行の形式は2通りある。デフォルトは「タイムスタンプ:文字列」という形式。
Option.HISTORY_TIMESTAMPED


Historyのオプション

LineReaderのオプションで履歴関連の設定が出来る。

History関連のオプションをLineReaderでセットする仕様はいただけないと思うが^^;
オプションをセットする機構がLineReaderにあるので、それを利用している形かなぁ…。

import org.jline.reader.LineReader;
import org.jline.reader.LineReader.Option;
分類 オプション 説明
履歴登録 LineReader.DISABLE_HISTORY trueにすると、入力された文字列を履歴に登録しない。
Option.HISTORY_IGNORE_SPACE trueの場合、入力された文字列の先頭が空白文字の場合は履歴に登録しない。
デフォルトはtrue。
Option.HISTORY_REDUCE_BLANKS trueの場合、入力された文字列をtrimしてから履歴に登録する。
デフォルトはtrue。
Option.HISTORY_IGNORE_DUPS trueの場合、入力された文字列が直前の履歴と同一なら登録しない。
デフォルトはtrue。
LineReader.HISTORY_IGNORE HISTORY_IGNOREで指定されたパターンに合致したら登録しない。
パターンは文字列を「:」で区切って指定する。ワイルドカード「*」も使用可能。
デフォルトはnull。
Option.HISTORY_INCREMENTAL trueの場合、メモリー上の履歴に登録する度にファイルに保存(save)する。
デフォルトはtrue。
LineReader.HISTORY_SIZE 保持する履歴の最大件数。デフォルトは500件。
履歴ファイル LineReader.HISTORY_FILE 履歴ファイルのパス。デフォルトはnull。
nullの場合、(loadsaveメソッドで)履歴ファイルの 処理は行われない。
Option.HISTORY_TIMESTAMPED trueの場合、履歴ファイルの中は「タイムスタンプ:文字列」という形式になる。falseだと「文字列」という形式。
(複数行の文字列の場合は改行コードがエスケープされて1行になる)
デフォルトはtrue。

ファイルの中が「文字列」という形式(「:」が無い)で当オプションがtrueだと、履歴ファイル読み込み時にIllegalArgumentExceptionが発生する。
逆だと、エラーにはならないが、タイムスタンプまで含めた文字列になってしまう。
LineReader.HISTORY_FILE_SIZE ファイルに保持する履歴の最大件数。デフォルトは10000件。
import java.nio.file.Path;
    Path path = Path.of(System.getProperty("java.io.tmpdir"), "jline-example-history.txt");
    lineReader.setVariable(LineReader.HISTORY_FILE, path);
    lineReader.setOpt(Option.HISTORY_IGNORE_DUPS);

//  lineReader.getHistory().attach(lineReader);

LineReaderで定義されている定数を使って設定する場合はLineReader#setVariable()(あるいはvariable())を使う。
LineReader.Optionで定義されている定数を使って設定する場合はLineReader#setOpt()やunsetOpt()(あるいはoption())を使う。

それから、History(DefaultHistory)の中でLineReaderの設定を参照する為に、HistoryにLineReaderをアタッチする必要がある。
ただし、LineReaderImpl.readLine()の中でアタッチされるので、自分で明示的にsave/loadを実行したい場合以外はattachメソッドを呼ぶ必要は無い。
(attachメソッドを呼ぶと、内部でload(履歴ファイルの読み込み)が実行される)


特定の履歴をファイルに保存しない例

HistoryのisPersistableメソッドをオーバーライドすると、特定の履歴(文字列)だけファイルに保存しないということが出来る。

import org.jline.reader.impl.history.DefaultHistory;
public class MyHistory extends DefaultHistory {

    @Override
    public boolean isPersistable(Entry entry) {
        String line = entry.line();
        if (line.startsWith(":") || line.startsWith("\\")) {
            // :や\で始まる文字列は履歴ファイルに保存しない
            return false;
        }
        return true;
    }
}
    LineReader lineReader = LineReaderBuilder.builder()
        .history(new MyHistory())
        .build();

ちなみに、LineReader.HISTORY_IGNOREという設定値もあるが、これは履歴ファイルへ保存しないものではなく、履歴自体へ登録しないパターンを設定する。
(ちなみにそれもmatchPatternsメソッドをオーバーライドすれば、自由に変更できる)


JLine3へ戻る / JLineへ戻る / Javaへ戻る / 技術メモへ戻る
メールの送信先:ひしだま