Stage: Builtin completion #qp2, Autocompletion Works Locally but Fails Codecrafters Tests

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 to echo␣.
  • When the user types exi and presses <TAB>, it completes to exit␣.
  • 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

  1. Does Codecrafters expect a specific way of handling output that differs from regular terminal behavior?
  2. Is there any specific way to simulate user input/output to debug this issue?
  3. 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

I’ll take a look and get back to you by the end of the week.

@Md-Talim Looks like you’ve got past this stage. Do you happen to remember what was wrong? Would love to see if we can improve the tester / instructions.

Closing this thread due to inactivity. If you still need assistance, feel free to reopen or start a new discussion!