Skip to content

Commit 5036cea

Browse files
author
Max Vorobev
authored
Autocomplete for commands (#173)
## What is the goal of this PR? Introduce context-aware autocomplete for interactive TypeDB Console sessions. ## What are the changes implemented in this PR? Fix #172 * Make all commands' `token`s public so they can be reused when instantiating completer classes * Instantiate a completer using `TreeCompleter` class from JLine. It's aware of context: where database name is expected, it completes database names; same is true for user names in `user delete <username>` command.
1 parent c0a7575 commit 5036cea

File tree

2 files changed

+68
-14
lines changed

2 files changed

+68
-14
lines changed

TypeDBConsole.java

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import com.vaticle.typedb.client.api.answer.Numeric;
2929
import com.vaticle.typedb.client.api.answer.NumericGroup;
3030
import com.vaticle.typedb.client.api.connection.database.Database;
31+
import com.vaticle.typedb.client.api.connection.user.User;
3132
import com.vaticle.typedb.client.common.exception.TypeDBClientException;
3233
import com.vaticle.typedb.common.collection.Either;
3334
import com.vaticle.typedb.common.util.Java;
@@ -45,8 +46,12 @@
4546
import com.vaticle.typeql.lang.query.TypeQLQuery;
4647
import com.vaticle.typeql.lang.query.TypeQLUndefine;
4748
import com.vaticle.typeql.lang.query.TypeQLUpdate;
49+
import org.jline.builtins.Completers;
50+
import org.jline.reader.Candidate;
51+
import org.jline.reader.Completer;
4852
import org.jline.reader.LineReader;
4953
import org.jline.reader.LineReaderBuilder;
54+
import org.jline.reader.impl.completer.StringsCompleter;
5055
import org.jline.terminal.Terminal;
5156
import org.jline.terminal.TerminalBuilder;
5257
import org.jline.utils.InfoCmp;
@@ -62,6 +67,7 @@
6267
import java.nio.file.Paths;
6368
import java.time.Duration;
6469
import java.time.Instant;
70+
import java.util.ArrayList;
6571
import java.util.Arrays;
6672
import java.util.Iterator;
6773
import java.util.List;
@@ -74,6 +80,7 @@
7480
import java.util.function.Consumer;
7581
import java.util.stream.Stream;
7682

83+
import static org.jline.builtins.Completers.TreeCompleter.node;
7784
import static com.vaticle.typedb.common.collection.Collections.set;
7885
import static com.vaticle.typedb.console.common.exception.ErrorMessage.Console.INCOMPATIBLE_JAVA_RUNTIME;
7986
import static java.util.stream.Collectors.toList;
@@ -162,6 +169,7 @@ private void runREPLMode(CLIOptions options) {
162169
LineReader reader = LineReaderBuilder.builder()
163170
.terminal(terminal)
164171
.variable(LineReader.HISTORY_FILE, COMMAND_HISTORY_FILE)
172+
.completer(getCompleter(client))
165173
.build();
166174
while (true) {
167175
REPLCommand command;
@@ -213,6 +221,52 @@ private void runREPLMode(CLIOptions options) {
213221
}
214222
}
215223

224+
private Completers.TreeCompleter getCompleter(TypeDBClient client) {
225+
Completer databaseNameCompleter = (reader, line, candidates) -> client.databases().all().stream()
226+
.map(Database::name)
227+
.filter(name -> name.startsWith(line.word()))
228+
.forEach(name -> candidates.add(new Candidate(name)));
229+
Completer userNameCompleter = (reader, line, candidates) -> {
230+
client.asCluster().users().all().stream()
231+
.map(User::name)
232+
// "admin" user is excluded as it can't be deleted
233+
.filter(name -> name.startsWith(line.word()) && !"admin".equals(name))
234+
.forEach(name -> candidates.add(new Candidate(name)));
235+
};
236+
final List<Completers.TreeCompleter.Node> nodes = new ArrayList<>();
237+
nodes.add(
238+
node(REPLCommand.Database.token,
239+
node(REPLCommand.Database.List.token),
240+
node(REPLCommand.Database.Create.token),
241+
node(REPLCommand.Database.Delete.token,
242+
node(databaseNameCompleter)),
243+
node(REPLCommand.Database.Schema.token,
244+
node(databaseNameCompleter)
245+
)
246+
));
247+
if (client.isCluster()) {
248+
nodes.add(node(REPLCommand.User.token,
249+
node(REPLCommand.User.List.token),
250+
node(REPLCommand.User.Create.token),
251+
node(REPLCommand.User.Delete.token,
252+
node(userNameCompleter))
253+
));
254+
}
255+
nodes.add(node(REPLCommand.Transaction.token,
256+
node(databaseNameCompleter,
257+
node(new StringsCompleter("schema", "data"),
258+
node(new StringsCompleter("read", "write")
259+
// TODO(vmax): complete [transaction-options] here
260+
)
261+
)
262+
)
263+
));
264+
nodes.add(node(REPLCommand.Help.token));
265+
nodes.add(node(REPLCommand.Clear.token));
266+
nodes.add(node(REPLCommand.Exit.token));
267+
return new Completers.TreeCompleter(nodes);
268+
}
269+
216270
private boolean transactionREPL(TypeDBClient client, String database, TypeDBSession.Type sessionType, TypeDBTransaction.Type transactionType, TypeDBOptions options) {
217271
LineReader reader = LineReaderBuilder.builder()
218272
.terminal(terminal)

command/REPLCommand.java

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ default Transaction asTransaction() {
138138

139139
class Exit implements REPLCommand {
140140

141-
private static String token = "exit";
141+
public static String token = "exit";
142142
private static String helpCommand = token;
143143
private static String description = "Exit console";
144144

@@ -155,7 +155,7 @@ public Exit asExit() {
155155

156156
class Help implements REPLCommand {
157157

158-
private static String token = "help";
158+
public static String token = "help";
159159
private static String helpCommand = token;
160160
private static String description = "Print this help menu";
161161

@@ -172,7 +172,7 @@ public Help asHelp() {
172172

173173
class Clear implements REPLCommand {
174174

175-
private static String token = "clear";
175+
public static String token = "clear";
176176
private static String helpCommand = token;
177177
private static String description = "Clear console screen";
178178

@@ -189,11 +189,11 @@ public Clear asClear() {
189189

190190
abstract class Database implements REPLCommand {
191191

192-
private static String token = "database";
192+
public static String token = "database";
193193

194194
public static class List extends REPLCommand.Database {
195195

196-
private static String token = "list";
196+
public static String token = "list";
197197
private static String helpCommand = Database.token + " " + token;
198198
private static String description = "List the databases on the server";
199199

@@ -210,7 +210,7 @@ public Database.List asDatabaseList() {
210210

211211
public static class Create extends REPLCommand.Database {
212212

213-
private static String token = "create";
213+
public static String token = "create";
214214
private static String helpCommand = Database.token + " " + token + " " + "<db>";
215215
private static String description = "Create a database with name <db> on the server";
216216

@@ -237,7 +237,7 @@ public Database.Create asDatabaseCreate() {
237237

238238
public static class Delete extends REPLCommand.Database {
239239

240-
private static String token = "delete";
240+
public static String token = "delete";
241241
private static String helpCommand = Database.token + " " + token + " " + "<db>";
242242
private static String description = "Delete a database with name <db> on the server";
243243

@@ -264,7 +264,7 @@ public Database.Delete asDatabaseDelete() {
264264

265265
public static class Schema extends REPLCommand.Database {
266266

267-
private static String token = "schema";
267+
public static String token = "schema";
268268
private static String helpCommand = Database.token + " " + token + " " + "<db>";
269269
private static String description = "Print the schema of the database with name <db>";
270270

@@ -291,7 +291,7 @@ public Database.Schema asDatabaseSchema() {
291291

292292
public static class Replicas extends REPLCommand.Database {
293293

294-
private static String token = "replicas";
294+
public static String token = "replicas";
295295
private static String helpCommand = Database.token + " " + token + " " + "<db>";
296296
private static String description = "List replicas of a database with name <db>";
297297

@@ -319,11 +319,11 @@ public Database.Replicas asDatabaseReplicas() {
319319

320320
abstract class User implements REPLCommand {
321321

322-
private static String token = "user";
322+
public static String token = "user";
323323

324324
public static class List extends REPLCommand.User {
325325

326-
private static String token = "list";
326+
public static String token = "list";
327327
private static String helpCommand = User.token + " " + token;
328328
private static String description = "List the users on the server";
329329

@@ -340,7 +340,7 @@ public User.List asUserList() {
340340

341341
public static class Create extends REPLCommand.User {
342342

343-
private static String token = "create";
343+
public static String token = "create";
344344
private static String helpCommand = User.token + " " + token + " " + "<username> <password>";
345345
private static String description = "Create a user with name <username> and password <password> on the server";
346346

@@ -373,7 +373,7 @@ public User.Create asUserCreate() {
373373

374374
public static class Delete extends REPLCommand.User {
375375

376-
private static String token = "delete";
376+
public static String token = "delete";
377377
private static String helpCommand = User.token + " " + token + " " + "<username>";
378378
private static String description = "Delete a user with name <username> on the server";
379379

@@ -401,7 +401,7 @@ public User.Delete asUserDelete() {
401401

402402
class Transaction implements REPLCommand {
403403

404-
private static final String token = "transaction";
404+
public static final String token = "transaction";
405405
private static final String helpCommand = token + " <db> schema|data read|write [" + Options.token + "]";
406406
private static final String description = "Start a transaction to database <db> with schema or data session, with read or write transaction";
407407

0 commit comments

Comments
 (0)