Clean up a bit

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1736006 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Activator.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Activator.java
index ca19dd2..d9312a9 100644
--- a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Activator.java
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Activator.java
@@ -25,7 +25,6 @@
 import java.util.Set;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
-import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.TimeUnit;
 
 import org.apache.felix.gogo.jline.Shell.Context;
@@ -47,7 +46,7 @@
     private ExecutorService executor;
 
     public Activator() {
-        regs = new HashSet<ServiceRegistration>();
+        regs = new HashSet<>();
     }
 
     public void start(BundleContext context) throws Exception {
@@ -87,7 +86,7 @@
     }
 
     private void startShell(final BundleContext context, CommandProcessor processor) {
-        Dictionary<String, Object> dict = new Hashtable<String, Object>();
+        Dictionary<String, Object> dict = new Hashtable<>();
         dict.put(CommandProcessor.COMMAND_SCOPE, "gogo");
 
         // register converters
@@ -102,21 +101,17 @@
         regs.add(context.registerService(Procedural.class.getName(), new Procedural(), dict));
 
         dict.put(CommandProcessor.COMMAND_FUNCTION, Posix.functions);
-        regs.add(context.registerService(Posix.class.getName(), new Posix(), dict));
+        regs.add(context.registerService(Posix.class.getName(), new Posix(processor), dict));
 
         dict.put(CommandProcessor.COMMAND_FUNCTION, Telnet.functions);
         regs.add(context.registerService(Telnet.class.getName(), new Telnet(processor), dict));
 
-        Shell shell = new Shell(new ShellContext(), processor, null);
+        Shell shell = new Shell(new ShellContext(), processor);
         dict.put(CommandProcessor.COMMAND_FUNCTION, Shell.functions);
         regs.add(context.registerService(Shell.class.getName(), shell, dict));
 
         // start shell on a separate thread...
-        executor = Executors.newSingleThreadExecutor(new ThreadFactory() {
-            public Thread newThread(Runnable runnable) {
-                return new Thread(runnable, "Gogo shell");
-            }
-        });
+        executor = Executors.newSingleThreadExecutor(runnable -> new Thread(runnable, "Gogo shell"));
         executor.submit(new StartShellJob(context, processor));
     }
 
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Builtin.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Builtin.java
index 91dcf17..8b4556f 100644
--- a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Builtin.java
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Builtin.java
@@ -20,8 +20,11 @@
 
 import java.io.BufferedReader;
 import java.io.BufferedWriter;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStreamReader;
+import java.io.PrintStream;
 import java.io.StringWriter;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
@@ -43,21 +46,39 @@
 import java.util.Set;
 import java.util.TreeMap;
 import java.util.TreeSet;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
+import org.apache.felix.gogo.runtime.CommandSessionImpl;
 import org.apache.felix.gogo.runtime.Job;
 import org.apache.felix.service.command.CommandSession;
 import org.apache.felix.service.command.Converter;
+import org.apache.felix.service.command.Function;
+import org.jline.builtins.Commands;
+import org.jline.builtins.Completers.DirectoriesCompleter;
+import org.jline.builtins.Completers.FilesCompleter;
 import org.jline.builtins.Options;
+import org.jline.reader.Candidate;
+import org.jline.reader.LineReader;
+import org.jline.reader.ParsedLine;
+import org.jline.reader.Widget;
+
+import static org.apache.felix.gogo.jline.Shell.*;
 
 /**
  * gosh built-in commands.
  */
 public class Builtin {
 
-    static final String[] functions = {"format", "getopt", "new", "set", "tac", "type", "jobs", "fg", "bg"};
+    static final String[] functions = {
+            "format", "getopt", "new", "set", "tac", "type",
+            "jobs", "fg", "bg",
+            "keymap", "setopt", "unsetopt", "complete", "history", "widget",
+            "__files", "__directories", "__usage_completion"
+    };
 
-    private static final String[] packages = {"java.lang", "java.io", "java.net",
-            "java.util"};
+    private static final String[] packages = {"java.lang", "java.io", "java.net", "java.util"};
+
     private final static Set<String> KEYWORDS = new HashSet<String>(
             Arrays.asList(new String[]{"abstract", "continue", "for", "new", "switch",
                     "assert", "default", "goto", "package", "synchronized", "boolean", "do",
@@ -68,11 +89,6 @@
                     "finally", "long", "strictfp", "volatile", "const", "float", "native",
                     "super", "while"}));
 
-    @SuppressWarnings("unchecked")
-    static Set<String> getCommands(CommandSession session) {
-        return (Set<String>) session.get(".commands");
-    }
-
     public CharSequence format(CommandSession session) {
         return format(session, session.get("_"));    // last result
     }
@@ -564,4 +580,133 @@
         return list;
     }
 
+    public void history(CommandSession session, String[] argv) throws IOException {
+        Commands.history(Shell.getReader(session), System.out, System.err, argv);
+    }
+
+    public void complete(CommandSession session, String[] argv) {
+        Commands.complete(Shell.getReader(session), System.out, System.err, Shell.getCompletions(session), argv);
+    }
+
+    public void widget(final CommandSession session, String[] argv) throws Exception {
+        java.util.function.Function<String, Widget> creator = func -> () -> {
+            try {
+                session.execute(func);
+            } catch (Exception e) {
+                // TODO: log exception ?
+                return false;
+            }
+            return true;
+        };
+        Commands.widget(Shell.getReader(session), System.out, System.err, creator, argv);
+    }
+
+    public void keymap(CommandSession session, String[] argv) {
+        Commands.keymap(Shell.getReader(session), System.out, System.err, argv);
+    }
+
+    public void setopt(CommandSession session, String[] argv) {
+        Commands.setopt(Shell.getReader(session), System.out, System.err, argv);
+    }
+
+    public void unsetopt(CommandSession session, String[] argv) {
+        Commands.unsetopt(Shell.getReader(session), System.out, System.err, argv);
+    }
+
+    public List<Candidate> __files(CommandSession session) {
+        ParsedLine line = Shell.getParsedLine(session);
+        LineReader reader = Shell.getReader(session);
+        List<Candidate> candidates = new ArrayList<>();
+        new FilesCompleter(session.currentDir()) {
+            @Override
+            protected String getDisplay(Path p) {
+                return getFileDisplay(session, p);
+            }
+        }.complete(reader, line, candidates);
+        return candidates;
+    }
+
+    public List<Candidate> __directories(CommandSession session) {
+        ParsedLine line = Shell.getParsedLine(session);
+        LineReader reader = Shell.getReader(session);
+        List<Candidate> candidates = new ArrayList<>();
+        new DirectoriesCompleter(session.currentDir()) {
+            @Override
+            protected String getDisplay(Path p) {
+                return getFileDisplay(session, p);
+            }
+        }.complete(reader, line, candidates);
+        return candidates;
+    }
+
+    private String getFileDisplay(CommandSession session, Path path) {
+        String type;
+        String suffix;
+        if (Files.isSymbolicLink(path)) {
+            type = "sl";
+            suffix = "@";
+        } else if (Files.isDirectory(path)) {
+            type = "dr";
+            suffix = "/";
+        } else if (Files.isExecutable(path)) {
+            type = "ex";
+            suffix = "*";
+        } else if (!Files.isRegularFile(path)) {
+            type = "ot";
+            suffix = "";
+        } else {
+            type = "";
+            suffix = "";
+        }
+        String col = Posix.getColorMap(session, "LS").get(type);
+        if (col != null && !col.isEmpty()) {
+            return "\033[" + col + "m" + path.getFileName().toString() + "\033[m" + suffix;
+        } else {
+            return path.getFileName().toString() + suffix;
+        }
+
+    }
+
+    public void __usage_completion(CommandSession session, String command) throws Exception {
+        Object func = session.get(command.contains(":") ? command : "*:" + command);
+        if (func instanceof Function) {
+            ByteArrayInputStream bais = new ByteArrayInputStream(new byte[0]);
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            ByteArrayOutputStream baes = new ByteArrayOutputStream();
+            CommandSession ts = ((CommandSessionImpl) session).processor().createSession(bais, new PrintStream(baos), new PrintStream(baes));
+            ts.execute(command + " --help");
+
+            String regex = "(?x)\\s*" + "(?:-([^-]))?" +  // 1: short-opt-1
+                    "(?:,?\\s*-(\\w))?" +                 // 2: short-opt-2
+                    "(?:,?\\s*--(\\w[\\w-]*)(=\\w+)?)?" + // 3: long-opt-1 and 4:arg-1
+                    "(?:,?\\s*--(\\w[\\w-]*))?" +         // 5: long-opt-2
+                    ".*?(?:\\(default=(.*)\\))?\\s*" +    // 6: default
+                    "(.*)";                               // 7: description
+            Pattern pattern = Pattern.compile(regex);
+            for (String l : baes.toString().split("\n")) {
+                Matcher matcher = pattern.matcher(l);
+                if (matcher.matches()) {
+                    List<String> args = new ArrayList<>();
+                    if (matcher.group(1) != null) {
+                        args.add("--short-option");
+                        args.add(matcher.group(1));
+                    }
+                    if (matcher.group(3) != null) {
+                        args.add("--long-option");
+                        args.add(matcher.group(1));
+                    }
+                    if (matcher.group(4) != null) {
+                        args.add("--argument");
+                        args.add("");
+                    }
+                    if (matcher.group(7) != null) {
+                        args.add("--description");
+                        args.add(matcher.group(7));
+                    }
+                    complete(session, args.toArray(new String[args.size()]));
+                }
+            }
+        }
+    }
+
 }
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Highlighter.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Highlighter.java
index 7617f7b..655bab9 100644
--- a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Highlighter.java
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Highlighter.java
@@ -103,7 +103,6 @@
                     break;
                 }
                 if (token.start() > cur) {
-//                    ansi.a(buffer.substring(cur, token.start()));
                     cur = token.start();
                 }
                 // Find corresponding statement
@@ -153,37 +152,6 @@
                     }
                 }
                 Arrays.fill(types, token.start(), Math.min(token.start() + token.length(), types.length), type);
-                /*
-                String valid;
-                if (token.start() + token.length() <= buffer.length()) {
-                    valid = token.toString();
-                } else {
-                    valid = token.subSequence(0, buffer.length() - token.start()).toString();
-                }
-                switch (type) {
-                    case Reserved:
-                        ansi.fg(Color.MAGENTA).a(valid).fg(Color.DEFAULT);
-                        break;
-                    case String:
-                    case Number:
-                    case Constant:
-                        ansi.fg(Color.GREEN).a(valid).fg(Color.DEFAULT);
-                        break;
-                    case Variable:
-                    case VariableName:
-                        ansi.fg(Color.CYAN).a(valid).fg(Color.DEFAULT);
-                        break;
-                    case Function:
-                        ansi.fg(Color.BLUE).a(valid).fg(Color.DEFAULT);
-                        break;
-                    case BadFunction:
-                        ansi.fg(Color.RED).a(valid).fg(Color.DEFAULT);
-                        break;
-                    default:
-                        ansi.a(valid);
-                        break;
-                }
-                */
                 cur = Math.min(token.start() + token.length(), buffer.length());
             }
 
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/IoUtils.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/IoUtils.java
deleted file mode 100644
index 9f14b96..0000000
--- a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/IoUtils.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.felix.gogo.jline;
-
-import java.io.File;
-import java.nio.file.LinkOption;
-import java.nio.file.attribute.PosixFilePermission;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.List;
-import java.util.Set;
-
-import org.apache.sshd.common.util.OsUtils;
-
-/**
- * TODO Add javadoc
- *
- * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
- */
-public final class IoUtils {
-
-    public static final LinkOption[] EMPTY_LINK_OPTIONS = new LinkOption[0];
-
-    public static final List<String> WINDOWS_EXECUTABLE_EXTENSIONS = Collections.unmodifiableList(Arrays.asList(".bat", ".exe", ".cmd"));
-
-    private static final LinkOption[] NO_FOLLOW_OPTIONS = new LinkOption[]{LinkOption.NOFOLLOW_LINKS};
-
-    /**
-     * Private Constructor
-     */
-    private IoUtils() {
-        throw new UnsupportedOperationException("No instance allowed");
-    }
-
-    public static LinkOption[] getLinkOptions(boolean followLinks) {
-        if (followLinks) {
-            return EMPTY_LINK_OPTIONS;
-        } else {    // return a clone that modifications to the array will not affect others
-            return NO_FOLLOW_OPTIONS.clone();
-        }
-    }
-
-    /**
-     * @param fileName The file name to be evaluated - ignored if {@code null}/empty
-     * @return {@code true} if the file ends in one of the {@link #WINDOWS_EXECUTABLE_EXTENSIONS}
-     */
-    public static boolean isWindowsExecutable(String fileName) {
-        if ((fileName == null) || (fileName.length() <= 0)) {
-            return false;
-        }
-        for (String suffix : WINDOWS_EXECUTABLE_EXTENSIONS) {
-            if (fileName.endsWith(suffix)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * @param f The {@link File} to be checked
-     * @return A {@link Set} of {@link PosixFilePermission}s based on whether
-     * the file is readable/writable/executable. If so, then <U>all</U> the
-     * relevant permissions are set (i.e., owner, group and others)
-     */
-    public static Set<PosixFilePermission> getPermissionsFromFile(File f) {
-        Set<PosixFilePermission> perms = EnumSet.noneOf(PosixFilePermission.class);
-        if (f.canRead()) {
-            perms.add(PosixFilePermission.OWNER_READ);
-            perms.add(PosixFilePermission.GROUP_READ);
-            perms.add(PosixFilePermission.OTHERS_READ);
-        }
-
-        if (f.canWrite()) {
-            perms.add(PosixFilePermission.OWNER_WRITE);
-            perms.add(PosixFilePermission.GROUP_WRITE);
-            perms.add(PosixFilePermission.OTHERS_WRITE);
-        }
-
-        if (f.canExecute() || (OsUtils.isWin32() && isWindowsExecutable(f.getName()))) {
-            perms.add(PosixFilePermission.OWNER_EXECUTE);
-            perms.add(PosixFilePermission.GROUP_EXECUTE);
-            perms.add(PosixFilePermission.OTHERS_EXECUTE);
-        }
-
-        return perms;
-    }
-
-}
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/JLineCommands.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/JLineCommands.java
deleted file mode 100644
index 807c319..0000000
--- a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/JLineCommands.java
+++ /dev/null
@@ -1,308 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.felix.gogo.jline;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.PrintStream;
-import java.nio.file.Files;
-import java.nio.file.LinkOption;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import org.apache.felix.gogo.jline.Shell.Context;
-import org.apache.felix.gogo.runtime.CommandSessionImpl;
-import org.apache.felix.service.command.CommandProcessor;
-import org.apache.felix.service.command.CommandSession;
-import org.apache.felix.service.command.Function;
-import org.jline.builtins.Completers.DirectoriesCompleter;
-import org.jline.builtins.Completers.FilesCompleter;
-import org.jline.builtins.Options;
-import org.jline.reader.Candidate;
-import org.jline.reader.LineReader;
-import org.jline.reader.ParsedLine;
-import org.jline.reader.Widget;
-import org.jline.reader.impl.completer.completer.FileNameCompleter;
-import org.jline.terminal.Attributes;
-import org.jline.terminal.Terminal;
-import org.jline.utils.InfoCmp.Capability;
-
-public class JLineCommands {
-
-    public static final String[] functions = {
-            "keymap", "setopt", "unsetopt", "complete", "history",
-            "less", "watch", "nano", "widget", "tmux",
-            "__files", "__directories", "__usage_completion"
-    };
-
-    private final CommandProcessor processor;
-
-    private final org.jline.builtins.Commands commands = new org.jline.builtins.Commands();
-
-    public JLineCommands(CommandProcessor processor) {
-        this.processor = processor;
-    }
-
-    public void tmux(final CommandSession session, String[] argv) throws Exception {
-        commands.tmux(Shell.getTerminal(session),
-                System.out, System.err,
-                () -> session.get(".tmux"),
-                t -> session.put(".tmux", t),
-                c -> startShell(session, c), argv);
-    }
-
-    private void startShell(CommandSession session, Terminal terminal) {
-        new Thread(() -> runShell(session, terminal), terminal.getName() + " shell").start();
-    }
-
-    private void runShell(CommandSession session, Terminal terminal) {
-        InputStream in = terminal.input();
-        OutputStream out = terminal.output();
-        CommandSession newSession = processor.createSession(in, out, out);
-        newSession.put(Shell.VAR_TERMINAL, terminal);
-        newSession.put(".tmux", session.get(".tmux"));
-        Context context = new Context() {
-            public String getProperty(String name) {
-                return System.getProperty(name);
-            }
-            public void exit() throws Exception {
-                terminal.close();
-            }
-        };
-        try {
-            new Shell(context, processor, terminal).gosh(newSession, new String[]{"--login"});
-        } catch (Exception e) {
-            e.printStackTrace();
-        } finally {
-            try {
-                terminal.close();
-            } catch (IOException e) {
-                e.printStackTrace();
-            }
-        }
-    }
-
-    public void nano(final CommandSession session, String[] argv) throws Exception {
-        commands.nano(Shell.getTerminal(session), System.out, System.err, session.currentDir(), argv);
-    }
-
-    public void watch(final CommandSession session, String[] argv) throws IOException, InterruptedException {
-        final String[] usage = {
-                "watch - watches & refreshes the output of a command",
-                "Usage: watch [OPTIONS] COMMAND",
-                "  -? --help                    Show help",
-                "  -n --interval                Interval between executions of the command in seconds",
-                "  -a --append                  The output should be appended but not clear the console"
-        };
-        final Options opt = Options.compile(usage).parse(argv);
-        if (opt.isSet("help")) {
-            opt.usage(System.err);
-            return;
-        }
-        List<String> args = opt.args();
-        if (args.isEmpty()) {
-            System.err.println("Argument expected");
-            return;
-        }
-        ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
-        final Terminal terminal = Shell.getTerminal(session);
-        final CommandProcessor processor = Shell.getProcessor(session);
-        try {
-            int interval = 1;
-            if (opt.isSet("interval")) {
-                interval = opt.getNumber("interval");
-                if (interval < 1) {
-                    interval = 1;
-                }
-            }
-            final String cmd = String.join(" ", args);
-            Runnable task = () -> {
-                ByteArrayOutputStream baos = new ByteArrayOutputStream();
-                PrintStream os = new PrintStream(baos);
-                InputStream is = new ByteArrayInputStream(new byte[0]);
-                if (opt.isSet("append") || !terminal.puts(Capability.clear_screen)) {
-                    terminal.writer().println();
-                }
-                try {
-                    CommandSession ns = processor.createSession(is, os, os);
-                    Set<String> vars = Shell.getCommands(session);
-                    for (String n : vars) {
-                        ns.put(n, session.get(n));
-                    }
-                    ns.execute(cmd);
-                } catch (Throwable t) {
-                    t.printStackTrace(os);
-                }
-                os.flush();
-                terminal.writer().print(baos.toString());
-                terminal.writer().flush();
-            };
-            executorService.scheduleAtFixedRate(task, 0, interval, TimeUnit.SECONDS);
-            Attributes attr = terminal.enterRawMode();
-            terminal.reader().read();
-            terminal.setAttributes(attr);
-        } finally {
-            executorService.shutdownNow();
-        }
-    }
-
-    public void less(CommandSession session, String[] argv) throws IOException, InterruptedException {
-        commands.less(Shell.getTerminal(session), System.out, System.err, session.currentDir(), argv);
-    }
-
-    public void history(CommandSession session, String[] argv) throws IOException {
-        commands.history(Shell.getReader(session), System.out, System.err, argv);
-    }
-
-    public void complete(CommandSession session, String[] argv) {
-        commands.complete(Shell.getReader(session), System.out, System.err, Shell.getCompletions(session), argv);
-    }
-
-    public void widget(final CommandSession session, String[] argv) throws Exception {
-        java.util.function.Function<String, Widget> creator = func -> () -> {
-            try {
-                session.execute(func);
-            } catch (Exception e) {
-                // TODO: log exception ?
-                return false;
-            }
-            return true;
-        };
-        commands.widget(Shell.getReader(session), System.out, System.err, creator, argv);
-    }
-
-    public void keymap(CommandSession session, String[] argv) {
-        commands.keymap(Shell.getReader(session), System.out, System.err, argv);
-    }
-
-    public void setopt(CommandSession session, String[] argv) {
-        commands.setopt(Shell.getReader(session), System.out, System.err, argv);
-    }
-
-    public void unsetopt(CommandSession session, String[] argv) {
-        commands.unsetopt(Shell.getReader(session), System.out, System.err, argv);
-    }
-
-    public List<Candidate> __files(CommandSession session) {
-        ParsedLine line = Shell.getParsedLine(session);
-        LineReader reader = Shell.getReader(session);
-        List<Candidate> candidates = new ArrayList<>();
-        new FilesCompleter(session.currentDir()) {
-            @Override
-            protected String getDisplay(Path p) {
-                return getFileDisplay(session, p);
-            }
-        }.complete(reader, line, candidates);
-        return candidates;
-    }
-
-    public List<Candidate> __directories(CommandSession session) {
-        ParsedLine line = Shell.getParsedLine(session);
-        LineReader reader = Shell.getReader(session);
-        List<Candidate> candidates = new ArrayList<>();
-        new DirectoriesCompleter(session.currentDir()) {
-            @Override
-            protected String getDisplay(Path p) {
-                return getFileDisplay(session, p);
-            }
-        }.complete(reader, line, candidates);
-        return candidates;
-    }
-
-    private String getFileDisplay(CommandSession session, Path path) {
-        String type;
-        String suffix;
-        if (Files.isSymbolicLink(path)) {
-            type = "sl";
-            suffix = "@";
-        } else if (Files.isDirectory(path)) {
-            type = "dr";
-            suffix = "/";
-        } else if (Files.isExecutable(path)) {
-            type = "ex";
-            suffix = "*";
-        } else if (!Files.isRegularFile(path)) {
-            type = "ot";
-            suffix = "";
-        } else {
-            type = "";
-            suffix = "";
-        }
-        String col = Posix.getColorMap(session, "LS").get(type);
-        if (col != null && !col.isEmpty()) {
-            return "\033[" + col + "m" + path.getFileName().toString() + "\033[m" + suffix;
-        } else {
-            return path.getFileName().toString() + suffix;
-        }
-
-    }
-
-    public void __usage_completion(CommandSession session, String command) throws Exception {
-        Object func = session.get(command.contains(":") ? command : "*:" + command);
-        if (func instanceof Function) {
-            ByteArrayInputStream bais = new ByteArrayInputStream(new byte[0]);
-            ByteArrayOutputStream baos = new ByteArrayOutputStream();
-            ByteArrayOutputStream baes = new ByteArrayOutputStream();
-            CommandSession ts = ((CommandSessionImpl) session).processor().createSession(bais, new PrintStream(baos), new PrintStream(baes));
-            ts.execute(command + " --help");
-
-            String regex = "(?x)\\s*" + "(?:-([^-]))?" +  // 1: short-opt-1
-                    "(?:,?\\s*-(\\w))?" +                 // 2: short-opt-2
-                    "(?:,?\\s*--(\\w[\\w-]*)(=\\w+)?)?" + // 3: long-opt-1 and 4:arg-1
-                    "(?:,?\\s*--(\\w[\\w-]*))?" +         // 5: long-opt-2
-                    ".*?(?:\\(default=(.*)\\))?\\s*" +    // 6: default
-                    "(.*)";                               // 7: description
-            Pattern pattern = Pattern.compile(regex);
-            for (String l : baes.toString().split("\n")) {
-                Matcher matcher = pattern.matcher(l);
-                if (matcher.matches()) {
-                    List<String> args = new ArrayList<>();
-                    if (matcher.group(1) != null) {
-                        args.add("--short-option");
-                        args.add(matcher.group(1));
-                    }
-                    if (matcher.group(3) != null) {
-                        args.add("--long-option");
-                        args.add(matcher.group(1));
-                    }
-                    if (matcher.group(4) != null) {
-                        args.add("--argument");
-                        args.add("");
-                    }
-                    if (matcher.group(7) != null) {
-                        args.add("--description");
-                        args.add(matcher.group(7));
-                    }
-                    complete(session, args.toArray(new String[args.size()]));
-                }
-            }
-        }
-    }
-
-}
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/JLineCompletionEnvironment.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/JLineCompletionEnvironment.java
deleted file mode 100644
index 93b31ef..0000000
--- a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/JLineCompletionEnvironment.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.felix.gogo.jline;
-
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.felix.service.command.CommandSession;
-import org.jline.builtins.Completers.CompletionData;
-import org.jline.builtins.Completers.CompletionEnvironment;
-import org.jline.reader.LineReader;
-import org.jline.reader.ParsedLine;
-
-public class JLineCompletionEnvironment implements CompletionEnvironment {
-
-    private final CommandSession session;
-
-    public JLineCompletionEnvironment(CommandSession session) {
-        this.session = session;
-    }
-
-    public Map<String, List<CompletionData>> getCompletions() {
-        return Shell.getCompletions(session);
-    }
-
-    public Set<String> getCommands() {
-        return Shell.getCommands(session);
-    }
-
-    public String resolveCommand(String command) {
-        return Shell.resolve(session, command);
-    }
-
-    public String commandName(String command) {
-        int idx = command.indexOf(':');
-        return idx >= 0 ? command.substring(idx + 1) : command;
-    }
-
-    public Object evaluate(LineReader reader, ParsedLine line, String func) throws Exception {
-        session.put(Shell.VAR_COMMAND_LINE, line);
-        return session.execute(func);
-    }
-}
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Main.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Main.java
index 53fc716..8064192 100644
--- a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Main.java
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Main.java
@@ -49,15 +49,14 @@
             try {
                 CommandProcessorImpl processor = new CommandProcessorImpl(tio);
                 Context context = new MyContext();
-                Shell shell = new Shell(context, processor, terminal);
+                Shell shell = new Shell(context, processor);
                 processor.addCommand("gogo", processor, "addCommand");
                 processor.addCommand("gogo", processor, "removeCommand");
                 processor.addCommand("gogo", processor, "eval");
                 register(processor, new Builtin(), Builtin.functions);
                 register(processor, new Procedural(), Procedural.functions);
-                register(processor, new Posix(), Posix.functions);
+                register(processor, new Posix(processor), Posix.functions);
                 register(processor, shell, Shell.functions);
-                register(processor, new JLineCommands(processor), JLineCommands.functions);
                 try {
                     register(processor, new Telnet(processor), Telnet.functions);
                 } catch (Throwable t) {
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ParsedLineImpl.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ParsedLineImpl.java
index 22f0cdc..fb122a6 100644
--- a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ParsedLineImpl.java
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ParsedLineImpl.java
@@ -38,7 +38,7 @@
         this.program = program;
         this.source = line.toString();
         this.cursor = cursor - line.start();
-        this.tokens = new ArrayList<String>();
+        this.tokens = new ArrayList<>();
         for (Token token : tokens) {
             this.tokens.add(token.toString());
         }
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Posix.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Posix.java
index d523a32..09cd5f1 100644
--- a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Posix.java
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Posix.java
@@ -19,15 +19,19 @@
 package org.apache.felix.gogo.jline;
 
 import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
+import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.io.OutputStream;
 import java.io.PrintStream;
 import java.nio.file.FileVisitOption;
 import java.nio.file.FileVisitResult;
 import java.nio.file.FileVisitor;
 import java.nio.file.Files;
+import java.nio.file.LinkOption;
 import java.nio.file.Path;
 import java.nio.file.PathMatcher;
 import java.nio.file.attribute.BasicFileAttributes;
@@ -48,6 +52,9 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.TreeMap;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
 import java.util.function.Consumer;
 import java.util.function.IntBinaryOperator;
 import java.util.function.Predicate;
@@ -56,17 +63,22 @@
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
+import org.apache.felix.gogo.jline.Shell.Context;
 import org.apache.felix.gogo.runtime.Pipe;
+import org.apache.felix.service.command.CommandProcessor;
 import org.apache.felix.service.command.CommandSession;
+import org.apache.sshd.common.util.OsUtils;
+import org.jline.builtins.Commands;
 import org.jline.builtins.Less.Source;
 import org.jline.builtins.Less.StdInSource;
 import org.jline.builtins.Less.URLSource;
 import org.jline.builtins.Options;
-import org.jline.reader.LineReader.Option;
+import org.jline.terminal.Attributes;
 import org.jline.terminal.Terminal;
 import org.jline.utils.AttributedString;
 import org.jline.utils.AttributedStringBuilder;
 import org.jline.utils.AttributedStyle;
+import org.jline.utils.InfoCmp.Capability;
 
 /**
  * Posix-like utilities.
@@ -75,42 +87,31 @@
  * http://www.opengroup.org/onlinepubs/009695399/utilities/contents.html</a>
  */
 public class Posix {
-    static final String[] functions = {"cat", "echo", "grep", "sort", "sleep", "cd", "pwd", "ls"};
+
+    static final String[] functions = {
+            "cat", "echo", "grep", "sort", "sleep", "cd", "pwd", "ls",
+            "less", "watch", "nano", "tmux",
+    };
 
     public static final String DEFAULT_LS_COLORS = "dr=1;91:ex=1;92:sl=1;96:ot=34;43";
 
-    public void _main(CommandSession session, String[] argv) {
+    private static final LinkOption[] NO_FOLLOW_OPTIONS = new LinkOption[]{LinkOption.NOFOLLOW_LINKS};
+    private static final List<String> WINDOWS_EXECUTABLE_EXTENSIONS = Collections.unmodifiableList(Arrays.asList(".bat", ".exe", ".cmd"));
+    private static final LinkOption[] EMPTY_LINK_OPTIONS = new LinkOption[0];
+
+    private final CommandProcessor processor;
+
+    public Posix(CommandProcessor processor) {
+        this.processor = processor;
+    }
+
+    protected void _main(CommandSession session, String[] argv) {
         if (argv == null || argv.length < 1) {
             throw new IllegalArgumentException();
         }
         try {
             argv = expand(session, argv);
-            switch (argv[0]) {
-                case "cat":
-                    cat(session, argv);
-                    break;
-                case "echo":
-                    echo(session, argv);
-                    break;
-                case "grep":
-                    grep(session, argv);
-                    break;
-                case "sort":
-                    sort(session, argv);
-                    break;
-                case "sleep":
-                    sleep(session, argv);
-                    break;
-                case "cd":
-                    cd(session, argv);
-                    break;
-                case "pwd":
-                    pwd(session, argv);
-                    break;
-                case "ls":
-                    ls(session, argv);
-                    break;
-            }
+            run(session, argv);
         } catch (IllegalArgumentException e) {
             System.err.println(e.getMessage());
             Pipe.error(2);
@@ -129,6 +130,243 @@
         }
     }
 
+    protected Options parseOptions(CommandSession session, String[] usage, Object[] argv) throws Exception {
+        Options opt = Options.compile(usage, s -> get(session, s)).parse(argv, true);
+        if (opt.isSet("help")) {
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            opt.usage(new PrintStream(baos));
+            throw new HelpException(baos.toString());
+        }
+        return opt;
+    }
+
+    protected String get(CommandSession session, String name) {
+        Object o = session.get(name);
+        return o != null ? o.toString() : null;
+    }
+
+    protected String[] expand(CommandSession session, String[] argv) throws IOException {
+        String reserved = "(?<!\\\\)[*(|<\\[?]";
+        List<String> params = new ArrayList<>();
+        for (String arg : argv) {
+            if (arg.matches(".*" + reserved + ".*")) {
+                String org = arg;
+                List<String> expanded = new ArrayList<>();
+                Path currentDir = session.currentDir();
+                Path dir;
+                String pfx = arg.replaceFirst(reserved + ".*", "");
+                String prefix;
+                if (pfx.indexOf('/') >= 0) {
+                    pfx = pfx.substring(0, pfx.lastIndexOf('/'));
+                    arg = arg.substring(pfx.length() + 1);
+                    dir = currentDir.resolve(pfx).normalize();
+                    prefix = pfx + "/";
+                } else {
+                    dir = currentDir;
+                    prefix = "";
+                }
+                PathMatcher matcher = dir.getFileSystem().getPathMatcher("glob:" + arg);
+                Files.walkFileTree(dir,
+                        EnumSet.of(FileVisitOption.FOLLOW_LINKS),
+                        Integer.MAX_VALUE,
+                        new FileVisitor<Path>() {
+                            @Override
+                            public FileVisitResult preVisitDirectory(Path file, BasicFileAttributes attrs) throws IOException {
+                                if (file.equals(dir)) {
+                                    return FileVisitResult.CONTINUE;
+                                }
+                                if (Files.isHidden(file)) {
+                                    return FileVisitResult.SKIP_SUBTREE;
+                                }
+                                Path r = dir.relativize(file);
+                                if (matcher.matches(r)) {
+                                    expanded.add(prefix + r.toString());
+                                }
+                                return FileVisitResult.CONTINUE;
+                            }
+
+                            @Override
+                            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+                                if (!Files.isHidden(file)) {
+                                    Path r = dir.relativize(file);
+                                    if (matcher.matches(r)) {
+                                        expanded.add(prefix + r.toString());
+                                    }
+                                }
+                                return FileVisitResult.CONTINUE;
+                            }
+
+                            @Override
+                            public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
+                                return FileVisitResult.CONTINUE;
+                            }
+
+                            @Override
+                            public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
+                                return FileVisitResult.CONTINUE;
+                            }
+                        });
+                Collections.sort(expanded);
+                if (expanded.isEmpty()) {
+                    throw new IOException("no matches found: " + org);
+                }
+                params.addAll(expanded);
+            } else {
+                params.add(arg);
+            }
+        }
+        return params.toArray(new String[params.size()]);
+    }
+
+    protected Object run(CommandSession session, String[] argv) throws Exception {
+        switch (argv[0]) {
+            case "cat":
+                cat(session, argv);
+                break;
+            case "echo":
+                echo(session, argv);
+                break;
+            case "grep":
+                grep(session, argv);
+                break;
+            case "sort":
+                sort(session, argv);
+                break;
+            case "sleep":
+                sleep(session, argv);
+                break;
+            case "cd":
+                cd(session, argv);
+                break;
+            case "pwd":
+                pwd(session, argv);
+                break;
+            case "ls":
+                ls(session, argv);
+                break;
+            case "less":
+                less(session, argv);
+                break;
+            case "watch":
+                watch(session, argv);
+                break;
+            case "nano":
+                nano(session, argv);
+                break;
+            case "tmux":
+                tmux(session, argv);
+                break;
+        }
+        return null;
+    }
+
+    protected void tmux(final CommandSession session, String[] argv) throws Exception {
+        Commands.tmux(Shell.getTerminal(session),
+                System.out, System.err,
+                () -> session.get(".tmux"),
+                t -> session.put(".tmux", t),
+                c -> startShell(session, c), argv);
+    }
+
+    private void startShell(CommandSession session, Terminal terminal) {
+        new Thread(() -> runShell(session, terminal), terminal.getName() + " shell").start();
+    }
+
+    private void runShell(CommandSession session, Terminal terminal) {
+        InputStream in = terminal.input();
+        OutputStream out = terminal.output();
+        CommandSession newSession = processor.createSession(in, out, out);
+        newSession.put(Shell.VAR_TERMINAL, terminal);
+        newSession.put(".tmux", session.get(".tmux"));
+        Context context = new Context() {
+            public String getProperty(String name) {
+                return System.getProperty(name);
+            }
+            public void exit() throws Exception {
+                terminal.close();
+            }
+        };
+        try {
+            new Shell(context, processor).gosh(newSession, new String[]{"--login"});
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            try {
+                terminal.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    protected void nano(final CommandSession session, String[] argv) throws Exception {
+        Commands.nano(Shell.getTerminal(session), System.out, System.err, session.currentDir(), argv);
+    }
+
+    protected void watch(final CommandSession session, String[] argv) throws IOException, InterruptedException {
+        final String[] usage = {
+                "watch - watches & refreshes the output of a command",
+                "Usage: watch [OPTIONS] COMMAND",
+                "  -? --help                    Show help",
+                "  -n --interval                Interval between executions of the command in seconds",
+                "  -a --append                  The output should be appended but not clear the console"
+        };
+        final Options opt = Options.compile(usage).parse(argv);
+        if (opt.isSet("help")) {
+            opt.usage(System.err);
+            return;
+        }
+        List<String> args = opt.args();
+        if (args.isEmpty()) {
+            System.err.println("Argument expected");
+            return;
+        }
+        ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
+        final Terminal terminal = Shell.getTerminal(session);
+        final CommandProcessor processor = Shell.getProcessor(session);
+        try {
+            int interval = 1;
+            if (opt.isSet("interval")) {
+                interval = opt.getNumber("interval");
+                if (interval < 1) {
+                    interval = 1;
+                }
+            }
+            final String cmd = String.join(" ", args);
+            Runnable task = () -> {
+                ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                PrintStream os = new PrintStream(baos);
+                InputStream is = new ByteArrayInputStream(new byte[0]);
+                if (opt.isSet("append") || !terminal.puts(Capability.clear_screen)) {
+                    terminal.writer().println();
+                }
+                try {
+                    CommandSession ns = processor.createSession(is, os, os);
+                    Set<String> vars = Shell.getCommands(session);
+                    for (String n : vars) {
+                        ns.put(n, session.get(n));
+                    }
+                    ns.execute(cmd);
+                } catch (Throwable t) {
+                    t.printStackTrace(os);
+                }
+                os.flush();
+                terminal.writer().print(baos.toString());
+                terminal.writer().flush();
+            };
+            executorService.scheduleAtFixedRate(task, 0, interval, TimeUnit.SECONDS);
+            Attributes attr = terminal.enterRawMode();
+            terminal.reader().read();
+            terminal.setAttributes(attr);
+        } finally {
+            executorService.shutdownNow();
+        }
+    }
+
+    protected void less(CommandSession session, String[] argv) throws IOException, InterruptedException {
+        Commands.less(Shell.getTerminal(session), System.out, System.err, session.currentDir(), argv);
+    }
+
     protected void sort(CommandSession session, String[] argv) throws Exception {
         final String[] usage = {
                 "sort -  writes sorted standard input to standard output.",
@@ -403,14 +641,14 @@
                 for (String view : path.getFileSystem().supportedFileAttributeViews()) {
                     try {
                         Map<String, Object> ta = Files.readAttributes(path, view + ":*",
-                                IoUtils.getLinkOptions(opt.isSet("L")));
+                                getLinkOptions(opt.isSet("L")));
                         ta.entrySet().forEach(e -> attrs.putIfAbsent(e.getKey(), e.getValue()));
                     } catch (IOException e) {
                         // Ignore
                     }
                 }
                 attrs.computeIfAbsent("isExecutable", s -> Files.isExecutable(path));
-                attrs.computeIfAbsent("permissions", s -> IoUtils.getPermissionsFromFile(path.toFile()));
+                attrs.computeIfAbsent("permissions", s -> getPermissionsFromFile(path.toFile()));
                 return attrs;
             }
         }
@@ -613,7 +851,7 @@
         if (before < 0) {
             before = context;
         }
-        List<String> lines = new ArrayList<String>();
+        List<String> lines = new ArrayList<>();
         boolean invertMatch = opt.isSet("invert-match");
         boolean lineNumber = opt.isSet("line-number");
         boolean count = opt.isSet("count");
@@ -785,10 +1023,7 @@
                 sortFields = new ArrayList<>();
                 sortFields.add("1");
             }
-            sortKeys = new ArrayList<Key>();
-            for (String f : sortFields) {
-                sortKeys.add(new Key(f));
-            }
+            sortKeys = sortFields.stream().map(Key::new).collect(Collectors.toList());
         }
 
         public int compare(String o1, String o2) {
@@ -823,8 +1058,7 @@
         }
 
         protected int compareRegion(String s1, int start1, int end1, String s2, int start2, int end2, boolean caseInsensitive) {
-            int n1 = end1, n2 = end2;
-            for (int i1 = start1, i2 = start2; i1 < end1 && i2 < n2; i1++, i2++) {
+            for (int i1 = start1, i2 = start2; i1 < end1 && i2 < end2; i1++, i2++) {
                 char c1 = s1.charAt(i1);
                 char c2 = s2.charAt(i2);
                 if (c1 != c2) {
@@ -843,7 +1077,7 @@
                     }
                 }
             }
-            return n1 - n2;
+            return end1 - end2;
         }
 
         protected int[] getSortKey(String str, List<Integer> fields, Key key) {
@@ -882,7 +1116,6 @@
             List<Integer> fields = new ArrayList<>();
             if (o.length() > 0) {
                 if (separator == '\0') {
-                    int i = 0;
                     fields.add(0);
                     for (int idx = 1; idx < o.length(); idx++) {
                         if (Character.isWhitespace(o.charAt(idx)) && !Character.isWhitespace(o.charAt(idx - 1))) {
@@ -1014,74 +1247,57 @@
         }
     }
 
-    private String[] expand(CommandSession session, String[] argv) throws IOException {
-        String reserved = "(?<!\\\\)[*(|<\\[?]";
-        List<String> params = new ArrayList<>();
-        for (String arg : argv) {
-            if (arg.matches(".*" + reserved + ".*")) {
-                String org = arg;
-                List<String> expanded = new ArrayList<>();
-                Path currentDir = session.currentDir();
-                Path dir;
-                String pfx = arg.replaceFirst(reserved + ".*", "");
-                String prefix;
-                if (pfx.indexOf('/') >= 0) {
-                    pfx = pfx.substring(0, pfx.lastIndexOf('/'));
-                    arg = arg.substring(pfx.length() + 1);
-                    dir = currentDir.resolve(pfx).normalize();
-                    prefix = pfx + "/";
-                } else {
-                    dir = currentDir;
-                    prefix = "";
-                }
-                PathMatcher matcher = dir.getFileSystem().getPathMatcher("glob:" + arg);
-                Files.walkFileTree(dir,
-                        EnumSet.of(FileVisitOption.FOLLOW_LINKS),
-                        Integer.MAX_VALUE,
-                        new FileVisitor<Path>() {
-                    @Override
-                    public FileVisitResult preVisitDirectory(Path file, BasicFileAttributes attrs) throws IOException {
-                        if (file.equals(dir)) {
-                            return FileVisitResult.CONTINUE;
-                        }
-                        if (Files.isHidden(file)) {
-                            return FileVisitResult.SKIP_SUBTREE;
-                        }
-                        Path r = dir.relativize(file);
-                        if (matcher.matches(r)) {
-                            expanded.add(prefix + r.toString());
-                        }
-                        return FileVisitResult.CONTINUE;
-                    }
-                    @Override
-                    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
-                        if (!Files.isHidden(file)) {
-                            Path r = dir.relativize(file);
-                            if (matcher.matches(r)) {
-                                expanded.add(prefix + r.toString());
-                            }
-                        }
-                        return FileVisitResult.CONTINUE;
-                    }
-                    @Override
-                    public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
-                        return FileVisitResult.CONTINUE;
-                    }
-                    @Override
-                    public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
-                        return FileVisitResult.CONTINUE;
-                    }
-                });
-                Collections.sort(expanded);
-                if (expanded.isEmpty()) {
-                    throw new IOException("no matches found: " + org);
-                }
-                params.addAll(expanded);
-            } else {
-                params.add(arg);
+    private static LinkOption[] getLinkOptions(boolean followLinks) {
+        if (followLinks) {
+            return EMPTY_LINK_OPTIONS;
+        } else {    // return a clone that modifications to the array will not affect others
+            return NO_FOLLOW_OPTIONS.clone();
+        }
+    }
+
+    /**
+     * @param fileName The file name to be evaluated - ignored if {@code null}/empty
+     * @return {@code true} if the file ends in one of the {@link #WINDOWS_EXECUTABLE_EXTENSIONS}
+     */
+    private static boolean isWindowsExecutable(String fileName) {
+        if ((fileName == null) || (fileName.length() <= 0)) {
+            return false;
+        }
+        for (String suffix : WINDOWS_EXECUTABLE_EXTENSIONS) {
+            if (fileName.endsWith(suffix)) {
+                return true;
             }
         }
-        return params.toArray(new String[params.size()]);
+        return false;
+    }
+
+    /**
+     * @param f The {@link File} to be checked
+     * @return A {@link Set} of {@link PosixFilePermission}s based on whether
+     * the file is readable/writable/executable. If so, then <U>all</U> the
+     * relevant permissions are set (i.e., owner, group and others)
+     */
+    private static Set<PosixFilePermission> getPermissionsFromFile(File f) {
+        Set<PosixFilePermission> perms = EnumSet.noneOf(PosixFilePermission.class);
+        if (f.canRead()) {
+            perms.add(PosixFilePermission.OWNER_READ);
+            perms.add(PosixFilePermission.GROUP_READ);
+            perms.add(PosixFilePermission.OTHERS_READ);
+        }
+
+        if (f.canWrite()) {
+            perms.add(PosixFilePermission.OWNER_WRITE);
+            perms.add(PosixFilePermission.GROUP_WRITE);
+            perms.add(PosixFilePermission.OTHERS_WRITE);
+        }
+
+        if (f.canExecute() || (OsUtils.isWin32() && isWindowsExecutable(f.getName()))) {
+            perms.add(PosixFilePermission.OWNER_EXECUTE);
+            perms.add(PosixFilePermission.GROUP_EXECUTE);
+            perms.add(PosixFilePermission.OTHERS_EXECUTE);
+        }
+
+        return perms;
     }
 
     public static Map<String, String> getColorMap(CommandSession session, String name) {
@@ -1099,19 +1315,4 @@
                                           s -> s.substring(s.indexOf('=') + 1)));
     }
 
-    private Options parseOptions(CommandSession session, String[] usage, Object[] argv) throws Exception {
-        Options opt = Options.compile(usage, s -> get(session, s)).parse(argv, true);
-        if (opt.isSet("help")) {
-            ByteArrayOutputStream baos = new ByteArrayOutputStream();
-            opt.usage(new PrintStream(baos));
-            throw new HelpException(baos.toString());
-        }
-        return opt;
-    }
-
-    private String get(CommandSession session, String name) {
-        Object o = session.get(name);
-        return o != null ? o.toString() : null;
-    }
-
 }
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Procedural.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Procedural.java
index 0311f1b..d1bf87e 100644
--- a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Procedural.java
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Procedural.java
@@ -26,13 +26,13 @@
 import org.apache.felix.service.command.Function;
 
 public class Procedural {
-    static final String[] functions = {"each", "if", "not", "throw", "try", "until",
-            "while"};
+
+    static final String[] functions = {"each", "if", "not", "throw", "try", "until", "while"};
 
     public List<Object> each(CommandSession session, Collection<Object> list,
                              Function closure) throws Exception {
-        List<Object> args = new ArrayList<Object>();
-        List<Object> results = new ArrayList<Object>();
+        List<Object> args = new ArrayList<>();
+        List<Object> results = new ArrayList<>();
         args.add(null);
 
         for (Object x : list) {
@@ -61,11 +61,8 @@
     }
 
     public boolean not(CommandSession session, Function condition) throws Exception {
-        if (null == condition) {
-            return true;
-        }
+        return condition == null || !isTrue(condition.execute(session, null));
 
-        return !isTrue(condition.execute(session, null));
     }
 
     // Reflective.coerce() prefers to construct a new Throwable(String)
@@ -126,7 +123,7 @@
             return false;
 
         if (result instanceof Boolean)
-            return ((Boolean) result).booleanValue();
+            return (Boolean) result;
 
         if (result instanceof Number) {
             if (0 == ((Number) result).intValue())
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Shell.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Shell.java
index 740242d..81c612d 100644
--- a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Shell.java
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/Shell.java
@@ -53,6 +53,7 @@
 import org.apache.felix.service.command.Function;
 import org.apache.felix.service.command.Parameter;
 import org.jline.builtins.Completers.CompletionData;
+import org.jline.builtins.Completers.CompletionEnvironment;
 import org.jline.builtins.Options;
 import org.jline.reader.EndOfFileException;
 import org.jline.reader.LineReader;
@@ -88,11 +89,11 @@
     private final Context context;
     private final CommandProcessor processor;
 
-    public Shell(Context context, CommandProcessor processor, Terminal terminal) {
-        this(context, processor, terminal, null);
+    public Shell(Context context, CommandProcessor processor) {
+        this(context, processor, null);
     }
 
-    public Shell(Context context, CommandProcessor processor, Terminal terminal, String profile) {
+    public Shell(Context context, CommandProcessor processor, String profile) {
         this.context = context;
         this.processor = processor;
         String baseDir = context.getProperty("gosh.home");
@@ -243,11 +244,9 @@
         }
 
         // export variables starting with upper-case to newSession
-        for (String key : getVariables(session)) {
-            if (key.matches("[.]?[A-Z].*")) {
-                newSession.put(key, session.get(key));
-            }
-        }
+        getVariables(session).stream()
+                .filter(key -> key.matches("[.]?[A-Z].*"))
+                .forEach(key -> newSession.put(key, session.get(key)));
 
         Terminal terminal = getTerminal(session);
         newSession.put(Shell.VAR_CONTEXT, context);
@@ -261,10 +260,34 @@
 
         LineReader reader;
         if (args.isEmpty() && interactive) {
+            CompletionEnvironment completionEnvironment = new CompletionEnvironment() {
+                @Override
+                public Map<String, List<CompletionData>> getCompletions() {
+                    return Shell.getCompletions(newSession);
+                }
+                @Override
+                public Set<String> getCommands() {
+                    return Shell.getCommands(session);
+                }
+                @Override
+                public String resolveCommand(String command) {
+                    return Shell.resolve(session, command);
+                }
+                @Override
+                public String commandName(String command) {
+                    int idx = command.indexOf(':');
+                    return idx >= 0 ? command.substring(idx + 1) : command;
+                }
+                @Override
+                public Object evaluate(LineReader reader, ParsedLine line, String func) throws Exception {
+                    session.put(Shell.VAR_COMMAND_LINE, line);
+                    return session.execute(func);
+                }
+            };
             reader = LineReaderBuilder.builder()
                     .terminal(terminal)
                     .variables(((CommandSessionImpl) newSession).getVariables())
-                    .completer(new org.jline.builtins.Completers.Completer(new JLineCompletionEnvironment(newSession)))
+                    .completer(new org.jline.builtins.Completers.Completer(completionEnvironment))
                     .highlighter(new Highlighter(session))
                     .history(new FileHistory(new File(System.getProperty("user.home"), ".gogo.history")))
                     .parser(new Parser())
@@ -357,6 +380,7 @@
                             while (true) {
                                 Job job = session.foregroundJob();
                                 if (job != null) {
+                                    //noinspection SynchronizationOnLocalVariableOrMethodParameter
                                     synchronized (job) {
                                         if (job.status() == Status.Foreground) {
                                             job.wait();
@@ -420,6 +444,7 @@
         return result;
     }
 
+    @Descriptor("start a new shell")
     public Object sh(final CommandSession session, String[] argv) throws Exception {
         return gosh(session, argv);
     }
@@ -428,6 +453,7 @@
         context.exit();
     }
 
+    @Descriptor("Evaluates contents of file")
     public Object source(CommandSession session, String script) throws Exception {
         URI uri = session.currentDir().toUri().resolve(script);
         session.put("0", uri);
@@ -439,7 +465,7 @@
     }
 
     private Map<String, List<Method>> getReflectionCommands(CommandSession session) {
-        Map<String, List<Method>> commands = new TreeMap<String, List<Method>>();
+        Map<String, List<Method>> commands = new TreeMap<>();
         Set<String> names = getCommands(session);
         for (String name : names) {
             Function function = (Function) session.get(name);
@@ -471,9 +497,7 @@
     @Descriptor("displays available commands")
     public void help(CommandSession session) {
         Map<String, List<Method>> commands = getReflectionCommands(session);
-        for (String name : commands.keySet()) {
-            System.out.println(name);
-        }
+        commands.keySet().forEach(System.out::println);
     }
 
     @Descriptor("displays information about a specific command")
@@ -517,7 +541,7 @@
                 Map<String, String> flagDescs = new TreeMap<>();
                 Map<String, Parameter> options = new TreeMap<>();
                 Map<String, String> optionDescs = new TreeMap<>();
-                List<String> params = new ArrayList<String>();
+                List<String> params = new ArrayList<>();
                 Annotation[][] anns = m.getParameterAnnotations();
                 for (int paramIdx = 0; paramIdx < anns.length; paramIdx++) {
                     Class<?> paramType = m.getParameterTypes()[paramIdx];
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ssh/ShellFactoryImpl.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ssh/ShellFactoryImpl.java
index ad0cc8d..ed717e2 100644
--- a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ssh/ShellFactoryImpl.java
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/ssh/ShellFactoryImpl.java
@@ -257,7 +257,7 @@
                         destroy();
                     }
                 };
-                new Shell(context, processor, terminal).gosh(session, new String[]{"--login"});
+                new Shell(context, processor).gosh(session, new String[]{"--login"});
             } catch (Throwable t) {
                 t.printStackTrace();
             }
diff --git a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/Telnet.java b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/Telnet.java
index 7bba28b..f4d6537 100644
--- a/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/Telnet.java
+++ b/gogo/jline/src/main/java/org/apache/felix/gogo/jline/telnet/Telnet.java
@@ -178,7 +178,7 @@
                                 close();
                             }
                         };
-                        new Shell(context, processor, terminal).gosh(session, new String[]{"--login"});
+                        new Shell(context, processor).gosh(session, new String[]{"--login"});
                     }
 
                     @Override