JLine3のCompleterのメモ。
JLineは、タブキーを押すと入力候補を表示・選択できる機能がある。
デフォルトでは入力候補が空なので、何も補完できないが。
LineReaderにCompleterを指定すると入力補完されるようになる。
Completerは、JLineで入力補完の候補を管理するクラス(インターフェース)。
具象クラスとして、文字列を補完するStringsCompleterやファイル名を補完するFileNameCompleter等がある。
import org.jline.reader.Completer; import org.jline.reader.impl.completer.StringsCompleter;
Completer completer = new StringsCompleter("aaa", "abc", "bbb"); LineReader lineReader = LineReaderBuilder.builder() // .completer(completer) // .build();
タブキーを押すと、StringsCompleterで指定した文字列が入力候補として表示される。
jline> ここでタブキーを押下 aaa abc bbb ←補完候補の一覧が表示される jline> aaa ←さらにタブキーを押すと、候補の中から順番に選択できる aaa abc bbb
jline> aここでタブキーを押下 aaa abc ←先頭が一致する候補だけが表示される
jline> aaa 空白の後でタブキーを押すと、そこでも補完候補が表示される
aaa abc bbb
独自のCompleterを作る例として、SQL文の入力補完を考える。
まず、select・insert・update・deleteを候補として表示してみる。
import org.jline.reader.Candidate; import org.jline.reader.Completer; import org.jline.reader.LineReader; import org.jline.reader.ParsedLine;
public class MyCompleter implements Completer { private List<String> list = List.of("insert", "select", "update", "delete");
@Override public void complete(LineReader reader, ParsedLine commandLine, List<Candidate> candidates) { for (String word : list) { candidates.add(new Candidate(word, word, null, null, null, null, true)); } } }
LineReader lineReader = LineReaderBuilder.builder() // .completer(new MyCompleter) // .build();
入力候補は、completeメソッドのcandidatesに追加する。
Candidateコンストラクターの第1引数が候補の文字列。
第2引数は一覧表示されるときの文字列。色を変えたりすることが出来る模様。
第7引数(最後の引数)は、後続があるかどうか。後続がある場合、補完後にコマンドライン上で空白が追加される。
ただし、これだけだと、一度補完した後にさらにタブキーを押すと、そこでも同じ候補が出てきてしまう。
jline> select 空白の後でタブキーを押すと、そこでも補完候補が表示される
delete insert select update
一度単語を入力した後にさらに補完されないためには、カーソル位置に応じて補完候補を用意する必要がある。
@Override public void complete(LineReader reader, ParsedLine commandLine, List<Candidate> candidates) { String buffer = commandLine.line().substring(0, commandLine.cursor()); for (String word : list) { if (word.startsWith(buffer)) { candidates.add(new Candidate(word, word, null, null, null, null, true)); } } }
commandLine.line()で、入力されている文字列全体が取れる。
commandLine.cursor()で、現在のカーソル位置が取れる。
カーソル位置までの文字列の先頭が入力候補の文字列と同じであれば、候補として表示しないようにすれば良さそう。
さらに、insertの場合は直後の単語はinto、deleteの直後はfromと決まっているので、それを補完したい。
入力候補に2つ目の単語を入れてみる。
private List<String> list = List.of("insert into", "select", "update", "delete from");
これでも補完候補として扱われるのだが、空白を含む文字列は、補完されたときにエスケープされてしまう。
(エスケープしないように設定している場合は、シングルクォーテーションで囲まれてしまう)
jline> ここでタブキーを押下
delete from insert into select update
↓補完後
jline> insert\ into
自分で、入力中の文字列がinsertやdeleteであることを判別してみる。
@Override public void complete(LineReader reader, ParsedLine commandLine, List<Candidate> candidates) { String buffer = commandLine.line().substring(0, commandLine.cursor()); if (buffer.startsWith("insert")) { if (buffer.startsWith("insert into")) { return; } String word = "into"; candidates.add(new Candidate(word, word, null, null, null, null, true)); return; } if (buffer.startsWith("delete")) { if (buffer.startsWith("delete from")) { return; } String word = "from"; candidates.add(new Candidate(word, word, null, null, null, null, true)); return; } for (String word : list) { if (word.startsWith(buffer)) { candidates.add(new Candidate(word, word, null, null, null, null, true)); } } }
こうして見るとなんとなく分かるが、本来はパーサー(構文解析器)を用意して、カーソル位置に応じた単語を補完候補にするのが妥当だろう。