I’m implementing the builtin command autocompletion stage for my shell project. Locally, autocompletion for echo
and exit
works as expected, but the Codecrafters tests fail.
What I Have Implemented
- When the user types
ech
and presses<TAB>
, it completes toecho␣
. - When the user types
exi
and presses<TAB>
, it completes toexit␣
. - The logic ensures autocompletion happens only if there is exactly one match.
Issue
- The same code works fine on my local machine but does not pass the Codecrafters tests.
- I’m not sure what the test framework expects differently.
- The failure message does not clearly indicate what’s wrong.
Questions
- Does Codecrafters expect a specific way of handling output that differs from regular terminal behavior?
- Is there any specific way to simulate user input/output to debug this issue?
- Could there be a difference in how
<TAB>
is handled in the test environment?
Any insights from others who have passed this stage would be really helpful. Thanks!
Here’s the code in java:
package shell;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import shell.command.*;
import shell.environment.Environment;
import shell.environment.SystemEnvironment;
import shell.io.Redirection;
import shell.parser.Parser;
import shell.process.ProcessExecutor;
import shell.process.SystemProcessExecutor;
import shell.terminal.Termios;
public class Shell {
private final Map<String, Command> builtinCommands;
private final Environment environment;
private final ProcessExecutor processExecutor;
private boolean shouldExit;
public Shell() {
this.environment = new SystemEnvironment();
this.processExecutor = new SystemProcessExecutor();
this.builtinCommands = new HashMap<>();
this.shouldExit = false;
initializeBuiltinCommands();
}
private void initializeBuiltinCommands() {
builtinCommands.put("exit", new ExitCommand(() -> shouldExit = true));
builtinCommands.put("echo", new EchoCommand());
builtinCommands.put("pwd", new PwdCommand(environment));
builtinCommands.put("cd", new CdCommand(environment));
builtinCommands.put("type", new TypeCommand(builtinCommands, environment));
}
private void executeCommand(String input) {
try {
Parser parser = new Parser(input);
List<String> args = parser.parse();
Redirection redirection = parser.getRedirection();
if (args.isEmpty()) {
return;
}
String commandName = args.get(0);
Command command = builtinCommands.getOrDefault(commandName,
new ExternalCommand(commandName, processExecutor));
command.execute(args, redirection);
} catch (Exception e) {
System.err.println(e.getMessage());
}
}
private String autocomplete(String input) {
List<String> matches = builtinCommands.keySet()
.stream()
.filter(command -> command.startsWith(input))
.sorted()
.toList();
if (matches.size() == 1) {
return matches.get(0) + " ";
}
return null;
}
private String read() {
Termios.enableRawMode();
final var line = new StringBuilder();
try {
while (true) {
int key = System.in.read();
if (key == -1 || key == 4) { // CTRL + D (EOF)
return null;
} else if (key == '\n') { // ENTER KEY
System.out.println();
return line.toString();
} else if (key == '\t') { // HANDLE TAB KEY
String completion = autocomplete(line.toString());
if (completion != null) {
System.out.print("\r$ " + completion);
line.setLength(0);
line.append(completion);
}
} else if (key == 127) { // BACKSPACE KEY
if (line.length() > 0) {
line.setLength(line.length() - 1);
System.out.print("\b \b"); // Move cursor back, erase character
}
} else {
line.append((char) key);
System.out.print((char) key);
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
Termios.disableRawMode();
}
return "";
}
public void run() {
while (true) {
System.out.print("$ ");
final String line = read();
if (line == null) {
break;
}
executeCommand(line);
if (shouldExit) {
break;
}
}
}
}
See the full code on Github here (feat/auto-completions branch): https://github.com/Md-Talim/codecrafters-shell-java/tree/feat/auto-completions