// 
// Decompiled by Procyon v0.6.0
// 

package org.jline.builtins;

import org.jline.utils.Colors;
import java.io.Reader;
import java.io.InputStreamReader;
import java.io.FileWriter;
import org.jline.utils.StyleResolver;
import java.util.HashMap;
import java.nio.file.OpenOption;
import java.nio.charset.StandardCharsets;
import java.io.BufferedReader;
import org.jline.reader.Reference;
import org.jline.reader.Macro;
import org.jline.reader.Binding;
import java.util.Set;
import org.jline.keymap.KeyMap;
import java.util.HashSet;
import java.util.Collection;
import java.util.TreeSet;
import org.jline.reader.Widget;
import java.util.function.Function;
import java.util.Map;
import org.jline.reader.Highlighter;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalUnit;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAccessor;
import java.time.LocalTime;
import java.time.ZoneId;
import org.jline.utils.AttributedStyle;
import org.jline.utils.AttributedStringBuilder;
import org.jline.reader.History;
import java.util.regex.Pattern;
import org.jline.reader.LineReader;
import java.io.IOException;
import java.util.stream.Stream;
import java.nio.file.PathMatcher;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.function.Predicate;
import java.util.Objects;
import java.nio.file.Files;
import java.nio.file.FileVisitOption;
import java.nio.file.FileSystems;
import java.io.File;
import java.nio.file.Paths;
import java.util.Iterator;
import java.util.List;
import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.io.InputStream;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.io.PrintStream;
import org.jline.terminal.Terminal;

public class Commands
{
    public static void tmux(final Terminal terminal, final PrintStream out, final PrintStream err, final Supplier<Object> getter, final Consumer<Object> setter, final Consumer<Terminal> runner, final String[] argv) throws Exception {
        final String[] usage = { "tmux -  terminal multiplexer", "Usage: tmux [command]", "  -? --help                    Show help" };
        final Options opt = Options.compile(usage).parse(argv);
        if (opt.isSet("help")) {
            throw new Options.HelpException(opt.usage());
        }
        if (argv.length == 0) {
            final Object instance = getter.get();
            if (instance != null) {
                err.println("tmux: can't run tmux inside itself");
            }
            else {
                final Tmux tmux = new Tmux(terminal, err, runner);
                setter.accept(tmux);
                try {
                    tmux.run();
                }
                finally {
                    setter.accept(null);
                }
            }
        }
        else {
            final Object instance = getter.get();
            if (instance != null) {
                ((Tmux)instance).execute(out, err, Arrays.asList(argv));
            }
            else {
                err.println("tmux: no instance running");
            }
        }
    }
    
    public static void nano(final Terminal terminal, final PrintStream out, final PrintStream err, final Path currentDir, final String[] argv) throws Exception {
        nano(terminal, out, err, currentDir, argv, null);
    }
    
    public static void nano(final Terminal terminal, final PrintStream out, final PrintStream err, final Path currentDir, final String[] argv, final ConfigurationPath configPath) throws Exception {
        final Options opt = Options.compile(Nano.usage()).parse(argv);
        if (opt.isSet("help")) {
            throw new Options.HelpException(opt.usage());
        }
        final Nano edit = new Nano(terminal, currentDir, opt, configPath);
        edit.open(opt.args());
        edit.run();
    }
    
    public static void less(final Terminal terminal, final InputStream in, final PrintStream out, final PrintStream err, final Path currentDir, final Object[] argv) throws Exception {
        less(terminal, in, out, err, currentDir, argv, null);
    }
    
    public static void less(final Terminal terminal, final InputStream in, final PrintStream out, final PrintStream err, final Path currentDir, final Object[] argv, final ConfigurationPath configPath) throws Exception {
        final Options opt = Options.compile(Less.usage()).parse(argv);
        if (opt.isSet("help")) {
            throw new Options.HelpException(opt.usage());
        }
        final Less less = new Less(terminal, currentDir, opt, configPath);
        final List<Source> sources = new ArrayList<Source>();
        if (opt.argObjects().isEmpty()) {
            opt.argObjects().add("-");
        }
        for (final Object o : opt.argObjects()) {
            if (o instanceof String) {
                String arg = (String)o;
                arg = (arg.startsWith("~") ? arg.replace("~", System.getProperty("user.home")) : arg);
                if ("-".equals(arg)) {
                    sources.add(new Source.StdInSource(in));
                }
                else if (arg.contains("*") || arg.contains("?")) {
                    for (final Path p : findFiles(currentDir, arg)) {
                        sources.add(new Source.URLSource(p.toUri().toURL(), p.toString()));
                    }
                }
                else {
                    sources.add(new Source.URLSource(currentDir.resolve(arg).toUri().toURL(), arg));
                }
            }
            else if (o instanceof Source) {
                sources.add((Source)o);
            }
            else {
                ByteArrayInputStream bais = null;
                if (o instanceof String[]) {
                    bais = new ByteArrayInputStream(String.join("\n", (CharSequence[])o).getBytes());
                }
                else if (o instanceof ByteArrayInputStream) {
                    bais = (ByteArrayInputStream)o;
                }
                else if (o instanceof byte[]) {
                    bais = new ByteArrayInputStream((byte[])o);
                }
                if (bais == null) {
                    continue;
                }
                sources.add(new Source.InputStreamSource(bais, true, "Less"));
            }
        }
        less.run(sources);
    }
    
    protected static List<Path> findFiles(final Path root, String files) throws IOException {
        String regex;
        files = (regex = (files.startsWith("~") ? files.replace("~", System.getProperty("user.home")) : files));
        Path searchRoot = Paths.get("/", new String[0]);
        if (new File(files).isAbsolute()) {
            regex = regex.replaceAll("\\\\", "/").replaceAll("//", "/");
            if (regex.contains("/")) {
                String sr;
                for (sr = regex.substring(0, regex.lastIndexOf("/") + 1); sr.contains("*") || sr.contains("?"); sr = sr.substring(0, sr.lastIndexOf("/"))) {}
                searchRoot = Paths.get(sr + "/", new String[0]);
            }
        }
        else {
            regex = ((root.toString().length() == 0) ? "" : (root + "/")) + files;
            regex = regex.replaceAll("\\\\", "/").replaceAll("//", "/");
            searchRoot = root;
        }
        final PathMatcher pathMatcher = FileSystems.getDefault().getPathMatcher("glob:" + regex);
        try (final Stream<Path> pathStream = Files.walk(searchRoot, new FileVisitOption[0])) {
            final Stream<Path> stream = pathStream;
            final PathMatcher obj = pathMatcher;
            Objects.requireNonNull(obj);
            final List<? super Path> list = stream.filter(obj::matches).collect((Collector<? super Path, ?, List<? super Path>>)Collectors.toList());
            return (List<Path>)list;
        }
    }
    
    public static void history(final LineReader reader, final PrintStream out, final PrintStream err, final Path currentDir, final String[] argv) throws Exception {
        final String[] usage = { "history -  list history of commands", "Usage: history [-dnrfEie] [-m match] [first] [last]", "       history -ARWI [filename]", "       history -s [old=new] [command]", "       history --clear", "       history --save", "  -? --help                       Displays command help", "     --clear                      Clear history", "     --save                       Save history", "  -m match                        If option -m is present the first argument is taken as a pattern", "                                  and only the history events matching the pattern will be shown", "  -d                              Print timestamps for each event", "  -f                              Print full time date stamps in the US format", "  -E                              Print full time date stamps in the European format", "  -i                              Print full time date stamps in ISO8601 format", "  -n                              Suppresses command numbers", "  -r                              Reverses the order of the commands", "  -A                              Appends the history out to the given file", "  -R                              Reads the history from the given file", "  -W                              Writes the history out to the given file", "  -I                              If added to -R, only the events that are not contained within the internal list are added", "                                  If added to -W or -A, only the events that are new since the last incremental operation", "                                  to the file are added", "  [first] [last]                  These optional arguments may be specified as a number or as a string. A negative number", "                                  is used as an offset to the current history event number. A string specifies the most", "                                  recent event beginning with the given string.", "  -e                              Uses the nano editor to edit the commands before executing", "  -s                              Re-executes the command without invoking an editor" };
        final Options opt = Options.compile(usage).parse(argv);
        if (opt.isSet("help")) {
            throw new Options.HelpException(opt.usage());
        }
        final History history = reader.getHistory();
        boolean done = true;
        final boolean increment = opt.isSet("I");
        if (opt.isSet("clear")) {
            history.purge();
        }
        else if (opt.isSet("save")) {
            history.save();
        }
        else if (opt.isSet("A")) {
            final Path file = opt.args().isEmpty() ? null : currentDir.resolve(opt.args().get(0));
            history.append(file, increment);
        }
        else if (opt.isSet("R")) {
            final Path file = opt.args().isEmpty() ? null : currentDir.resolve(opt.args().get(0));
            history.read(file, increment);
        }
        else if (opt.isSet("W")) {
            final Path file = opt.args().isEmpty() ? null : currentDir.resolve(opt.args().get(0));
            history.write(file, increment);
        }
        else {
            done = false;
        }
        if (done) {
            return;
        }
        final ReExecute execute = new ReExecute(history, opt);
        int argId = execute.getArgId();
        Pattern pattern = null;
        if (opt.isSet("m") && opt.args().size() > argId) {
            final StringBuilder sb = new StringBuilder();
            char prev = '0';
            for (final char c : opt.args().get(argId++).toCharArray()) {
                if (c == '*' && prev != '\\' && prev != '.') {
                    sb.append('.');
                }
                sb.append(c);
                prev = c;
            }
            pattern = Pattern.compile(sb.toString(), 32);
        }
        boolean reverse = opt.isSet("r") || (opt.isSet("s") && opt.args().size() <= argId);
        int firstId = (opt.args().size() > argId) ? retrieveHistoryId(history, opt.args().get(argId++)) : -17;
        int lastId = (opt.args().size() > argId) ? retrieveHistoryId(history, opt.args().get(argId++)) : -1;
        firstId = historyId(firstId, history.first(), history.last());
        lastId = historyId(lastId, history.first(), history.last());
        if (firstId > lastId) {
            final int tmpId = firstId;
            firstId = lastId;
            lastId = tmpId;
            reverse = !reverse;
        }
        final int tot = lastId - firstId + 1;
        int listed = 0;
        final Highlighter highlighter = reader.getHighlighter();
        Iterator<History.Entry> iter = null;
        if (reverse) {
            iter = history.reverseIterator(lastId);
        }
        else {
            iter = history.iterator(firstId);
        }
        while (iter.hasNext() && listed < tot) {
            final History.Entry entry = iter.next();
            ++listed;
            if (pattern != null && !pattern.matcher(entry.line()).matches()) {
                continue;
            }
            if (execute.isExecute()) {
                if (!execute.isEdit()) {
                    execute.addCommandInBuffer(reader, entry.line());
                    break;
                }
                execute.addCommandInFile(entry.line());
            }
            else {
                final AttributedStringBuilder sb2 = new AttributedStringBuilder();
                if (!opt.isSet("n")) {
                    sb2.append("  ");
                    sb2.styled(AttributedStyle::bold, String.format("%3d", entry.index()));
                }
                if (opt.isSet("d") || opt.isSet("f") || opt.isSet("E") || opt.isSet("i")) {
                    sb2.append("  ");
                    if (opt.isSet("d")) {
                        final LocalTime lt = LocalTime.from(entry.time().atZone(ZoneId.systemDefault())).truncatedTo(ChronoUnit.SECONDS);
                        DateTimeFormatter.ISO_LOCAL_TIME.formatTo(lt, sb2);
                    }
                    else {
                        final LocalDateTime lt2 = LocalDateTime.from((TemporalAccessor)entry.time().atZone(ZoneId.systemDefault()).truncatedTo(ChronoUnit.MINUTES));
                        String format = "yyyy-MM-dd hh:mm";
                        if (opt.isSet("f")) {
                            format = "MM/dd/yy hh:mm";
                        }
                        else if (opt.isSet("E")) {
                            format = "dd.MM.yyyy hh:mm";
                        }
                        DateTimeFormatter.ofPattern(format).formatTo(lt2, sb2);
                    }
                }
                sb2.append("  ");
                sb2.append(highlighter.highlight(reader, entry.line()));
                out.println(sb2.toAnsi(reader.getTerminal()));
            }
        }
        execute.editCommandsAndClose(reader);
    }
    
    private static int historyId(final int id, final int minId, final int maxId) {
        int out = id;
        if (id < 0) {
            out = maxId + id + 1;
        }
        if (out < minId) {
            out = minId;
        }
        else if (out > maxId) {
            out = maxId;
        }
        return out;
    }
    
    private static int retrieveHistoryId(final History history, final String s) throws IllegalArgumentException {
        try {
            return Integer.parseInt(s);
        }
        catch (final NumberFormatException ex) {
            for (final History.Entry entry : history) {
                if (entry.line().startsWith(s)) {
                    return entry.index();
                }
            }
            throw new IllegalArgumentException("history: event not found: " + s);
        }
    }
    
    public static void complete(final LineReader reader, final PrintStream out, final PrintStream err, final Map<String, List<Completers.CompletionData>> completions, final String[] argv) throws Options.HelpException {
        final String[] usage = { "complete -  edit command specific tab-completions", "Usage: complete", "  -? --help                       Displays command help", "  -c --command=COMMAND            Command to add completion to", "  -d --description=DESCRIPTION    Description of this completions", "  -e --erase                      Erase the completions", "  -s --short-option=SHORT_OPTION  Posix-style option to complete", "  -l --long-option=LONG_OPTION    GNU-style option to complete", "  -a --argument=ARGUMENTS         A list of possible arguments", "  -n --condition=CONDITION        The completion should only be used if the", "                                  specified command has a zero exit status" };
        final Options opt = Options.compile(usage).parse(argv);
        if (opt.isSet("help")) {
            throw new Options.HelpException(opt.usage());
        }
        final String command = opt.get("command");
        if (opt.isSet("erase")) {
            completions.remove(command);
            return;
        }
        final List<Completers.CompletionData> cmdCompletions = completions.computeIfAbsent(command, s -> new ArrayList());
        List<String> options = null;
        if (opt.isSet("short-option")) {
            for (final String op : opt.getList("short-option")) {
                if (options == null) {
                    options = new ArrayList<String>();
                }
                options.add("-" + op);
            }
        }
        if (opt.isSet("long-option")) {
            for (final String op : opt.getList("long-option")) {
                if (options == null) {
                    options = new ArrayList<String>();
                }
                options.add("--" + op);
            }
        }
        final String description = opt.isSet("description") ? opt.get("description") : null;
        final String argument = opt.isSet("argument") ? opt.get("argument") : null;
        final String condition = opt.isSet("condition") ? opt.get("condition") : null;
        cmdCompletions.add(new Completers.CompletionData(options, description, argument, condition));
    }
    
    public static void widget(final LineReader reader, final PrintStream out, final PrintStream err, final Function<String, Widget> widgetCreator, final String[] argv) throws Exception {
        final String[] usage = { "widget -  manipulate widgets", "Usage: widget -N new-widget [function-name]", "       widget -D widget ...", "       widget -A old-widget new-widget", "       widget -U string ...", "       widget -l [options]", "  -? --help                       Displays command help", "  -A                              Create alias to widget", "  -N                              Create new widget", "  -D                              Delete widgets", "  -U                              Push characters to the stack", "  -l                              List user-defined widgets", "  -a                              With -l, list all widgets" };
        final Options opt = Options.compile(usage).parse(argv);
        if (opt.isSet("help")) {
            throw new Options.HelpException(opt.usage());
        }
        final int actions = (opt.isSet("N") + opt.isSet("D") + opt.isSet("U") + opt.isSet("l") + opt.isSet("A")) ? 1 : 0;
        if (actions > 1) {
            err.println("widget: incompatible operation selection options");
            return;
        }
        if (opt.isSet("l")) {
            final TreeSet<String> ws = new TreeSet<String>(reader.getWidgets().keySet());
            if (opt.isSet("a")) {
                final Set<String> temp = new HashSet<String>(ws);
                for (final String s : temp) {
                    ws.add(reader.getWidgets().get(s).toString());
                }
            }
            for (final String s2 : ws) {
                if (opt.isSet("a")) {
                    out.println(s2);
                }
                else {
                    if (reader.getWidgets().get(s2).toString().startsWith(".")) {
                        continue;
                    }
                    out.println(s2 + " (" + reader.getWidgets().get(s2) + ")");
                }
            }
        }
        else if (opt.isSet("N")) {
            if (opt.args().size() < 1) {
                err.println("widget: not enough arguments for -N");
                return;
            }
            if (opt.args().size() > 2) {
                err.println("widget: too many arguments for -N");
                return;
            }
            final String name = opt.args().get(0);
            final String func = (opt.args().size() == 2) ? opt.args().get(1) : name;
            reader.getWidgets().put(name, widgetCreator.apply(func));
        }
        else if (opt.isSet("D")) {
            for (final String name2 : opt.args()) {
                reader.getWidgets().remove(name2);
            }
        }
        else if (opt.isSet("A")) {
            if (opt.args().size() < 2) {
                err.println("widget: not enough arguments for -A");
                return;
            }
            if (opt.args().size() > 2) {
                err.println("widget: too many arguments for -A");
                return;
            }
            Widget org = null;
            if (opt.args().get(0).startsWith(".")) {
                org = reader.getBuiltinWidgets().get(opt.args().get(0).substring(1));
            }
            else {
                org = reader.getWidgets().get(opt.args().get(0));
            }
            if (org == null) {
                err.println("widget: no such widget `" + opt.args().get(0) + "'");
                return;
            }
            reader.getWidgets().put(opt.args().get(1), org);
        }
        else if (opt.isSet("U")) {
            for (final String arg : opt.args()) {
                reader.runMacro(KeyMap.translate(arg));
            }
        }
        else if (opt.args().size() == 1) {
            reader.callWidget(opt.args().get(0));
        }
    }
    
    public static void keymap(final LineReader reader, final PrintStream out, final PrintStream err, final String[] argv) throws Options.HelpException {
        final String[] usage = { "keymap -  manipulate keymaps", "Usage: keymap [options] -l [-L] [keymap ...]", "       keymap [options] -d", "       keymap [options] -D keymap ...", "       keymap [options] -A old-keymap new-keymap", "       keymap [options] -N new-keymap [old-keymap]", "       keymap [options] -M", "       keymap [options] -r in-string ...", "       keymap [options] -s in-string out-string ...", "       keymap [options] in-string command ...", "       keymap [options] [in-string]", "  -? --help                       Displays command help", "  -A                              Create alias to keymap", "  -D                              Delete named keymaps", "  -L                              Output in form of keymap commands", "  -M (default=main)               Specify keymap to select", "  -N                              Create new keymap", "  -R                              Interpret in-strings as ranges", "  -a                              Select vicmd keymap", "  -d                              Delete existing keymaps and reset to default state", "  -e                              Select emacs keymap and bind it to main", "  -l                              List existing keymap names", "  -p                              List bindings which have given key sequence as a a prefix", "  -r                              Unbind specified in-strings ", "  -s                              Bind each in-string to each out-string ", "  -v                              Select viins keymap and bind it to main" };
        final Options opt = Options.compile(usage).parse(argv);
        if (opt.isSet("help")) {
            throw new Options.HelpException(opt.usage());
        }
        final Map<String, KeyMap<Binding>> keyMaps = reader.getKeyMaps();
        final int actions = (opt.isSet("N") + opt.isSet("d") + opt.isSet("D") + opt.isSet("l") + opt.isSet("r") + opt.isSet("s") + opt.isSet("A")) ? 1 : 0;
        if (actions > 1) {
            err.println("keymap: incompatible operation selection options");
            return;
        }
        if (opt.isSet("l")) {
            final boolean commands = opt.isSet("L");
            if (!opt.args().isEmpty()) {
                for (final String arg : opt.args()) {
                    final KeyMap<Binding> map = keyMaps.get(arg);
                    if (map == null) {
                        err.println("keymap: no such keymap: `" + arg + "'");
                    }
                    else {
                        out.println(arg);
                    }
                }
            }
            else {
                final Set<String> keySet = keyMaps.keySet();
                Objects.requireNonNull(out);
                keySet.forEach(out::println);
            }
        }
        else if (opt.isSet("N")) {
            if (opt.isSet("e") || opt.isSet("v") || opt.isSet("a") || opt.isSet("M")) {
                err.println("keymap: keymap can not be selected with -N");
                return;
            }
            if (opt.args().size() < 1) {
                err.println("keymap: not enough arguments for -N");
                return;
            }
            if (opt.args().size() > 2) {
                err.println("keymap: too many arguments for -N");
                return;
            }
            KeyMap<Binding> org = null;
            if (opt.args().size() == 2) {
                org = keyMaps.get(opt.args().get(1));
                if (org == null) {
                    err.println("keymap: no such keymap `" + opt.args().get(1) + "'");
                    return;
                }
            }
            final KeyMap<Binding> map2 = new KeyMap<Binding>();
            if (org != null) {
                for (final Map.Entry<String, Binding> bound : org.getBoundKeys().entrySet()) {
                    map2.bind(bound.getValue(), bound.getKey());
                }
            }
            keyMaps.put(opt.args().get(0), map2);
        }
        else if (opt.isSet("A")) {
            if (opt.isSet("e") || opt.isSet("v") || opt.isSet("a") || opt.isSet("M")) {
                err.println("keymap: keymap can not be selected with -N");
                return;
            }
            if (opt.args().size() < 2) {
                err.println("keymap: not enough arguments for -A");
                return;
            }
            if (opt.args().size() > 2) {
                err.println("keymap: too many arguments for -A");
                return;
            }
            final KeyMap<Binding> org = keyMaps.get(opt.args().get(0));
            if (org == null) {
                err.println("keymap: no such keymap `" + opt.args().get(0) + "'");
                return;
            }
            keyMaps.put(opt.args().get(1), org);
        }
        else if (opt.isSet("d")) {
            if (opt.isSet("e") || opt.isSet("v") || opt.isSet("a") || opt.isSet("M")) {
                err.println("keymap: keymap can not be selected with -N");
                return;
            }
            if (!opt.args().isEmpty()) {
                err.println("keymap: too many arguments for -d");
                return;
            }
            keyMaps.clear();
            keyMaps.putAll(reader.defaultKeyMaps());
        }
        else if (opt.isSet("D")) {
            if (opt.isSet("e") || opt.isSet("v") || opt.isSet("a") || opt.isSet("M")) {
                err.println("keymap: keymap can not be selected with -N");
                return;
            }
            if (opt.args().size() < 1) {
                err.println("keymap: not enough arguments for -A");
                return;
            }
            for (final String name : opt.args()) {
                if (keyMaps.remove(name) == null) {
                    err.println("keymap: no such keymap `" + name + "'");
                }
            }
        }
        else if (opt.isSet("r")) {
            String keyMapName = "main";
            final int sel = (opt.isSet("a") + opt.isSet("e") + opt.isSet("v") + opt.isSet("M")) ? 1 : 0;
            if (sel > 1) {
                err.println("keymap: incompatible keymap selection options");
                return;
            }
            if (opt.isSet("a")) {
                keyMapName = "vicmd";
            }
            else if (opt.isSet("e")) {
                keyMapName = "emacs";
            }
            else if (opt.isSet("v")) {
                keyMapName = "viins";
            }
            else if (opt.isSet("M")) {
                if (opt.args().isEmpty()) {
                    err.println("keymap: argument expected: -M");
                    return;
                }
                keyMapName = opt.args().remove(0);
            }
            final KeyMap<Binding> map3 = keyMaps.get(keyMapName);
            if (map3 == null) {
                err.println("keymap: no such keymap `" + keyMapName + "'");
                return;
            }
            final boolean range = opt.isSet("R");
            final boolean prefix = opt.isSet("p");
            final Set<String> toRemove = new HashSet<String>();
            final Map<String, Binding> bound2 = map3.getBoundKeys();
            for (final String arg2 : opt.args()) {
                if (range) {
                    final Collection<String> r = KeyMap.range(opt.args().get(0));
                    if (r == null) {
                        err.println("keymap: malformed key range `" + opt.args().get(0) + "'");
                        return;
                    }
                    toRemove.addAll(r);
                }
                else {
                    final String seq = KeyMap.translate(arg2);
                    for (final String k : bound2.keySet()) {
                        if ((prefix && k.startsWith(seq) && k.length() > seq.length()) || (!prefix && k.equals(seq))) {
                            toRemove.add(k);
                        }
                    }
                }
            }
            for (final String seq2 : toRemove) {
                map3.unbind(seq2);
            }
            if (opt.isSet("e") || opt.isSet("v")) {
                keyMaps.put("main", map3);
            }
        }
        else if (opt.isSet("s") || opt.args().size() > 1) {
            String keyMapName = "main";
            final int sel = (opt.isSet("a") + opt.isSet("e") + opt.isSet("v") + opt.isSet("M")) ? 1 : 0;
            if (sel > 1) {
                err.println("keymap: incompatible keymap selection options");
                return;
            }
            if (opt.isSet("a")) {
                keyMapName = "vicmd";
            }
            else if (opt.isSet("e")) {
                keyMapName = "emacs";
            }
            else if (opt.isSet("v")) {
                keyMapName = "viins";
            }
            else if (opt.isSet("M")) {
                if (opt.args().isEmpty()) {
                    err.println("keymap: argument expected: -M");
                    return;
                }
                keyMapName = opt.args().remove(0);
            }
            final KeyMap<Binding> map3 = keyMaps.get(keyMapName);
            if (map3 == null) {
                err.println("keymap: no such keymap `" + keyMapName + "'");
                return;
            }
            final boolean range = opt.isSet("R");
            if (opt.args().size() % 2 == 1) {
                err.println("keymap: even number of arguments required");
                return;
            }
            for (int i = 0; i < opt.args().size(); i += 2) {
                final Binding bout = opt.isSet("s") ? new Macro(KeyMap.translate(opt.args().get(i + 1))) : new Reference(opt.args().get(i + 1));
                if (range) {
                    final Collection<String> r2 = KeyMap.range(opt.args().get(i));
                    if (r2 == null) {
                        err.println("keymap: malformed key range `" + opt.args().get(i) + "'");
                        return;
                    }
                    map3.bind(bout, r2);
                }
                else {
                    final String in = KeyMap.translate(opt.args().get(i));
                    map3.bind(bout, in);
                }
            }
            if (opt.isSet("e") || opt.isSet("v")) {
                keyMaps.put("main", map3);
            }
        }
        else {
            String keyMapName = "main";
            final int sel = (opt.isSet("a") + opt.isSet("e") + opt.isSet("v") + opt.isSet("M")) ? 1 : 0;
            if (sel > 1) {
                err.println("keymap: incompatible keymap selection options");
                return;
            }
            if (opt.isSet("a")) {
                keyMapName = "vicmd";
            }
            else if (opt.isSet("e")) {
                keyMapName = "emacs";
            }
            else if (opt.isSet("v")) {
                keyMapName = "viins";
            }
            else if (opt.isSet("M")) {
                if (opt.args().isEmpty()) {
                    err.println("keymap: argument expected: -M");
                    return;
                }
                keyMapName = opt.args().remove(0);
            }
            final KeyMap<Binding> map3 = keyMaps.get(keyMapName);
            if (map3 == null) {
                err.println("keymap: no such keymap `" + keyMapName + "'");
                return;
            }
            final boolean prefix2 = opt.isSet("p");
            final boolean commands2 = opt.isSet("L");
            if (prefix2 && opt.args().isEmpty()) {
                err.println("keymap: option -p requires a prefix string");
                return;
            }
            if (!opt.args().isEmpty() || (!opt.isSet("e") && !opt.isSet("v"))) {
                final Map<String, Binding> bound3 = map3.getBoundKeys();
                final String seq3 = opt.args().isEmpty() ? null : KeyMap.translate(opt.args().get(0));
                Map.Entry<String, Binding> begin = null;
                String last = null;
                final Iterator<Map.Entry<String, Binding>> iterator = bound3.entrySet().iterator();
                while (iterator.hasNext()) {
                    final Map.Entry<String, Binding> entry = iterator.next();
                    final String key = entry.getKey();
                    if (seq3 == null || (prefix2 && key.startsWith(seq3) && !key.equals(seq3)) || (!prefix2 && key.equals(seq3))) {
                        if (begin != null || !iterator.hasNext()) {
                            final String n = ((last.length() > 1) ? last.substring(0, last.length() - 1) : "") + (char)(last.charAt(last.length() - 1) + '\u0001');
                            if (key.equals(n) && entry.getValue().equals(begin.getValue())) {
                                last = key;
                            }
                            else {
                                final StringBuilder sb = new StringBuilder();
                                if (commands2) {
                                    sb.append("keymap -M ");
                                    sb.append(keyMapName);
                                    sb.append(" ");
                                }
                                if (begin.getKey().equals(last)) {
                                    sb.append(KeyMap.display(last));
                                    sb.append(" ");
                                    displayValue(sb, begin.getValue());
                                    out.println(sb);
                                }
                                else {
                                    if (commands2) {
                                        sb.append("-R ");
                                    }
                                    sb.append(KeyMap.display(begin.getKey()));
                                    sb.append("-");
                                    sb.append(KeyMap.display(last));
                                    sb.append(" ");
                                    displayValue(sb, begin.getValue());
                                    out.println(sb);
                                }
                                begin = entry;
                                last = key;
                            }
                        }
                        else {
                            begin = entry;
                            last = key;
                        }
                    }
                }
            }
            if (opt.isSet("e") || opt.isSet("v")) {
                keyMaps.put("main", map3);
            }
        }
    }
    
    public static void setopt(final LineReader reader, final PrintStream out, final PrintStream err, final String[] argv) throws Options.HelpException {
        final String[] usage = { "setopt -  set options", "Usage: setopt [-m] option ...", "       setopt", "  -? --help                       Displays command help", "  -m                              Use pattern matching" };
        final Options opt = Options.compile(usage).parse(argv);
        if (opt.isSet("help")) {
            throw new Options.HelpException(opt.usage());
        }
        if (opt.args().isEmpty()) {
            for (final LineReader.Option option : LineReader.Option.values()) {
                if (reader.isSet(option) != option.isDef()) {
                    out.println((option.isDef() ? "no-" : "") + option.toString().toLowerCase().replace('_', '-'));
                }
            }
        }
        else {
            final boolean match = opt.isSet("m");
            doSetOpts(reader, out, err, opt.args(), match, true);
        }
    }
    
    public static void unsetopt(final LineReader reader, final PrintStream out, final PrintStream err, final String[] argv) throws Options.HelpException {
        final String[] usage = { "unsetopt -  unset options", "Usage: unsetopt [-m] option ...", "       unsetopt", "  -? --help                       Displays command help", "  -m                              Use pattern matching" };
        final Options opt = Options.compile(usage).parse(argv);
        if (opt.isSet("help")) {
            throw new Options.HelpException(opt.usage());
        }
        if (opt.args().isEmpty()) {
            for (final LineReader.Option option : LineReader.Option.values()) {
                if (reader.isSet(option) == option.isDef()) {
                    out.println((option.isDef() ? "no-" : "") + option.toString().toLowerCase().replace('_', '-'));
                }
            }
        }
        else {
            final boolean match = opt.isSet("m");
            doSetOpts(reader, out, err, opt.args(), match, false);
        }
    }
    
    private static void doSetOpts(final LineReader reader, final PrintStream out, final PrintStream err, final List<String> options, final boolean match, final boolean set) {
        for (final String name : options) {
            String tname = name.toLowerCase().replaceAll("[-_]", "");
            if (match) {
                tname = tname.replaceAll("\\*", "[a-z]*");
                tname = tname.replaceAll("\\?", "[a-z]");
            }
            boolean found = false;
            for (final LineReader.Option option : LineReader.Option.values()) {
                final String optName = option.name().toLowerCase().replaceAll("[-_]", "");
                Label_0290: {
                    Label_0189: {
                        if (match) {
                            if (!optName.matches(tname)) {
                                break Label_0189;
                            }
                        }
                        else if (!optName.equals(tname)) {
                            break Label_0189;
                        }
                        if (set) {
                            reader.setOpt(option);
                        }
                        else {
                            reader.unsetOpt(option);
                        }
                        found = true;
                        if (!match) {
                            break;
                        }
                        break Label_0290;
                    }
                    if (match) {
                        if (!("no" + optName).matches(tname)) {
                            break Label_0290;
                        }
                    }
                    else if (!("no" + optName).equals(tname)) {
                        break Label_0290;
                    }
                    if (set) {
                        reader.unsetOpt(option);
                    }
                    else {
                        reader.setOpt(option);
                    }
                    if (!match) {
                        found = true;
                        break;
                    }
                    break;
                }
            }
            if (!found) {
                err.println("No matching option: " + name);
            }
        }
    }
    
    private static void displayValue(final StringBuilder sb, final Object value) {
        if (value == null) {
            sb.append("undefined-key");
        }
        else if (value instanceof Macro) {
            sb.append(KeyMap.display(((Macro)value).getSequence()));
        }
        else if (value instanceof Reference) {
            sb.append(((Reference)value).name());
        }
        else {
            sb.append(value);
        }
    }
    
    public static void setvar(final LineReader lineReader, final PrintStream out, final PrintStream err, final String[] argv) throws Options.HelpException {
        final String[] usage = { "setvar -  set lineReader variable value", "Usage: setvar [variable] [value]", "  -? --help                    Show help" };
        final Options opt = Options.compile(usage).parse(argv);
        if (opt.isSet("help")) {
            throw new Options.HelpException(opt.usage());
        }
        if (opt.args().isEmpty()) {
            for (final Map.Entry<String, Object> entry : lineReader.getVariables().entrySet()) {
                out.println(entry.getKey() + ": " + entry.getValue());
            }
        }
        else if (opt.args().size() == 1) {
            out.println(lineReader.getVariable(opt.args().get(0)));
        }
        else {
            lineReader.setVariable(opt.args().get(0), opt.args().get(1));
        }
    }
    
    public static void colors(final Terminal terminal, final PrintStream out, final String[] argv) throws Options.HelpException, IOException {
        final String[] usage = { "colors -  view 256-color table and ANSI-styles", "Usage: colors [OPTIONS]", "  -? --help                     Displays command help", "  -a --ansistyles               List ANSI-styles", "  -c --columns=COLUMNS          Number of columns in name/rgb table", "                                COLUMNS = 1, display columns: color, style, ansi and HSL", "  -f --find=NAME                Find color names which contains NAME ", "  -l --lock=STYLE               Lock fore- or background color", "  -n --name                     Color name table (default number table)", "  -r --rgb                      Use and display rgb value", "  -s --small                    View 16-color table (default 256-color)", "  -v --view=COLOR               View 24bit color table of COLOR ", "                                COLOR = <colorName>, <color24bit> or hue<angle>" };
        final Options opt = Options.compile(usage).parse(argv);
        if (opt.isSet("help")) {
            throw new Options.HelpException(opt.usage());
        }
        final Colors colors = new Colors(terminal, out);
        if (opt.isSet("ansistyles")) {
            colors.printStyles();
        }
        else {
            String style = null;
            if (opt.isSet("lock")) {
                style = opt.get("lock");
                if (style.length() - style.replace(":", "").length() > 1) {
                    style = null;
                }
            }
            if (!opt.isSet("view")) {
                final boolean rgb = opt.isSet("rgb");
                int columns = (terminal.getWidth() > (rgb ? 71 : 122)) ? 6 : 5;
                String findName = null;
                boolean nameTable = opt.isSet("name");
                boolean table16 = opt.isSet("small");
                if (opt.isSet("find")) {
                    findName = opt.get("find").toLowerCase();
                    nameTable = true;
                    table16 = false;
                    columns = 4;
                }
                if (table16) {
                    columns += 2;
                }
                if (opt.isSet("columns")) {
                    columns = opt.getNumber("columns");
                }
                colors.printColors(nameTable, rgb, table16, columns, findName, style);
            }
            else {
                colors.printColor(opt.get("view").toLowerCase(), style);
            }
        }
    }
    
    public static void highlighter(final LineReader lineReader, final Terminal terminal, final PrintStream out, final PrintStream err, final String[] argv, final ConfigurationPath configPath) throws Options.HelpException {
        final String[] usage = { "highlighter -  manage nanorc theme system", "Usage: highlighter [OPTIONS]", "  -? --help                       Displays command help", "  -c --columns=COLUMNS            Number of columns in theme view", "  -l --list                       List available nanorc themes", "  -r --refresh                    Refresh highlighter config", "  -s --switch=THEME               Switch nanorc theme", "  -v --view=THEME                 View nanorc theme" };
        final Options opt = Options.compile(usage).parse(argv);
        if (opt.isSet("help")) {
            throw new Options.HelpException(opt.usage());
        }
        try {
            if (opt.isSet("refresh")) {
                lineReader.getHighlighter().refresh(lineReader);
            }
            else if (opt.isSet("switch")) {
                final Path userConfig = configPath.getUserConfig("jnanorc");
                if (userConfig != null) {
                    final SyntaxHighlighter sh = SyntaxHighlighter.build(userConfig, null);
                    final Path currentTheme = sh.getCurrentTheme();
                    final String newTheme = replaceFileName(currentTheme, opt.get("switch"));
                    final File themeFile = new File(newTheme);
                    if (themeFile.exists()) {
                        switchTheme(err, userConfig, newTheme);
                        final Path lessConfig = configPath.getUserConfig("jlessrc");
                        if (lessConfig != null) {
                            switchTheme(err, lessConfig, newTheme);
                        }
                        lineReader.getHighlighter().refresh(lineReader);
                    }
                }
            }
            else {
                final Path config = configPath.getConfig("jnanorc");
                final Path currentTheme2 = (config != null) ? SyntaxHighlighter.build(config, null).getCurrentTheme() : null;
                if (currentTheme2 != null) {
                    if (opt.isSet("list")) {
                        final String parameter = replaceFileName(currentTheme2, "*.nanorctheme");
                        out.println(currentTheme2.getParent() + ":");
                        final PathMatcher pathMatcher = FileSystems.getDefault().getPathMatcher("glob:" + parameter);
                        try (final Stream<Path> pathStream = Files.walk(Paths.get(new File(parameter).getParent(), new String[0]), new FileVisitOption[0])) {
                            final Stream<Path> stream = pathStream;
                            final PathMatcher obj = pathMatcher;
                            Objects.requireNonNull(obj);
                            stream.filter(obj::matches).forEach(p -> out.println(p.getFileName()));
                        }
                    }
                    else {
                        Path themeFile2;
                        if (opt.isSet("view")) {
                            themeFile2 = Paths.get(replaceFileName(currentTheme2, opt.get("view")), new String[0]);
                        }
                        else {
                            themeFile2 = currentTheme2;
                        }
                        out.println(themeFile2.toAbsolutePath());
                        try (final BufferedReader reader = Files.newBufferedReader(themeFile2)) {
                            final List<List<String>> tokens = new ArrayList<List<String>>();
                            int maxKeyLen = 0;
                            int maxValueLen = 0;
                            String line;
                            while ((line = reader.readLine()) != null) {
                                line = line.trim();
                                if (!line.isEmpty() && !line.startsWith("#")) {
                                    final List<String> parts = Arrays.asList(line.split("\\s+", 2));
                                    if (!parts.get(0).matches("[A-Z_]+")) {
                                        continue;
                                    }
                                    if (parts.get(0).length() > maxKeyLen) {
                                        maxKeyLen = parts.get(0).length();
                                    }
                                    if (parts.get(1).length() > maxValueLen) {
                                        maxValueLen = parts.get(1).length();
                                    }
                                    tokens.add(parts);
                                }
                            }
                            final AttributedStringBuilder asb = new AttributedStringBuilder();
                            maxKeyLen += 2;
                            ++maxValueLen;
                            final int cols = opt.isSet("columns") ? opt.getNumber("columns") : 2;
                            final List<Integer> tabstops = new ArrayList<Integer>();
                            for (int c = 0; c < cols; ++c) {
                                tabstops.add((c + 1) * maxKeyLen + c * maxValueLen);
                                tabstops.add((c + 1) * maxKeyLen + (c + 1) * maxValueLen);
                            }
                            asb.tabs(tabstops);
                            int ind = 0;
                            for (final List<String> token : tokens) {
                                asb.style(AttributedStyle.DEFAULT).append(" ");
                                asb.style(compileStyle("token" + ind++, token.get(1)));
                                asb.append(token.get(0)).append("\t");
                                asb.append(token.get(1));
                                asb.style(AttributedStyle.DEFAULT).append("\t");
                                if (ind % cols == 0) {
                                    asb.style(AttributedStyle.DEFAULT).append("\n");
                                }
                            }
                            asb.toAttributedString().println(terminal);
                        }
                    }
                }
            }
        }
        catch (final Exception e) {
            err.println(e.getMessage());
        }
    }
    
    private static void switchTheme(final PrintStream err, final Path config, final String theme) {
        try (final Stream<String> stream = Files.lines(config, StandardCharsets.UTF_8)) {
            final List<String> list = stream.map(line -> {
                String string;
                if (line.matches("\\s*theme\\s+.*")) {
                    string = "theme " + theme;
                }
                else {
                    string = line;
                }
                return string;
            }).collect((Collector<? super Object, ?, List<String>>)Collectors.toList());
            Files.write(config, list, StandardCharsets.UTF_8, new OpenOption[0]);
        }
        catch (final IOException e) {
            err.println(e.getMessage());
        }
    }
    
    private static String replaceFileName(final Path path, final String name) {
        final int nameLength = path.getFileName().toString().length();
        final int pathLength = path.toString().length();
        return (path.toString().substring(0, pathLength - nameLength) + name).replace("\\", "\\\\");
    }
    
    private static AttributedStyle compileStyle(final String reference, final String colorDef) {
        final Map<String, String> spec = new HashMap<String, String>();
        spec.put(reference, colorDef);
        final Styles.StyleCompiler obj;
        final Styles.StyleCompiler sh = obj = new Styles.StyleCompiler(spec, true);
        Objects.requireNonNull(obj);
        return new StyleResolver(obj::getStyle).resolve("." + reference);
    }
    
    private static class ReExecute
    {
        private final boolean execute;
        private final boolean edit;
        private String oldParam;
        private String newParam;
        private FileWriter cmdWriter;
        private File cmdFile;
        private int argId;
        
        public ReExecute(final History history, final Options opt) throws IOException {
            this.argId = 0;
            this.execute = (opt.isSet("e") || opt.isSet("s"));
            this.edit = opt.isSet("e");
            if (this.execute) {
                final Iterator<History.Entry> iter = history.reverseIterator(history.last());
                if (iter.hasNext()) {
                    iter.next();
                    iter.remove();
                }
                if (this.edit) {
                    this.cmdFile = File.createTempFile("jline-history-", null);
                    this.cmdWriter = new FileWriter(this.cmdFile);
                }
                else if (!opt.args().isEmpty()) {
                    final String[] s = opt.args().get(this.argId).split("=");
                    if (s.length == 2) {
                        ++this.argId;
                        this.oldParam = s[0];
                        this.newParam = s[1];
                    }
                }
            }
        }
        
        public int getArgId() {
            return this.argId;
        }
        
        public boolean isEdit() {
            return this.edit;
        }
        
        public boolean isExecute() {
            return this.execute;
        }
        
        public void addCommandInFile(final String command) throws IOException {
            this.cmdWriter.write(command + "\n");
        }
        
        public void addCommandInBuffer(final LineReader reader, final String command) {
            reader.addCommandsInBuffer(Arrays.asList(this.replaceParam(command)));
        }
        
        private String replaceParam(final String command) {
            String out = command;
            if (this.oldParam != null && this.newParam != null) {
                out = command.replaceAll(this.oldParam, this.newParam);
            }
            return out;
        }
        
        public void editCommandsAndClose(final LineReader reader) throws Exception {
            if (this.edit) {
                this.cmdWriter.close();
                try {
                    reader.editAndAddInBuffer(this.cmdFile);
                }
                finally {
                    this.cmdFile.delete();
                }
            }
        }
    }
    
    private static class Colors
    {
        private static final String COLORS_24BIT = "[0-9a-fA-F]{6}";
        private static final List<String> COLORS_16;
        boolean name;
        boolean rgb;
        private final Terminal terminal;
        private final PrintStream out;
        private boolean fixedBg;
        private String fixedStyle;
        int r;
        int g;
        int b;
        
        public Colors(final Terminal terminal, final PrintStream out) {
            this.terminal = terminal;
            this.out = out;
        }
        
        private String getAnsiStyle(final String style) {
            return style;
        }
        
        public void printStyles() {
            final AttributedStringBuilder asb = new AttributedStringBuilder();
            asb.tabs(13);
            for (final String s : Styles.ANSI_STYLES) {
                final AttributedStyle as = new StyleResolver(this::getAnsiStyle).resolve("." + s);
                asb.style(as);
                asb.append(s);
                asb.style(AttributedStyle.DEFAULT);
                asb.append("\t");
                asb.append(this.getAnsiStyle(s));
                asb.append("\t");
                asb.append(as.toAnsi());
                asb.append("\n");
            }
            asb.toAttributedString().println(this.terminal);
        }
        
        private String getStyle(final String color) {
            char fg = ' ';
            String out;
            if (this.name) {
                out = (this.fixedBg ? "fg:" : "bg:") + "~" + color.substring(1);
                fg = color.charAt(0);
            }
            else if (this.rgb) {
                out = (this.fixedBg ? "fg-rgb:" : "bg-rgb:") + "#" + color.substring(1);
                fg = color.charAt(0);
            }
            else if (color.substring(1).matches("\\d+")) {
                out = (this.fixedBg ? "38;5;" : "48;5;") + color.substring(1);
                fg = color.charAt(0);
            }
            else {
                out = (this.fixedBg ? "fg:" : "bg:") + color;
            }
            if (this.fixedStyle == null) {
                if (color.startsWith("!") || color.equals("white") || fg == 'b') {
                    out += ",fg:black";
                }
                else {
                    out += ",fg:!white";
                }
            }
            else {
                out = out + "," + this.fixedStyle;
            }
            return out;
        }
        
        private String foreground(final int idx) {
            String fg = "w";
            if ((idx > 6 && idx < 16) || (idx > 33 && idx < 52) || (idx > 69 && idx < 88) || (idx > 105 && idx < 124) || (idx > 141 && idx < 160) || (idx > 177 && idx < 196) || (idx > 213 && idx < 232) || idx > 243) {
                fg = "b";
            }
            return fg;
        }
        
        private String addPadding(final int width, final String field) {
            final int s = width - field.length();
            final int left = s / 2;
            final StringBuilder lp = new StringBuilder();
            final StringBuilder rp = new StringBuilder();
            for (int i = 0; i < left; ++i) {
                lp.append(" ");
            }
            for (int i = 0; i < s - left; ++i) {
                rp.append(" ");
            }
            return (Object)lp + field + (Object)rp;
        }
        
        private String addLeftPadding(final int width, final String field) {
            final int s = width - field.length();
            final StringBuilder lp = new StringBuilder();
            for (int i = 0; i < s; ++i) {
                lp.append(" ");
            }
            return (Object)lp + field;
        }
        
        private void setFixedStyle(final String style) {
            this.fixedStyle = style;
            if (style != null && (style.contains("b:") || style.contains("b-") || style.contains("bg:") || style.contains("bg-") || style.contains("background"))) {
                this.fixedBg = true;
            }
        }
        
        private List<String> retrieveColorNames() throws IOException {
            List<String> out;
            try (final InputStream is = new Source.ResourceSource("/org/jline/utils/colors.txt", null).read();
                 final BufferedReader br = new BufferedReader(new InputStreamReader(is))) {
                out = br.lines().map((Function<? super String, ?>)String::trim).filter(s -> !s.startsWith("#")).filter(s -> !s.isEmpty()).collect((Collector<? super Object, ?, List<String>>)Collectors.toList());
            }
            return out;
        }
        
        public void printColors(final boolean name, final boolean rgb, final boolean small, final int columns, String findName, final String style) throws IOException {
            this.name = (!rgb && name);
            this.rgb = rgb;
            this.setFixedStyle(style);
            final AttributedStringBuilder asb = new AttributedStringBuilder();
            final int width = this.terminal.getWidth();
            final String tableName = small ? " 16-color " : "256-color ";
            if (!name && !rgb) {
                this.out.print(tableName);
                this.out.print("table, fg:<name> ");
                if (!small) {
                    this.out.print("/ 38;5;<n>");
                }
                this.out.println();
                this.out.print("                 bg:<name> ");
                if (!small) {
                    this.out.print("/ 48;5;<n>");
                }
                this.out.println("\n");
                final boolean narrow = width < 180;
                for (final String c : Colors.COLORS_16) {
                    final AttributedStyle ss = new StyleResolver(this::getStyle).resolve('.' + c, null);
                    asb.style(ss);
                    asb.append(this.addPadding(11, c));
                    asb.style(AttributedStyle.DEFAULT);
                    if (c.equals("white")) {
                        if (narrow || small) {
                            asb.append('\n');
                        }
                        else {
                            asb.append("    ");
                        }
                    }
                    else {
                        if (!c.equals("!white")) {
                            continue;
                        }
                        asb.append('\n');
                    }
                }
                asb.append('\n');
                if (!small) {
                    for (int i = 16; i < 256; ++i) {
                        final String fg = this.foreground(i);
                        final String code = Integer.toString(i);
                        final AttributedStyle ss2 = new StyleResolver(this::getStyle).resolve("." + fg + code, null);
                        asb.style(ss2);
                        String str = " ";
                        if (i < 100) {
                            str = "  ";
                        }
                        else if (i > 231) {
                            str = ((i % 2 == 0) ? "    " : "   ");
                        }
                        asb.append(str).append(code).append(' ');
                        if (i == 51 || i == 87 || i == 123 || i == 159 || i == 195 || i == 231 || (narrow && (i == 33 || i == 69 || i == 105 || i == 141 || i == 177 || i == 213 || i == 243))) {
                            asb.style(AttributedStyle.DEFAULT);
                            asb.append('\n');
                            if (i == 231) {
                                asb.append('\n');
                            }
                        }
                    }
                }
            }
            else {
                this.out.print(tableName);
                if (name) {
                    asb.tabs(Arrays.asList(25, 60, 75));
                    this.out.println("table, fg:~<name> OR 38;5;<n>");
                    this.out.println("                 bg:~<name> OR 48;5;<n>");
                }
                else {
                    asb.tabs(Arrays.asList(15, 45, 70));
                    this.out.println("table, fg-rgb:<color24bit> OR 38;5;<n>");
                    this.out.println("                 bg-rgb:<color24bit> OR 48;5;<n>");
                }
                this.out.println();
                int col = 0;
                int idx = 0;
                int colWidth = rgb ? 12 : 21;
                int lb = 1;
                if (findName != null && (findName.startsWith("#") || findName.startsWith("x"))) {
                    findName = findName.substring(1);
                }
                for (String line : this.retrieveColorNames()) {
                    if (!rgb) {
                        if (findName != null) {
                            if (!line.toLowerCase().contains(findName)) {
                                ++idx;
                                continue;
                            }
                        }
                        else if (small) {
                            colWidth = 15;
                            lb = 1;
                        }
                        else if (columns > 4) {
                            if (idx > 15 && idx < 232) {
                                colWidth = ((columns != 6 || col == 1 || col == 2 || col == 3) ? 21 : 20);
                                lb = 1;
                            }
                            else {
                                colWidth = ((columns != 6 || idx % 2 == 0 || col == 7) ? 15 : 16);
                                lb = -1;
                            }
                        }
                    }
                    final String fg2 = this.foreground(idx);
                    if (rgb) {
                        line = Integer.toHexString(org.jline.utils.Colors.DEFAULT_COLORS_256[idx]);
                        for (int p = line.length(); p < 6; ++p) {
                            line = "0" + line;
                        }
                        if (findName != null && !line.toLowerCase().matches(findName)) {
                            ++idx;
                            continue;
                        }
                    }
                    final AttributedStyle ss3 = new StyleResolver(this::getStyle).resolve("." + fg2 + line, null);
                    if (rgb) {
                        line = "#" + line;
                    }
                    asb.style(ss3);
                    String idxstr = Integer.toString(idx);
                    if (rgb) {
                        if (idx < 10) {
                            idxstr = "  " + idxstr;
                        }
                        else if (idx < 100) {
                            idxstr = " " + idxstr;
                        }
                    }
                    asb.append(idxstr).append(this.addPadding(colWidth - idxstr.length(), line));
                    if (columns == 1) {
                        asb.style(AttributedStyle.DEFAULT);
                        asb.append("\t").append(this.getStyle(fg2 + line.substring((int)(rgb ? 1 : 0))));
                        asb.append("\t").append(ss3.toAnsi());
                        final int[] rgb2 = this.rgb(org.jline.utils.Colors.DEFAULT_COLORS_256[idx]);
                        final int[] hsl = this.rgb2hsl(rgb2[0], rgb2[1], rgb2[2]);
                        asb.append("\t").append(this.addLeftPadding(6, hsl[0] + ", ")).append(this.addLeftPadding(4, hsl[1] + "%")).append(", ").append(this.addLeftPadding(4, hsl[2] + "%"));
                    }
                    ++col;
                    ++idx;
                    if ((col + 1) * colWidth > width || col + lb > columns) {
                        col = 0;
                        asb.style(AttributedStyle.DEFAULT);
                        asb.append('\n');
                    }
                    if (findName == null) {
                        if (idx == 16) {
                            if (small) {
                                break;
                            }
                            if (col == 0) {
                                continue;
                            }
                            col = 0;
                            asb.style(AttributedStyle.DEFAULT);
                            asb.append('\n');
                        }
                        else {
                            if (idx != 232 || col == 0) {
                                continue;
                            }
                            col = 0;
                            asb.style(AttributedStyle.DEFAULT);
                            asb.append('\n');
                        }
                    }
                }
            }
            asb.toAttributedString().println(this.terminal);
        }
        
        private int[] rgb(final long color) {
            final int[] rgb = { 0, 0, 0 };
            rgb[0] = (int)(color >> 16 & 0xFFL);
            rgb[1] = (int)(color >> 8 & 0xFFL);
            rgb[2] = (int)(color & 0xFFL);
            return rgb;
        }
        
        private int[] hue2rgb(final int degree) {
            final int[] rgb = { 0, 0, 0 };
            final double hue = degree / 60.0;
            final double a = Math.tan(degree / 360.0 * 2.0 * 3.141592653589793) / Math.sqrt(3.0);
            if (hue >= 0.0 && hue < 1.0) {
                rgb[0] = 255;
                rgb[1] = (int)(2.0 * a * 255.0 / (1.0 + a));
            }
            else if (hue >= 1.0 && hue < 2.0) {
                rgb[0] = (int)(255.0 * (1.0 + a) / (2.0 * a));
                rgb[1] = 255;
            }
            else if (hue >= 2.0 && hue < 3.0) {
                rgb[1] = 255;
                rgb[2] = (int)(255.0 * (1.0 + a) / (1.0 - a));
            }
            else if (hue >= 3.0 && hue < 4.0) {
                rgb[1] = (int)(255.0 * (1.0 - a) / (1.0 + a));
                rgb[2] = 255;
            }
            else if (hue >= 4.0 && hue <= 5.0) {
                rgb[0] = (int)(255.0 * (a - 1.0) / (2.0 * a));
                rgb[2] = 255;
            }
            else if (hue > 5.0 && hue <= 6.0) {
                rgb[0] = 255;
                rgb[2] = (int)(510.0 * a / (a - 1.0));
            }
            return rgb;
        }
        
        private int[] rgb2hsl(final int r, final int g, final int b) {
            final int[] hsl = { 0, 0, 0 };
            if (r != 0 || g != 0 || b != 0) {
                hsl[0] = (int)Math.round(57.29577951308232 * Math.atan2(Math.sqrt(3.0) * (g - b), 2 * r - g - b));
                while (hsl[0] < 0) {
                    final int[] array = hsl;
                    final int n = 0;
                    array[n] += 360;
                }
            }
            final double mx = Math.max(Math.max(r, g), b) / 255.0;
            final double mn = Math.min(Math.min(r, g), b) / 255.0;
            final double l = (mx + mn) / 2.0;
            hsl[1] = ((l == 0.0 || l == 1.0) ? 0 : ((int)Math.round(100.0 * (mx - mn) / (1.0 - Math.abs(2.0 * l - 1.0)))));
            hsl[2] = (int)Math.round(100.0 * l);
            return hsl;
        }
        
        String getStyleRGB(final String s) {
            if (this.fixedStyle == null) {
                final double ry = Math.pow(this.r / 255.0, 2.2);
                final double by = Math.pow(this.b / 255.0, 2.2);
                final double gy = Math.pow(this.g / 255.0, 2.2);
                final double y = 0.2126 * ry + 0.7151 * gy + 0.0721 * by;
                String fg = "black";
                if (1.05 / (y + 0.05) > (y + 0.05) / 0.05) {
                    fg = "white";
                }
                return "bg-rgb:" + String.format("#%02x%02x%02x", this.r, this.g, this.b) + ",fg:" + fg;
            }
            return (this.fixedBg ? "fg-rgb:" : "bg-rgb:") + String.format("#%02x%02x%02x", this.r, this.g, this.b) + "," + this.fixedStyle;
        }
        
        public void printColor(final String name, final String style) throws IOException {
            this.setFixedStyle(style);
            double zoom = 1.0;
            int[] rgb = { 0, 0, 0 };
            if (name.matches("[0-9a-fA-F]{6}")) {
                rgb = this.rgb(Long.parseLong(name, 16));
                zoom = 2.0;
            }
            else if ((name.startsWith("#") || name.startsWith("x")) && name.substring(1).matches("[0-9a-fA-F]{6}")) {
                rgb = this.rgb(Long.parseLong(name.substring(1), 16));
                zoom = 2.0;
            }
            else if (Colors.COLORS_16.contains(name)) {
                for (int i = 0; i < 16; ++i) {
                    if (Colors.COLORS_16.get(i).equals(name)) {
                        rgb = this.rgb(org.jline.utils.Colors.DEFAULT_COLORS_256[i]);
                        break;
                    }
                }
            }
            else if (name.matches("hue[1-3]?[0-9]{1,2}")) {
                final int hueAngle = Integer.parseInt(name.substring(3));
                if (hueAngle > 360) {
                    throw new IllegalArgumentException("Color not found: " + name);
                }
                rgb = this.hue2rgb(hueAngle);
            }
            else {
                if (!name.matches("[a-z0-9]+")) {
                    throw new IllegalArgumentException("Color not found: " + name);
                }
                final List<String> colors = this.retrieveColorNames();
                if (colors.contains(name)) {
                    for (int j = 0; j < 256; ++j) {
                        if (colors.get(j).equals(name)) {
                            rgb = this.rgb(org.jline.utils.Colors.DEFAULT_COLORS_256[j]);
                            break;
                        }
                    }
                }
                else {
                    boolean found = false;
                    for (int k = 0; k < 256; ++k) {
                        if (colors.get(k).startsWith(name)) {
                            rgb = this.rgb(org.jline.utils.Colors.DEFAULT_COLORS_256[k]);
                            found = true;
                            break;
                        }
                    }
                    if (!found) {
                        for (int k = 0; k < 256; ++k) {
                            if (colors.get(k).contains(name)) {
                                rgb = this.rgb(org.jline.utils.Colors.DEFAULT_COLORS_256[k]);
                                found = true;
                                break;
                            }
                        }
                    }
                    if (!found) {
                        throw new IllegalArgumentException("Color not found: " + name);
                    }
                }
            }
            double step = 32.0;
            int barSize = 14;
            final int width = this.terminal.getWidth();
            if (width > 287) {
                step = 8.0;
                barSize = 58;
            }
            else if (width > 143) {
                step = 16.0;
                barSize = 29;
            }
            else if (width > 98) {
                step = 24.0;
                barSize = 18;
            }
            this.r = rgb[0];
            this.g = rgb[1];
            this.b = rgb[2];
            final int[] hsl = this.rgb2hsl(this.r, this.g, this.b);
            final int hueAngle = hsl[0];
            this.out.println("HSL: " + hsl[0] + "deg, " + hsl[1] + "%, " + hsl[2] + "%");
            if (hsl[2] > 85 || hsl[2] < 15 || hsl[1] < 15) {
                zoom = 1.0;
            }
            final double div = zoom * 256.0 / step;
            final int ndiv = (int)(div / zoom);
            final double xrs = (255 - this.r) / div;
            final double xgs = (255 - this.g) / div;
            final double xbs = (255 - this.b) / div;
            final double[] yrs = new double[ndiv];
            final double[] ygs = new double[ndiv];
            final double[] ybs = new double[ndiv];
            final double[] ro = new double[ndiv];
            final double[] go = new double[ndiv];
            final double[] bo = new double[ndiv];
            final AttributedStringBuilder asb = new AttributedStringBuilder();
            for (int y = 0; y < ndiv; ++y) {
                for (int x = 0; x < ndiv; ++x) {
                    if (y == 0) {
                        yrs[x] = (rgb[0] + x * xrs) / div;
                        ygs[x] = (rgb[1] + x * xgs) / div;
                        ybs[x] = (rgb[2] + x * xbs) / div;
                        ro[x] = rgb[0] + x * xrs;
                        go[x] = rgb[1] + x * xgs;
                        bo[x] = rgb[2] + x * xbs;
                        this.r = (int)ro[x];
                        this.g = (int)go[x];
                        this.b = (int)bo[x];
                    }
                    else {
                        this.r = (int)(ro[x] - y * yrs[x]);
                        this.g = (int)(go[x] - y * ygs[x]);
                        this.b = (int)(bo[x] - y * ybs[x]);
                    }
                    final String col = String.format("%02x%02x%02x", this.r, this.g, this.b);
                    final AttributedStyle s = new StyleResolver(this::getStyleRGB).resolve(".rgb" + col);
                    asb.style(s);
                    asb.append(" ").append("#").append(col).append(" ");
                }
                asb.style(AttributedStyle.DEFAULT).append("\n");
            }
            asb.toAttributedString().println(this.terminal);
            if (hueAngle != -1) {
                final int dAngle = 5;
                int zero = (int)(hueAngle - dAngle / 2.0 * (barSize - 1));
                zero -= zero % 5;
                final AttributedStringBuilder asb2 = new AttributedStringBuilder();
                for (int l = 0; l < barSize; ++l) {
                    int angle;
                    for (angle = zero + dAngle * l; angle < 0; angle += 360) {}
                    while (angle > 360) {
                        angle -= 360;
                    }
                    rgb = this.hue2rgb(angle);
                    this.r = rgb[0];
                    this.g = rgb[1];
                    this.b = rgb[2];
                    final AttributedStyle s2 = new StyleResolver(this::getStyleRGB).resolve(".hue" + angle);
                    asb2.style(s2);
                    asb2.append(" ").append(this.addPadding(3, "" + angle)).append(" ");
                }
                asb2.style(AttributedStyle.DEFAULT).append("\n");
                asb2.toAttributedString().println(this.terminal);
            }
        }
        
        static {
            COLORS_16 = Arrays.asList("black", "red", "green", "yellow", "blue", "magenta", "cyan", "white", "!black", "!red", "!green", "!yellow", "!blue", "!magenta", "!cyan", "!white");
        }
    }
}
