<commit_before>/*
 * Copyright (c) 2002-2006, <NAME>. All rights reserved.
 *
 * This software is distributable under the BSD license. See the terms of the
 * BSD license in the documentation provided with this software.
 */
package jline;

import java.awt.*;
import java.awt.datatransfer.*;

import java.io.*;
import java.util.*;
import java.util.List;


/**
 *  A reader for console applications. It supports custom tab-completion,
 *  saveable command history, and command line editing. On some
 *  platforms, platform-specific commands will need to be
 *  issued before the reader will function properly. See
 *  {@link Terminal#initializeTerminal} for convenience methods for
 *  issuing platform-specific setup commands.
 *
 *  @author  <a href="mailto:<EMAIL>"><NAME></a>
 */
public class ConsoleReader implements ConsoleOperations {
    String prompt;
    private boolean useHistory = true;
    public static final String CR = System.getProperty("line.separator");

    /**
     *  Map that contains the operation name to keymay operation mapping.
     */
    public static SortedMap KEYMAP_NAMES;

    static {
        Map names = new TreeMap();

        names.put("MOVE_TO_BEG", new Short(MOVE_TO_BEG));
        names.put("MOVE_TO_END", new Short(MOVE_TO_END));
        names.put("PREV_CHAR", new Short(PREV_CHAR));
        names.put("NEWLINE", new Short(NEWLINE));
        names.put("KILL_LINE", new Short(KILL_LINE));
        names.put("PASTE", new Short(PASTE));
        names.put("CLEAR_SCREEN", new Short(CLEAR_SCREEN));
        names.put("NEXT_HISTORY", new Short(NEXT_HISTORY));
        names.put("PREV_HISTORY", new Short(PREV_HISTORY));
        names.put("REDISPLAY", new Short(REDISPLAY));
        names.put("KILL_LINE_PREV", new Short(KILL_LINE_PREV));
        names.put("DELETE_PREV_WORD", new Short(DELETE_PREV_WORD));
        names.put("NEXT_CHAR", new Short(NEXT_CHAR));
        names.put("REPEAT_PREV_CHAR", new Short(REPEAT_PREV_CHAR));
        names.put("SEARCH_PREV", new Short(SEARCH_PREV));
        names.put("REPEAT_NEXT_CHAR", new Short(REPEAT_NEXT_CHAR));
        names.put("SEARCH_NEXT", new Short(SEARCH_NEXT));
        names.put("PREV_SPACE_WORD", new Short(PREV_SPACE_WORD));
        names.put("TO_END_WORD", new Short(TO_END_WORD));
        names.put("REPEAT_SEARCH_PREV", new Short(REPEAT_SEARCH_PREV));
        names.put("PASTE_PREV", new Short(PASTE_PREV));
        names.put("REPLACE_MODE", new Short(REPLACE_MODE));
        names.put("SUBSTITUTE_LINE", new Short(SUBSTITUTE_LINE));
        names.put("TO_PREV_CHAR", new Short(TO_PREV_CHAR));
        names.put("NEXT_SPACE_WORD", new Short(NEXT_SPACE_WORD));
        names.put("DELETE_PREV_CHAR", new Short(DELETE_PREV_CHAR));
        names.put("ADD", new Short(ADD));
        names.put("PREV_WORD", new Short(PREV_WORD));
        names.put("CHANGE_META", new Short(CHANGE_META));
        names.put("DELETE_META", new Short(DELETE_META));
        names.put("END_WORD", new Short(END_WORD));
        names.put("NEXT_CHAR", new Short(NEXT_CHAR));
        names.put("INSERT", new Short(INSERT));
        names.put("REPEAT_SEARCH_NEXT", new Short(REPEAT_SEARCH_NEXT));
        names.put("PASTE_NEXT", new Short(PASTE_NEXT));
        names.put("REPLACE_CHAR", new Short(REPLACE_CHAR));
        names.put("SUBSTITUTE_CHAR", new Short(SUBSTITUTE_CHAR));
        names.put("TO_NEXT_CHAR", new Short(TO_NEXT_CHAR));
        names.put("UNDO", new Short(UNDO));
        names.put("NEXT_WORD", new Short(NEXT_WORD));
        names.put("DELETE_NEXT_CHAR", new Short(DELETE_NEXT_CHAR));
        names.put("CHANGE_CASE", new Short(CHANGE_CASE));
        names.put("COMPLETE", new Short(COMPLETE));
        names.put("EXIT", new Short(EXIT));

        KEYMAP_NAMES = new TreeMap(Collections.unmodifiableMap(names));
    }

    /**
     *  The map for logical operations.
     */
    private final short[] keybindings;

    /**
     *  If true, issue an audible keyboard bell when appropriate.
     */
    private boolean bellEnabled = true;

    /**
     *  The current character mask.
     */
    private Character mask = null;

    /**
     *  The null mask.
     */
    private static final Character NULL_MASK = new Character((char) 0);

    /**
     *  The number of tab-completion candidates above which a warning
     *  will be prompted before showing all the candidates.
     */
    private int autoprintThreshhold = Integer.getInteger
        ("jline.completion.threshold", 100).intValue(); // same default as bash

    /**
     *  The Terminal to use.
     */
    private final Terminal terminal;
    private CompletionHandler completionHandler =
        new CandidateListCompletionHandler();
    InputStream in;
    final Writer out;
    final CursorBuffer buf = new CursorBuffer();
    static PrintWriter debugger;
    History history = new History();
    final List completors = new LinkedList();
    private Character echoCharacter = null;

    /**
     *  Create a new reader using {@link FileDescriptor#in} for input
     *  and {@link System#out} for output. {@link FileDescriptor#in} is
     *  used because it has a better chance of being unbuffered.
     */
    public ConsoleReader() throws IOException {
        this(new FileInputStream(FileDescriptor.in),
            new PrintWriter(System.out));
    }

    /**
     *  Create a new reader using the specified {@link InputStream}
     *  for input and the specific writer for output, using the
     *  default keybindings resource.
     */
    public ConsoleReader(final InputStream in, final Writer out)
                  throws IOException {
        this(in, out, null);
    }

    public ConsoleReader(final InputStream in, final Writer out,
                         final InputStream bindings) throws IOException {
        this(in, out, bindings, Terminal.getTerminal());
    }

    /**
     *  Create a new reader.
     *
     *  @param  in       the input
     *  @param  out      the output
     *  @param  bindings the key bindings to use
     *  @param  term     the terminal to use
     */
    public ConsoleReader(InputStream in, Writer out, InputStream bindings,
                         Terminal term) throws IOException {
        this.terminal = term;
        setInput(in);
        this.out = out;

        if (bindings == null) {
            String bindingFile =
                System.getProperty("jline.keybindings",
                   new File(System.getProperty("user.home",
                       ".jlinebindings.properties")).getAbsolutePath());

            if (!(new File(bindingFile).isFile())) {
                bindings = ConsoleReader.class.
                    getResourceAsStream("keybindings.properties");
            } else {
                bindings = new FileInputStream(new File(bindingFile));
            }
        }

        this.keybindings = new short[Byte.MAX_VALUE * 2];

        Arrays.fill(this.keybindings, UNKNOWN);

        /**
         *  Loads the key bindings. Bindings file is in the format:
         *
         *  keycode: operation name
         */
        if (bindings != null) {
            Properties p = new Properties();
            p.load(bindings);
            bindings.close();

            for (Iterator i = p.keySet().iterator(); i.hasNext();) {
                String val = (String) i.next();

                try {
                    Short code = new Short(val);
                    String op = (String) p.getProperty(val);

                    Short opval = (Short) KEYMAP_NAMES.get(op);

                    if (opval != null) {
                        keybindings[code.shortValue()] = opval.shortValue();
                    }
                } catch (NumberFormatException nfe) {
                    consumeException(nfe);
                }
            }

            // hardwired arrow key bindings
            // keybindings[VK_UP] = PREV_HISTORY;
            // keybindings[VK_DOWN] = NEXT_HISTORY;
            // keybindings[VK_LEFT] = PREV_CHAR;
            // keybindings[VK_RIGHT] = NEXT_CHAR;
        }
    }

    public Terminal getTerminal() {
        return this.terminal;
    }

    /**
     *  Set the stream for debugging. Development use only.
     */
    public void setDebug(final PrintWriter debugger) {
        ConsoleReader.debugger = debugger;
    }

    /**
     *  Set the stream to be used for console input.
     */
    public void setInput(final InputStream in) {
        this.in = in;
    }

    /**
     *  Returns the stream used for console input.
     */
    public InputStream getInput() {
        return this.in;
    }

    /**
     *  Read the next line and return the contents of the buffer.
     */
    public String readLine() throws IOException {
        return readLine((String) null);
    }

    /**
     *  Read the next line with the specified character mask. If null, then
     *  characters will be echoed. If 0, then no characters will be echoed.
     */
    public String readLine(final Character mask) throws IOException {
        return readLine(null, mask);
    }

    /**
     *  @param  bellEnabled  if true, enable audible keyboard bells if
     *                                          an alert is required.
     */
    public void setBellEnabled(final boolean bellEnabled) {
        this.bellEnabled = bellEnabled;
    }

    /**
     *  @return  true is audible keyboard bell is enabled.
     */
    public boolean getBellEnabled() {
        return this.bellEnabled;
    }

    /**
     *  Query the terminal to find the current width;
     *
     *  @see         Terminal#getTerminalWidth
     *  @return  the width of the current terminal.
     */
    public int getTermwidth() {
        return Terminal.setupTerminal().getTerminalWidth();
    }

    /**
     *  Query the terminal to find the current width;
     *
     *  @see         Terminal#getTerminalHeight
     *
     *  @return  the height of the current terminal.
     */
    public int getTermheight() {
        return Terminal.setupTerminal().getTerminalHeight();
    }

    /**
     *  @param  autoprintThreshhold  the number of candidates to print
     *                               without issuing a warning.
     */
    public void setAutoprintThreshhold(final int autoprintThreshhold) {
        this.autoprintThreshhold = autoprintThreshhold;
    }

    /**
     *  @return  the number of candidates to print without issing a warning.
     */
    public int getAutoprintThreshhold() {
        return this.autoprintThreshhold;
    }

    int getKeyForAction(short logicalAction) {
        for (int i = 0; i < keybindings.length; i++) {
            if (keybindings[i] == logicalAction) {
                return i;
            }
        }

        return -1;
    }

    /**
     *  Clear the echoed characters for the specified character code.
     */
    int clearEcho(int c) throws IOException {
        // if the terminal is not echoing, then just return...
        if (!terminal.getEcho()) {
            return 0;
        }

        // otherwise, clear
        int num = countEchoCharacters((char) c);
        back(num);
        drawBuffer(num);

        return num;
    }

    int countEchoCharacters(char c) {
        // tabs as special: we need to determine the number of spaces
        // to cancel based on what out current cursor position is
        if (c == 9) {
            int tabstop = 8; // will this ever be different?
            int position = getCursorPosition();

            return tabstop - (position % tabstop);
        }

        return getPrintableCharacters(c).length();
    }

    /**
     *  Return the number of characters that will be printed when the
     *  specified character is echoed to the screen. Adapted from
     *  cat by <NAME>, as repeated in stty by
     *  <NAME>.
     */
    StringBuffer getPrintableCharacters(char ch) {
        StringBuffer sbuff = new StringBuffer();

        if (ch >= 32) {
            if (ch < 127) {
                sbuff.append(ch);
            } else if (ch == 127) {
                sbuff.append('^');
                sbuff.append('?');
            } else {
                sbuff.append('M');
                sbuff.append('-');

                if (ch >= (128 + 32)) {
                    if (ch < (128 + 127)) {
                        sbuff.append((char) (ch - 128));
                    } else {
                        sbuff.append('^');
                        sbuff.append('?');
                    }
                } else {
                    sbuff.append('^');
                    sbuff.append((char) (ch - 128 + 64));
                }
            }
        } else {
            sbuff.append('^');
            sbuff.append((char) (ch + 64));
        }

        return sbuff;
    }

    int getCursorPosition() {
        // FIXME: does not handle anything but a line with a prompt
        // absolute position
        return ((prompt == null) ? 0 : prompt.length()) + buf.cursor;
    }

    public String readLine(final String prompt) throws IOException {
        return readLine(prompt, null);
    }

    /** 
     *  The default prompt that will be issued. 
     */
    public void setDefaultPrompt(String prompt) {
        this.prompt = prompt;
    }

    /** 
     *  The default prompt that will be issued. 
     */
    public String getDefaultPrompt() {
        return prompt;
    }

    /**
     *  Read a line from the <i>in</i> {@link InputStream}, and
     *  return the line (without any trailing newlines).
     *
     *  @param  prompt the prompt to issue to the console, may be null.
     *  @return        a line that is read from the terminal, or null if there
     *                 was null input (e.g., <i>CTRL-D</i> was pressed).
     */
    public String readLine(final String prompt, final Character mask)
        throws IOException {
        this.mask = mask;
        if (prompt != null)
            this.prompt = prompt;

        try {
            terminal.beforeReadLine(this, prompt, mask);

            if ((prompt != null) && (prompt.length() > 0)) {
                out.write(prompt);
                out.flush();
            }

            // if the terminal is unsupported, just use plain-java reading
            if (!terminal.isSupported()) {
                return readLine(in);
            }

            while (true) {
                int[] next = readBinding();

                if (next == null) {
                    return null;
                }

                int c = next[0];
                int code = next[1];

                if (c == -1) {
                    return null;
                }

                boolean success = true;

                switch (code) {
                    case EXIT: // ctrl-d

                        if (buf.buffer.length() == 0) {
                            return null;
                        }

                    case COMPLETE: // tab
                        success = complete();
                        break;

                    case MOVE_TO_BEG:
                        success = setCursorPosition(0);
                        break;

                    case KILL_LINE: // CTRL-K
                        success = killLine();
                        break;

                    case CLEAR_SCREEN: // CTRL-L
                        success = clearScreen();
                        break;

                    case KILL_LINE_PREV: // CTRL-U
                        success = resetLine();
                        break;

                    case NEWLINE: // enter
                        printNewline(); // output newline
                        return finishBuffer();

                    case DELETE_PREV_CHAR: // backspace
                        success = backspace();
                        break;

                    case MOVE_TO_END:
                        success = moveToEnd();
                        break;

                    case PREV_CHAR:
                        success = moveCursor(-1) != 0;
                        break;

                    case NEXT_CHAR:
                        success = moveCursor(1) != 0;
                        break;

                    case NEXT_HISTORY:
                        success = moveHistory(true);
                        break;

                    case PREV_HISTORY:
                        success = moveHistory(false);
                        break;

                    case REDISPLAY:
                        break;

                    case PASTE:
                        success = paste();
                        break;

                    case DELETE_PREV_WORD:
                        success = deletePreviousWord();
                        break;

                    case PREV_WORD:
                        success = previousWord();
                        break;

                    case NEXT_WORD:
                        success = nextWord();
                        break;

                    case UNKNOWN:default:
                        putChar(c, true);
                }

                if (!(success)) {
                    beep();
                }

                flushConsole();
            }
        } finally {
            terminal.afterReadLine(this, prompt, mask);
        }
    }

    private String readLine(InputStream in) throws IOException {
        StringBuffer buf = new StringBuffer();

        while (true) {
            int i = in.read();

            if ((i == -1) || (i == '\n') || (i == '\r')) {
                return buf.toString();<commit_msg>Fix for when prompt is null (submitted by <NAME>)<commit_after>/*
 * Copyright (c) 2002-2006, <NAME>. All rights reserved.
 *
 * This software is distributable under the BSD license. See the terms of the
 * BSD license in the documentation provided with this software.
 */
package jline;

import java.awt.*;
import java.awt.datatransfer.*;

import java.io.*;
import java.util.*;
import java.util.List;


/**
 *  A reader for console applications. It supports custom tab-completion,
 *  saveable command history, and command line editing. On some
 *  platforms, platform-specific commands will need to be
 *  issued before the reader will function properly. See
 *  {@link Terminal#initializeTerminal} for convenience methods for
 *  issuing platform-specific setup commands.
 *
 *  @author  <a href="mailto:<EMAIL>"><NAME></a>
 */
public class ConsoleReader implements ConsoleOperations {
    String prompt;
    private boolean useHistory = true;
    public static final String CR = System.getProperty("line.separator");

    /**
     *  Map that contains the operation name to keymay operation mapping.
     */
    public static SortedMap KEYMAP_NAMES;

    static {
        Map names = new TreeMap();

        names.put("MOVE_TO_BEG", new Short(MOVE_TO_BEG));
        names.put("MOVE_TO_END", new Short(MOVE_TO_END));
        names.put("PREV_CHAR", new Short(PREV_CHAR));
        names.put("NEWLINE", new Short(NEWLINE));
        names.put("KILL_LINE", new Short(KILL_LINE));
        names.put("PASTE", new Short(PASTE));
        names.put("CLEAR_SCREEN", new Short(CLEAR_SCREEN));
        names.put("NEXT_HISTORY", new Short(NEXT_HISTORY));
        names.put("PREV_HISTORY", new Short(PREV_HISTORY));
        names.put("REDISPLAY", new Short(REDISPLAY));
        names.put("KILL_LINE_PREV", new Short(KILL_LINE_PREV));
        names.put("DELETE_PREV_WORD", new Short(DELETE_PREV_WORD));
        names.put("NEXT_CHAR", new Short(NEXT_CHAR));
        names.put("REPEAT_PREV_CHAR", new Short(REPEAT_PREV_CHAR));
        names.put("SEARCH_PREV", new Short(SEARCH_PREV));
        names.put("REPEAT_NEXT_CHAR", new Short(REPEAT_NEXT_CHAR));
        names.put("SEARCH_NEXT", new Short(SEARCH_NEXT));
        names.put("PREV_SPACE_WORD", new Short(PREV_SPACE_WORD));
        names.put("TO_END_WORD", new Short(TO_END_WORD));
        names.put("REPEAT_SEARCH_PREV", new Short(REPEAT_SEARCH_PREV));
        names.put("PASTE_PREV", new Short(PASTE_PREV));
        names.put("REPLACE_MODE", new Short(REPLACE_MODE));
        names.put("SUBSTITUTE_LINE", new Short(SUBSTITUTE_LINE));
        names.put("TO_PREV_CHAR", new Short(TO_PREV_CHAR));
        names.put("NEXT_SPACE_WORD", new Short(NEXT_SPACE_WORD));
        names.put("DELETE_PREV_CHAR", new Short(DELETE_PREV_CHAR));
        names.put("ADD", new Short(ADD));
        names.put("PREV_WORD", new Short(PREV_WORD));
        names.put("CHANGE_META", new Short(CHANGE_META));
        names.put("DELETE_META", new Short(DELETE_META));
        names.put("END_WORD", new Short(END_WORD));
        names.put("NEXT_CHAR", new Short(NEXT_CHAR));
        names.put("INSERT", new Short(INSERT));
        names.put("REPEAT_SEARCH_NEXT", new Short(REPEAT_SEARCH_NEXT));
        names.put("PASTE_NEXT", new Short(PASTE_NEXT));
        names.put("REPLACE_CHAR", new Short(REPLACE_CHAR));
        names.put("SUBSTITUTE_CHAR", new Short(SUBSTITUTE_CHAR));
        names.put("TO_NEXT_CHAR", new Short(TO_NEXT_CHAR));
        names.put("UNDO", new Short(UNDO));
        names.put("NEXT_WORD", new Short(NEXT_WORD));
        names.put("DELETE_NEXT_CHAR", new Short(DELETE_NEXT_CHAR));
        names.put("CHANGE_CASE", new Short(CHANGE_CASE));
        names.put("COMPLETE", new Short(COMPLETE));
        names.put("EXIT", new Short(EXIT));

        KEYMAP_NAMES = new TreeMap(Collections.unmodifiableMap(names));
    }

    /**
     *  The map for logical operations.
     */
    private final short[] keybindings;

    /**
     *  If true, issue an audible keyboard bell when appropriate.
     */
    private boolean bellEnabled = true;

    /**
     *  The current character mask.
     */
    private Character mask = null;

    /**
     *  The null mask.
     */
    private static final Character NULL_MASK = new Character((char) 0);

    /**
     *  The number of tab-completion candidates above which a warning
     *  will be prompted before showing all the candidates.
     */
    private int autoprintThreshhold = Integer.getInteger
        ("jline.completion.threshold", 100).intValue(); // same default as bash

    /**
     *  The Terminal to use.
     */
    private final Terminal terminal;
    private CompletionHandler completionHandler =
        new CandidateListCompletionHandler();
    InputStream in;
    final Writer out;
    final CursorBuffer buf = new CursorBuffer();
    static PrintWriter debugger;
    History history = new History();
    final List completors = new LinkedList();
    private Character echoCharacter = null;

    /**
     *  Create a new reader using {@link FileDescriptor#in} for input
     *  and {@link System#out} for output. {@link FileDescriptor#in} is
     *  used because it has a better chance of being unbuffered.
     */
    public ConsoleReader() throws IOException {
        this(new FileInputStream(FileDescriptor.in),
            new PrintWriter(System.out));
    }

    /**
     *  Create a new reader using the specified {@link InputStream}
     *  for input and the specific writer for output, using the
     *  default keybindings resource.
     */
    public ConsoleReader(final InputStream in, final Writer out)
                  throws IOException {
        this(in, out, null);
    }

    public ConsoleReader(final InputStream in, final Writer out,
                         final InputStream bindings) throws IOException {
        this(in, out, bindings, Terminal.getTerminal());
    }

    /**
     *  Create a new reader.
     *
     *  @param  in       the input
     *  @param  out      the output
     *  @param  bindings the key bindings to use
     *  @param  term     the terminal to use
     */
    public ConsoleReader(InputStream in, Writer out, InputStream bindings,
                         Terminal term) throws IOException {
        this.terminal = term;
        setInput(in);
        this.out = out;

        if (bindings == null) {
            String bindingFile =
                System.getProperty("jline.keybindings",
                   new File(System.getProperty("user.home",
                       ".jlinebindings.properties")).getAbsolutePath());

            if (!(new File(bindingFile).isFile())) {
                bindings = ConsoleReader.class.
                    getResourceAsStream("keybindings.properties");
            } else {
                bindings = new FileInputStream(new File(bindingFile));
            }
        }

        this.keybindings = new short[Byte.MAX_VALUE * 2];

        Arrays.fill(this.keybindings, UNKNOWN);

        /**
         *  Loads the key bindings. Bindings file is in the format:
         *
         *  keycode: operation name
         */
        if (bindings != null) {
            Properties p = new Properties();
            p.load(bindings);
            bindings.close();

            for (Iterator i = p.keySet().iterator(); i.hasNext();) {
                String val = (String) i.next();

                try {
                    Short code = new Short(val);
                    String op = (String) p.getProperty(val);

                    Short opval = (Short) KEYMAP_NAMES.get(op);

                    if (opval != null) {
                        keybindings[code.shortValue()] = opval.shortValue();
                    }
                } catch (NumberFormatException nfe) {
                    consumeException(nfe);
                }
            }

            // hardwired arrow key bindings
            // keybindings[VK_UP] = PREV_HISTORY;
            // keybindings[VK_DOWN] = NEXT_HISTORY;
            // keybindings[VK_LEFT] = PREV_CHAR;
            // keybindings[VK_RIGHT] = NEXT_CHAR;
        }
    }

    public Terminal getTerminal() {
        return this.terminal;
    }

    /**
     *  Set the stream for debugging. Development use only.
     */
    public void setDebug(final PrintWriter debugger) {
        ConsoleReader.debugger = debugger;
    }

    /**
     *  Set the stream to be used for console input.
     */
    public void setInput(final InputStream in) {
        this.in = in;
    }

    /**
     *  Returns the stream used for console input.
     */
    public InputStream getInput() {
        return this.in;
    }

    /**
     *  Read the next line and return the contents of the buffer.
     */
    public String readLine() throws IOException {
        return readLine((String) null);
    }

    /**
     *  Read the next line with the specified character mask. If null, then
     *  characters will be echoed. If 0, then no characters will be echoed.
     */
    public String readLine(final Character mask) throws IOException {
        return readLine(null, mask);
    }

    /**
     *  @param  bellEnabled  if true, enable audible keyboard bells if
     *                                          an alert is required.
     */
    public void setBellEnabled(final boolean bellEnabled) {
        this.bellEnabled = bellEnabled;
    }

    /**
     *  @return  true is audible keyboard bell is enabled.
     */
    public boolean getBellEnabled() {
        return this.bellEnabled;
    }

    /**
     *  Query the terminal to find the current width;
     *
     *  @see         Terminal#getTerminalWidth
     *  @return  the width of the current terminal.
     */
    public int getTermwidth() {
        return Terminal.setupTerminal().getTerminalWidth();
    }

    /**
     *  Query the terminal to find the current width;
     *
     *  @see         Terminal#getTerminalHeight
     *
     *  @return  the height of the current terminal.
     */
    public int getTermheight() {
        return Terminal.setupTerminal().getTerminalHeight();
    }

    /**
     *  @param  autoprintThreshhold  the number of candidates to print
     *                               without issuing a warning.
     */
    public void setAutoprintThreshhold(final int autoprintThreshhold) {
        this.autoprintThreshhold = autoprintThreshhold;
    }

    /**
     *  @return  the number of candidates to print without issing a warning.
     */
    public int getAutoprintThreshhold() {
        return this.autoprintThreshhold;
    }

    int getKeyForAction(short logicalAction) {
        for (int i = 0; i < keybindings.length; i++) {
            if (keybindings[i] == logicalAction) {
                return i;
            }
        }

        return -1;
    }

    /**
     *  Clear the echoed characters for the specified character code.
     */
    int clearEcho(int c) throws IOException {
        // if the terminal is not echoing, then just return...
        if (!terminal.getEcho()) {
            return 0;
        }

        // otherwise, clear
        int num = countEchoCharacters((char) c);
        back(num);
        drawBuffer(num);

        return num;
    }

    int countEchoCharacters(char c) {
        // tabs as special: we need to determine the number of spaces
        // to cancel based on what out current cursor position is
        if (c == 9) {
            int tabstop = 8; // will this ever be different?
            int position = getCursorPosition();

            return tabstop - (position % tabstop);
        }

        return getPrintableCharacters(c).length();
    }

    /**
     *  Return the number of characters that will be printed when the
     *  specified character is echoed to the screen. Adapted from
     *  cat by <NAME>, as repeated in stty by
     *  <NAME>.
     */
    StringBuffer getPrintableCharacters(char ch) {
        StringBuffer sbuff = new StringBuffer();

        if (ch >= 32) {
            if (ch < 127) {
                sbuff.append(ch);
            } else if (ch == 127) {
                sbuff.append('^');
                sbuff.append('?');
            } else {
                sbuff.append('M');
                sbuff.append('-');

                if (ch >= (128 + 32)) {
                    if (ch < (128 + 127)) {
                        sbuff.append((char) (ch - 128));
                    } else {
                        sbuff.append('^');
                        sbuff.append('?');
                    }
                } else {
                    sbuff.append('^');
                    sbuff.append((char) (ch - 128 + 64));
                }
            }
        } else {
            sbuff.append('^');
            sbuff.append((char) (ch + 64));
        }

        return sbuff;
    }

    int getCursorPosition() {
        // FIXME: does not handle anything but a line with a prompt
        // absolute position
        return ((prompt == null) ? 0 : prompt.length()) + buf.cursor;
    }

    public String readLine(final String prompt) throws IOException {
        return readLine(prompt, null);
    }

    /** 
     *  The default prompt that will be issued. 
     */
    public void setDefaultPrompt(String prompt) {
        this.prompt = prompt;
    }

    /** 
     *  The default prompt that will be issued. 
     */
    public String getDefaultPrompt() {
        return prompt;
    }

    /**
     *  Read a line from the <i>in</i> {@link InputStream}, and
     *  return the line (without any trailing newlines).
     *
     *  @param  prompt the prompt to issue to the console, may be null.
     *  @return        a line that is read from the terminal, or null if there
     *                 was null input (e.g., <i>CTRL-D</i> was pressed).
     */
    public String readLine(final String prompt, final Character mask)
        throws IOException {
        this.mask = mask;
        if (prompt != null)
            this.prompt = prompt;

        try {
            terminal.beforeReadLine(this, this.prompt, mask);

            if ((this.prompt != null) && (this.prompt.length() > 0)) {
                out.write(this.prompt);
                out.flush();
            }

            // if the terminal is unsupported, just use plain-java reading
            if (!terminal.isSupported()) {
                return readLine(in);
            }

            while (true) {
                int[] next = readBinding();

                if (next == null) {
                    return null;
                }

                int c = next[0];
                int code = next[1];

                if (c == -1) {
                    return null;
                }

                boolean success = true;

                switch (code) {
                    case EXIT: // ctrl-d

                        if (buf.buffer.length() == 0) {
                            return null;
                        }

                    case COMPLETE: // tab
                        success = complete();
                        break;

                    case MOVE_TO_BEG:
                        success = setCursorPosition(0);
                        break;

                    case KILL_LINE: // CTRL-K
                        success = killLine();
                        break;

                    case CLEAR_SCREEN: // CTRL-L
                        success = clearScreen();
                        break;

                    case KILL_LINE_PREV: // CTRL-U
                        success = resetLine();
                        break;

                    case NEWLINE: // enter
                        printNewline(); // output newline
                        return finishBuffer();

                    case DELETE_PREV_CHAR: // backspace
                        success = backspace();
                        break;

                    case MOVE_TO_END:
                        success = moveToEnd();
                        break;

                    case PREV_CHAR:
                        success = moveCursor(-1) != 0;
                        break;

                    case NEXT_CHAR:
                        success = moveCursor(1) != 0;
                        break;

                    case NEXT_HISTORY:
                        success = moveHistory(true);
                        break;

                    case PREV_HISTORY:
                        success = moveHistory(false);
                        break;

                    case REDISPLAY:
                        break;

                    case PASTE:
                        success = paste();
                        break;

                    case DELETE_PREV_WORD:
                        success = deletePreviousWord();
                        break;

                    case PREV_WORD:
                        success = previousWord();
                        break;

                    case NEXT_WORD:
                        success = nextWord();
                        break;

                    case UNKNOWN:default:
                        putChar(c, true);
                }

                if (!(success)) {
                    beep();
                }

                flushConsole();
            }
        } finally {
            terminal.afterReadLine(this, this.prompt, mask);
        }
    }

    private String readLine(InputStream in) throws IOException {
        StringBuffer buf = new StringBuffer();

        while (true) {
            int i = in.read();

            if ((i == -1) || (i == '\n') || (i == '\r')) {
                return buf.toString();
{'fraction_non_alphanumeric': 0.1011569775746322, 'fraction_numerical': 0.0034280817026139123, 'mean_word_length': 1.6026765799256506, 'pattern_counts': {'":': 0, '<': 32, '<?xml version=': 0, '>': 32, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5435881', 'n_tokens_mistral': 9747, 'n_tokens_neox': 9152, 'n_words': 2569}